Checks
Checks run after run, in verify, and alone with skill-be check, over every skill that has an eval file.
Typecheck
Section titled “Typecheck”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 withlibraryset it treats the library’s exports as in scope. - It compiles without the DOM lib, so a global such as
Optionorfetchcannot shadow the library. - It allows untyped parameters (no
noImplicitAny) and tolerates errors about unknown names (TS2304, TS2552, TS2584) andunknown-typed values (TS18046). It also tolerates arithmetic on two calls writtena(…) / 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, addskipafter its language:
```ts skipasync 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'.Coverage
Section titled “Coverage”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.allocateChecks for another language
Section titled “Checks for another language”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: truescripts/check_block.py gets one block on stdin and fails when it does not parse:
import astimport osimport sys
# skill-be sends every block that has a language tag: check only the Python onesif 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 belowWhat the tables above leave implicit:
-
Every tagged block goes to the typecheck command. That includes
bash,jsonandyamlblocks, not only the library’s language: every fenced block with a language tag that is not markedskip. The script readsSKILL_BE_LANGand exits 0 for the languages it does not check, ascheck_block.pydoes. -
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.mdline 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.memberand counts a mention of the member name; a command gets no such rule. Printallocate, notMoney.allocate, if a skill that mentionsallocateshould cover it. Names inignoreare 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.