Skip to content

Checks

Checks run after run, in verify, and alone with skill-be check, over every skill that has an eval file.

Blocking by default. It extracts every fenced code block tagged ts or typescript from each SKILL.md and compiles it. The preset is lenient: its job is to catch wrong use of the library’s API, not to make every fragment a complete program.

  • It carries imports over from earlier blocks in the same SKILL.md, and with library set it treats the library’s exports as in scope.
  • It compiles without the DOM lib, so a global such as Option or fetch cannot shadow the library.
  • It allows untyped parameters (no noImplicitAny) and tolerates errors about unknown names (TS2304, TS2552, TS2584) and unknown-typed values (TS18046). It also tolerates arithmetic on two calls written a(…) / b(…), the notation for “either form”. Every other error fails, including a wrong argument type, an unknown method or a wrong arity on a library function.
  • It skips a block that does not parse (a placeholder such as async x => …), with a warning in the output. To skip a block on purpose, add skip after its language:
```ts skip
async x => …
```
checks:
typecheck:
library: tiny-money # a package name, or a path to its source entry
preludes:
money: ./typecheck/money.ts # per skill: a file declaring names the examples use
blocking: true
Key Meaning
library A package name resolved from the project’s node_modules, or a path to the library’s source entry. Without it no exports are in scope, and an import of the library must resolve from node_modules
preludes A map from skill name to a TypeScript file, compiled with that skill’s blocks. A file without imports declares global names, for example declare const db: Db;, so more of an example is checked
blocking Default true
command Another language: runs this command line (same rules as the command adapter) once per fenced block that has a language tag and is not marked skip, with the code on stdin and the environment variables SKILL_BE_LANG and SKILL_BE_SKILL. A non-zero exit is a problem. Each block may take 60 seconds
preset Only typescript; it is implied when command is not set

When command is set it takes the place of the preset. checks.typecheck: false turns the check off. A problem is reported with its SKILL.md line:

typecheck: 1 problem(s)
- skill "money", SKILL.md line 51: error TS2339: Property 'from' does not exist on type 'typeof Money'.

Off unless configured; a warning by default, blocking with blocking: true. It lists the library’s public API and reports every name that no SKILL.md and no eval file mentions as a whole word (in an eval file, the strings count). A name mentioned in any skill’s SKILL.md or eval file is covered.

checks:
coverage:
entry: ./lib/index.ts
ignore: [Money.zero]
blocking: false
Key Meaning
entry The TypeScript preset’s source entry file. It reads the entry’s exports and the public members of exported classes, named Class.member (matched by the member name). Required for the preset
ignore Names to skip, as they are reported
blocking Default false
command Another language: runs this command line and reads the API names it prints, one per line, with a 60 second limit
coverage: 1 API name(s) not mentioned in any SKILL.md or eval file, not blocking: Money.allocate

For a library in another language, both checks take a command instead of the TypeScript preset. This example checks a Python library, tiny_money, with two scripts next to skill-be.yaml:

checks:
typecheck:
command: python scripts/check_block.py
coverage:
command: python scripts/list_api.py
blocking: true

scripts/check_block.py gets one block on stdin and fails when it does not parse:

import ast
import os
import sys
# skill-be sends every block that has a language tag: check only the Python ones
if os.environ.get("SKILL_BE_LANG") not in ("python", "py"):
sys.exit(0)
try:
ast.parse(sys.stdin.read())
except SyntaxError as error:
print(f"line {error.lineno}: {error.msg}", file=sys.stderr)
sys.exit(1)

scripts/list_api.py prints the API, one name per line:

import inspect
import tiny_money
for name in tiny_money.__all__:
print(name)
exported = getattr(tiny_money, name)
if inspect.isclass(exported):
for member in vars(exported):
if not member.startswith("_"):
print(member) # "allocate", not "Money.allocate": see below

What the tables above leave implicit:

  • Every tagged block goes to the typecheck command. That includes bash, json and yaml blocks, not only the library’s language: every fenced block with a language tag that is not marked skip. The script reads SKILL_BE_LANG and exits 0 for the languages it does not check, as check_block.py does.

  • The command replaces the whole preset. Nothing of the TypeScript preset applies: imports do not carry over from earlier blocks, the library’s exports are not in scope, and no error is tolerated. Each block is checked alone, so a block that uses a name from an earlier block must import or define it again, or the script must allow for it.

  • How the command runs. It is started without a shell, in the config file’s directory, the same rules as the command adapter: quote a word that contains spaces, and wrap pipes or redirects in a script. Each block may take 60 seconds. A problem shows the block’s SKILL.md line and the end of the command’s stderr, or of its stdout when stderr is empty:

    typecheck: 1 problem(s)
    - skill "money", SKILL.md line 12: the typecheck command failed (exit code 1): line 1: '(' was never closed
  • Coverage matches each printed name exactly as printed. The TypeScript preset reports Class.member and counts a mention of the member name; a command gets no such rule. Print allocate, not Money.allocate, if a skill that mentions allocate should cover it. Names in ignore are matched as printed too.

ast.parse only checks the syntax. To check types as well, have the script write the block to a temporary file and run a type checker such as mypy on it.

Code tests stay TypeScript only: see Other languages.