.md file to compare - side-by-side diff against bootstrap
bootstrap
description: "Triggers on prompt mention of 'bootstrap'."
What it does for you
Teaches your assistant a brand-new skill without you writing anything.
What it produces
A recent result, so you can see the kind of work it returns.
loading…
How to get it
These run inside the Snappy workspace. Want this working in your business? I set skills like this up with you, in one focused week.
For developers how this skill is built, graded, and how it runs
at a glance- the short version
what's inside - the parts that make up a skill 2/4 present
A skill is just a few plain-text files. Only the main one is required. The rest are optional, added as the work needs them. This is what the skill is made of; how it runs is just below.
state/skills/bootstrap/SKILL.md
present
state/lib/bootstrap.ts
not present
state/bin/bootstrap/
not present
state/skills/bootstrap/AGENTS.md
present
how it's graded - what counts as a good run 4 criteria · 3 deterministic · 1 judge
Each row is one thing a good run has to get right. deterministic means a quick check decides, pass or fail. judge means the AI reads the result and rates it. Grading each piece on its own (instead of one overall score) shows exactly where a run fell short, so the fix is obvious.
how it runs - the shared frame every skill uses 5/5 present
Every skill runs the same way. One part does the work, a separate part checks it, and a short loader hands the AI exactly what it needs for the job. Anything this skill doesn't use shows a one-line note saying why, on purpose, not by accident.
State/lint/check.ts + state/lint/audit.ts + the lib state/log/evals.ndjson - NEVER port a leaf skill before its kernel-sibling imports are in state/lib/ — depth-first, not breadth-first. Recursive ports are the norm (Gotchas §Recursive depth)
- NEVER refactor kernel code during port — only the three mechanical rewrites are allowed (settings/load path, sibling api.ts paths, CLI-guard realpathSync wrapped in try/catch). Anything else gets logged to state/log/port-notes.ndjson and surfaced in the skill page Gotchas
- NEVER skip the "update state/index.md FIRST" step — check.ts will fail with index-skill-count-drift and force interleaving anyway
- ALWAYS use the strong import probe (Object.keys(m).filter(k => typeof m[k] === "function")) — weak Object.keys(m).length is satisfied by tsx's module wrapper even when nothing exported
- ALWAYS roll back on lib_loads == false OR lint_passes == false. Audit regressions are 0.5, not 0.0
what it has learned - fixes written back in over time sample
When a run hits something this skill didn't handle, the fix gets written back into the skill so it doesn't happen again. FIXED means it was corrected on the spot. LOGGED means it's queued for a bigger rewrite. Either way, the skill gets a little better and never makes the same mistake twice.
- Loading feedback rows…
how the work flows- who makes it, who checks it
read program.md + state/index.md + audit.ts output, then follow `state/skills/bootstrap/SKILL.md` §Steps 1-7
in order — `npx tsx -e 'import(...).then(m => Object.keys(m).filter(k => typeof m[k] === "function"))'` then `npx tsx state/lint/check.ts` then `npx tsx state/lint/audit.ts
SKILL.md- the skill, written out in plain English
<!-- kernel-ok: this skill IS the kernel→os port runbook; references to ../snappy-* paths are intentional migration-table examples. -->
bootstrap
This is the self-hosting test. If a fresh agent can execute this skill reading only program.md + state/ and no conversation context, the system is self-extending. If not, fix what's missing in program.md or this page and try again.
Preconditions
program.mdhas been read in full this session.state/index.mdhas been read in full this session.state/lint/audit.tshas been run this session and its output is the
source of truth for what gap to close.
$kernel_skillexists at~/projects/snappy-kernel/skills/$kernel_skill/.
Steps
1. Scope (no writes)
- Read
~/projects/snappy-kernel/skills/$kernel_skill/api.ts. - List every
import ... fromthat is not a Node builtin. For each,
classify: (a) already in state/lib/ (rewire to ./), (b) kernel sibling that needs recursive porting, or (c) external package (npm - leave alone).
- If any
(b)imports exist, recursively bootstrap them first - depth
first, not breadth. Do not port the leaf until its dependencies are in state/lib/.
- Read
state/skills/_template.mdonce. This is the starting point for
the new skill page.
2. Gate
Throw if any of these are true:
kernel_skilldirectory does not exist.- A file at
state/lib/<kernel_skill-without-prefix>.tsalready exists
(would clobber - require --force or a different name).
- A skill page at
state/skills/<skill_name>.mdalready exists. state/lint/check.tscurrently reports errors (bootstrap never runs on
a broken tree - fix first).
3. Port the lib
cp ~/projects/snappy-kernel/skills/$kernel_skill/api.ts \
~/projects/snappy-os/state/lib/<short_name>.ts
Rewrite rule - apply these three substitutions in order, then stop:
| Kernel pattern | Mini replacement |
|---|---|
from "../snappy-settings/load.ts" | from "./env.ts" |
from "../snappy-<name>/api.ts" | from "./<name>.ts" (only if the sibling is already in state/lib/) |
await import("../snappy-<name>/api.ts") | await import("./<name>.ts") (same condition) |
Do not refactor anything else. The kernel code is assumed to work; the port is a reshuffle, not a rewrite. If a rewrite is actually needed, log a note to state/log/port-notes.ndjson and flag it in the skill page's ## Gotchas section.
CLI-guard carve-out. Kernel api.ts files often end with a if (import.meta.url === \file://\${realpathSync(process.argv[1])}\) { ... } CLI-entry guard. realpathSync(process.argv[1]) crashes under await import() (the verify probe in §5), so this one pattern IS allowed to be patched: wrap the realpathSync call in a try { ... } catch { return; } or delete the guard block entirely. Any other rewrite still violates the rule. Log the carve-out as a row in state/log/port-notes.ndjson with {kind: "cli-guard-patch", file} so the next agent knows why the diff exists.
4. Write the skill page
cp state/skills/_template.md state/skills/$skill_name.md
Then fill in:
name:- $skill_namedescription:- one sentence derived from the kernel skill's SKILL.mdinputs:/requires:- derived from the exported functions' signatures## Steps- one bullet per exported function, pointing at the new lib## Eval- name the actor and the auditor explicitly. If you
cannot, mark eval: manual AND add a ## Gotchas note explaining which auditor would graduate it to auto.
## Gotchas- copy any loud warnings from the kernel SKILL.md skillatim
5. Update state/index.md FIRST
Catalog update is a prerequisite for verify, not a follow-up. Add one row to the Libraries table and one row to the appropriate Verbs section, and bump the ## Verbs (N total · R% auto-eval) header count. If you skip this, check.ts will immediately fail with index-skill-count-drift and you'll have to interleave anyway. Do it here.
6. Verify
Run these three commands in order. Any failure rolls back.
npx tsx -e 'import("/Users/robertboulos/projects/snappy-os/state/lib/<short_name>.ts").then(m => { const ns = m.default || m; const fns = Object.keys(ns).filter(k => typeof ns[k] === "function"); console.log(fns.length + " function exports: " + fns.join(",")); })'
npx tsx state/lint/check.ts
npx tsx state/lint/audit.ts
- Step 1 must print a function-export count that matches the number of
export function / export async function declarations in the kernel source. A weak check like Object.keys(m).length lets tsx's module wrapper satisfy "non-zero" even when nothing actually exported - filter to typeof m[k] === "function" so the probe is real.
- Step 2 must exit 0 (structural lint passes).
- Step 3 must exit 0 OR show strictly fewer phantoms than before the run
(new gaps are OK if they're nested ports; regressions are not). A phantom count that is unchanged counts as pass - baseline phantoms (external-deps, false-positive fn citations) are documented, not bugs.
7. Log + eval
append("chain", { run_id, skill: "bootstrap",
action: "ported",
kernel_skill, skill_name, lib_path, skill_path });
append("port", { wave: "incremental", via: "bootstrap",
kernel_skill, skill_name, exports, phantoms_before, phantoms_after });
score("bootstrap", run_id, {
score:
lib_loads && lint_passes && phantoms_not_worse ? 1.0 :
lib_loads && lint_passes ? 0.5 :
0.0,
exports_count, phantoms_delta,
primary_issue:
!lib_loads ? "lib-import-fail" :
!lint_passes ? "lint-errors" :
"phantoms-regressed",
});
Eval
Actor: the agent running this skill (human or subagent). Auditor: state/lint/check.ts + state/lint/audit.ts + the lib import probe. All three are deterministic scripts, none of them wrote the port.
Score convention:
| Outcome | Score |
|---|---|
| Lib loads, lint passes, audit delta ≤ 0 | 1.0 |
| Lib loads + lint passes but audit regressed | 0.5 |
| Lib fails to load OR lint errors | 0.0 - roll back |
Thesis check: bootstrap is only graduated when an agent with no conversation context beyond program.md + state/ can execute it end-to-end. If you find yourself editing this page to close a gap the agent hit, that gap is real - fix this page before running again.
Gotchas
- Recursive depth. If the kernel skill imports two other kernel
skills, you will be three ports deep before the leaf compiles. Budget for it. Log each nested port as its own port.ndjson row.
- JSDoc examples trip greps. The kernel api.ts files often have
@example blocks with literal import ... from "../snappy-..." strings. The lint's regex strips block comments before scanning, but if you extend the lint, preserve that behavior.
- Don't port
*.mdfiles. SKILL.md and AGENTS.md belong to the kernel
shape, not mini's. Extract the one or two load-bearing facts into the skill page's ## Gotchas section and leave the rest.
- Don't port scripts unless the skill needs them. Mini's bin/ is for
graduated skills only. A new prose skill points at state/lib/<name>.ts and nothing else.
Rubric
criteria:
- name: lib_port_correctness
kind: deterministic
check: "The ported library file at 'state/lib/<short_name>.ts' must exactly match the source kernel 'api.ts' with only the specified (and logged) rewrites applied, as verified by a diff tool ignoring the defined substitutions and CLI guard alterations."
- name: skill_page_completeness
kind: judge
check: "The generated skill page at 'state/skills/$skill_name.md' must have all required fields ('name', 'description', 'inputs', 'requires', 'steps', 'eval', 'gotchas') populated accurately and completely based on the kernel skill's metadata and the porting process, reflecting the intended purpose and usage of the new skill."
- name: state_index_updated
kind: deterministic
check: "The 'state/index.md' file must correctly reflect the addition of the new library and skill, including an incremented 'Verbs (N total)' count, within the same transaction as the porting."
- name: verification_steps_pass
kind: deterministic
check: "All three verification commands in Step 6 must execute successfully, with the first printing the correct number of function exports, the second exiting with code 0, and the third exiting with code 0 or reporting strictly fewer phantoms than prior to the bootstrap execution."AGENTS.md- what the AI loads when this skill comes up
bootstrap - loader
Per-turn rules for the bootstrap skill. Full reference: state/skills/bootstrap/SKILL.md. Do not skip these.
Critical Rules
- NEVER port a leaf skill before its kernel-sibling imports are in
state/lib/- depth-first, not breadth-first. Recursive ports are the norm (Gotchas §Recursive depth) - NEVER refactor kernel code during port - only the three mechanical rewrites are allowed (settings/load path, sibling api.ts paths, CLI-guard
realpathSyncwrapped in try/catch). Anything else gets logged tostate/log/port-notes.ndjsonand surfaced in the skill page Gotchas - NEVER skip the "update
state/index.mdFIRST" step -check.tswill fail withindex-skill-count-driftand force interleaving anyway - ALWAYS use the strong import probe (
Object.keys(m).filter(k => typeof m[k] === "function")) - weakObject.keys(m).lengthis satisfied by tsx's module wrapper even when nothing exported - ALWAYS roll back on
lib_loads == falseORlint_passes == false. Audit regressions are 0.5, not 0.0
Commands
| ui dashboard | state/skills/bootstrap/resources/ui.openui | |invoke: read program.md + state/index.md + audit.ts output, then follow state/skills/bootstrap/SKILL.md §Steps 1-7 |verify: in order - npx tsx -e 'import(...).then(m => Object.keys(m).filter(k => typeof m[k] === "function"))' then npx tsx state/lint/check.ts then npx tsx state/lint/audit.ts |eval log: state/log/evals.ndjson (skill: "bootstrap"); also state/log/port.ndjson for each nested port
OpenUI Resource
- Skill-owned OpenUI Lang resource:
state/skills/bootstrap/resources/ui.openui. Read it before rendering or editing this skill's generated component surface. - Treat this resource as a first-class artifact of the skill, not a generic chat response. Improve it when the skill's user-facing output needs to become richer.
- System resources compose OpenUI primitives and inherit SnappyChat tokens. Use
ui_contract: brandedin SKILL.md only for deliberate platform or client visuals.
Known Pitfalls
- JSDoc
@exampleblocks trip greps. Kernel api.ts files have literalimport ... from "../snappy-..."strings inside/** */. The lint strips block comments before scanning - preserve that behavior if you extend it. - Don't port
*.mdfiles. SKILL.md / AGENTS.md belong to kernel shape. Extract one or two load-bearing facts into the new skill's## Gotchassection. - Don't port scripts unless the new skill is graduating. Mini's
bin/is for graduated skills only. - Phantom audit count unchanged ≠ regression. Baseline phantoms (external-deps, false-positive fn citations) are documented; only strict regressions fail.
- CLI-guard carve-out logging. When you wrap
realpathSync(process.argv[1]), log a{kind: "cli-guard-patch", file}row tostate/log/port-notes.ndjsonso the next agent knows why the diff exists.
Self-Test
An agent reading this should correctly:
- [ ] Recursively port a kernel-sibling import before touching the leaf
- [ ] Bump the
## Verbs (N total · R% auto-eval)count when adding a row to index.md - [ ] Use the strong typeof-function probe in step 6 verify
- [ ] Score 0.5 (not 0.0) when audit phantoms grow but lib loads + lint passes
Self-report
If this loader fell short, append a line:
echo "[$(date -u +%FT%TZ)] bootstrap: <what was missing>" >> state/log/loader-feedback.log
<!-- kernel-ok: historical attribution for the PID-loader pattern. -->
Self-correcting loader (PID feedback)
This is the rule that makes loaders learn. Ported from snappy-kernel, where it demonstrably worked: the snappy-dom-cartographer skill mapped Skool admin UI once, every later ship-agent inherited the full map for free.
Before you finish, do two things:
1. Fix gaps (P - proportional). MANDATORY. If this loader didn't cover your case - if you had to read another file, run an undocumented command, work around a wrong selector, or discover a quirk - you MUST attempt an Edit to this AGENTS.md before you log.
- Default action: edit this file. Surgical. Add the missing line, table
row, or code example where it belongs. No restructuring.
- Only
LOGGEDis allowed when: the fix needs >10 lines, spans multiple
files, or requires a structural rewrite. In that case the state/regen/drain.sh queue picks it up asynchronously.
- "I didn't have time" / "it's minor" / "the next agent will figure it out"
are NOT valid reasons. Minor gaps compound - that's the whole point of this loop.
- Concrete example of a valid P-fix: discover
agent-browser screenshot
takes path positionally not as --output <path>. One line in a table. Edit it in. Do not just log it.
- The goal: the next agent never has to leave the loader.
2. Log the result.
echo "[$(date -u +%FT%TZ)] bootstrap: <what was missing or fixed> [FIXED|LOGGED]" >> state/log/loader-feedback.log
FIXED= you patched this loader inline (P-fix).LOGGED= too large for inline; the PostToolUse enqueue + Stop-hook drain
will rewrite the loader from scratch on next session-end.
Do not skip this. Every agent run must leave the system better than it found it. The loader is the setpoint; you are the sensor; the gap is the error signal; closing the gap is the correction.
api.ts- the code it can call
⚠ no api.ts - this skill has no typed action surface
scripts- helper scripts it can run
prose-only skill - 5 inline code blocks live in SKILL.md above (no state/bin/ sidecar yet).
how we check it- the checks, plus the last 10 runs
| timestamp | verb | score | primary_issue | artifact |
|---|---|---|---|---|
| 2026-04-25 04:11Z | - | 1.00 | - | - |
| 2026-04-21 15:58Z | - | 1.00 | - | - |
| 2026-04-21 15:56Z | - | 1.00 | - | - |
| 2026-04-21 03:53Z | - | 1.00 | - | - |
| 2026-04-20 17:35Z | - | 1.00 | - | - |
| 2026-04-16 01:11Z | - | 1.00 | - | - |
| 2026-04-16 01:11Z | - | 1.00 | - | - |
| 2026-04-16 01:01Z | - | 1.00 | - | - |
| 2026-04-16 00:04Z | - | 1.00 | - | - |
| 2026-04-25 04:11Z | - | 1.00 | - | - |