snappy-tool-design skill
lint skill?readexample skill verbreadprobe skill?readrenderreadloopreadhostsreadspecs skill?write-reversibleopenapi skillreadfix-loader skillwrite-reversible$ npx snappy-skills install snappy-tool-design
$ npx snappy-skills install --all
$ npx snappy-skills update
Use this before trusting or changing a tool surface. It reads the executable
HAND_CONTRACT, then checks the implementation, loader, manual, probe corpus,
and render evidence against 60 sourced rules. Full citations are in SKILL.md.
**A whole-collection run is bounded by the pool; never run two lints or suites
in parallel on this Mac.** lint --all, probe, hosts and loop all reach
every hand's contract through loadModels, which loads that hand's whole
api.ts module graph — 98 of them. That fan-out now goes through
../snappy-settings/spawn-pool.ts (pooledAll, MAX_COLLECTION_JOBS =
min(4, cores/2)); pooled it is FASTER, 1.29 s vs 1.78 s with byte-identical
output ⟨measured 2026-09-09⟩. The bound is per process: three unpooled `lint
--all` passes beside two runs of the runner's suite put this Mac at load 311.
typescriptimport { lintSkill, lintAll, firstExample, exampleHazard, fixLoader } from "../snappy-tool-design/api.ts";
import { runProbe } from "../snappy-tool-design/probe.ts";
import { runRender } from "../snappy-tool-design/render.ts";
import { clusterFailures, writeLoopArtifacts } from "../snappy-tool-design/loop.ts";
| Function | Purpose |
|---|---|
lintSkill(skill) |
Grade one skill across 60 static/probe/render rules without executing its verbs |
lintAll() |
Grade every snappy-* contract in one process and return PASS/FAIL/DEFER evidence |
firstExample(skill, verb) |
Return the shortest call derived from the contract |
exampleHazard(skill, verb) |
Name where a bare count would land when rule 59 bites, else null |
fixLoader(skill) |
Ask snappy-skill's sole writer to sync the generated verb block |
runProbe(options) |
Drive the real MCP, READS ONLY, and return the probe corpus the 26 measured rules grade from |
runRender(options) |
Draw every published face kind in a real ext-apps host at 360 and 720 px and return the render corpus |
clusterFailures(reports) |
Group every FAIL by MECHANISM so one fix kills many defects |
writeLoopArtifacts(reports, out, root) |
Write briefs/<cluster>.md and the ranked LEDGER.md |
bashnpx tsx ~/.claude/skills/snappy-tool-design/api.ts lint snappy-gmail
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all --summary
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example snappy-gmail list
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example snappy-imessage recent # + a rule-59 warning on stderr
npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader snappy-gmail
# the closed loop. The bearer is read from a 600-mode FILE, never an argument.
SNAPPY_RUNNER_TOKEN_FILE=<file> npx tsx .../api.ts probe --via mini
npx tsx .../api.ts render --host-dir <loop dir> --shot-dir <shots>
npx tsx .../api.ts render --host-dir <loop dir> --kind notion-append --out /tmp/one.json
SNAPPY_RUNNER_TOKEN_FILE=<file> npx tsx .../api.ts loop --via mini
<!-- why: probe/render/loop are the verbs that make 26 of the 60 rules gradeable;
without them lint answers DEFER forever and the feedback loop never closes
(2026-09-08). The token is a FILE because an argument lands in ps and logs. -->
loop clusters failures by MECHANISM and ranks them by defects-killed-per-fix,
then writes a lane brief per cluster. Re-run it after every landing — the top
row is shorter each time. That is the exponential part.
HAND_CONTRACT.description.requires matches required credential reads; optional and host keys stay out.execution: "call"; probes prove one request and <3 s p50.the prompt, contract verbs as the tools, the files and the contract
as the resources — so the prompt must make the DERIVED FIRST CALL
correct. No optional positional may stand in front of a commonly-supplied
argument (limit, count, n, …), and the generated loader block must be
current against the contract. Measured 2026-09-08: snappy-imessage recent,
snappy-krisp fetch-meetings and snappy-thumbnails audit each taught
<verb> 5 and filed the 5 as a contact, a date, or a channel.
Static absence is FAIL. Missing live probe or render evidence is DEFER, never a
fabricated PASS. S19 and S20 are snippet-only and remain unquoted until verified.
lint is advisory and never calls the target verb. fix-loader is the only
write and is idempotent through snappy-skill.syncLoaderVerbTable().
For any SDK, dependency, or API: enumerate its REAL surface from the installed package (node_modules/<pkg> — dist, CLI commands, shipped docs) and fetch its llms.txt/llms-full.txt BEFORE writing a line of integration. Import the package's own types; never redeclare a type a dependency exports; never write an integration from memory; never describe a surface you haven't listed. A hand-written parallel of a generated, vendored, or documented definition is a defect (DRY, original sense: one authoritative representation of every piece of knowledge). Writing from training memory instead of the installed source produces hallucinated APIs — the named killer of OpenAI's plugin ecosystem. Printing Press (spec→generated connector) is this law's special case; it applies to EVERYTHING.
The system spec lives at ~/.claude/skills/snappy-settings/skill-spec.md. It defines the entire Snappy operating system. You enforce it.
1. Fix gaps (P — proportional). If this AGENTS.md didn't cover your case — if you had to read another file, run a command not documented here, work around a wrong selector, or discover a quirk — Edit it in before you log.
<!-- why: agent-browser wrote to CWD until we passed the path positionally (2026-08-05) -->
This is the single highest-value habit in the loop. An instruction whose rationale is legible can be safely deleted later; one whose rationale is lost can only be kept, which is how loaders grow without bound. Unreasoned additions are the defect this rule exists to prevent.
NOOP and move on. Do NOT manufacture a gap to have something to write. A fabricated "learning" is strictly worse than silence: it becomes ground truth for every later agent and cannot be distinguished from a real one afterward. Most runs on a mature loader should be NOOP.agent-browser screenshot <path> takes path positionally, not as --output <path>. That's one line in a table, plus its why: comment. Edit it in.2. Validate structure. Check this skill against the spec:
api.ts exists with typed exports, CLI mode, import { env } from "../snappy-settings/load.ts"api.ts calls APIs directly (no Xano proxy, unless this skill IS a Xano DB skill)AGENTS.md has API module section with import + function table + CLIcertificate: block (premises / action / trace / evidence / conclusion). Same-session toasts are NOT evidence — must be a fresh independent read. Missing or weak certificates are a gap: P-fix them inline and log to ~/.claude/logs/verification-gaps.log.~/.claude/skills/snappy-settings/scripts/dry-check.sh <name-or-keyword>. If it matches an existing skill, extend that one instead. Fragmentation is the enemy.2b. If you just reported an action as successful, verify your certificate. You may not claim PASS unless the evidence: line is a fresh independent read (reload, new session, server round-trip). If your evidence is a toast, a return value, or a same-session DOM read, downgrade the claim to INCONCLUSIVE and log to verification-gaps.log.
3. Log always.
bashecho "[$(date -u +%FT%TZ)] <skill-name>: <what was missing or fixed> [FIXED|LOGGED|NOOP]" >> ~/.claude/logs/agents-md-feedback.log
FIXED = you patched it inline (with its why: comment). LOGGED = too large for inline fix, queued for drain-skill-regen.sh. NOOP = you checked and the loader covered your case — the correct and expected outcome on a mature loader.
Report what actually happened. Every agent run must leave the system better than it found it — and a run that found nothing to fix has already done that by confirming the loader holds. Accuracy is the contribution; volume is not.
<!-- SKILL-INDEX-START -->
[snappy-tool-design Index]|root: ~/.claude/skills/snappy-tool-design|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{HOSTS-LEDGER.md,SKILL.md,hosts-battery.md,skill-check-rung.md}|briefs:{hosts-family-has-no-read.md,hosts-first-call-refuses.md,hosts-no-face-family.md,hosts-not-driven-chatgpt.md,hosts-not-driven-claude.md,hosts-not-driven-codex.md,hosts-not-driven-menubar.md,hosts-not-driven-telegram.md,hosts-road-names-no-face.md,hosts-verb-folds-onto-no-shape.md,r10-one-term-names-each-concept.md,r11-each-task-presents-one-default-not-a-menu.md,r12-description-contains-no-unauthorised-imperative.md,r13-argument-without-a-description.md,r15-near-miss-keys-refuse-with-the-correct-contract-key.md,r16-runner-known-values-are-not-required-arguments.md,r18-verb-without-an-effect-class.md,r19-annotations-not-derived-from-class.md,r20-read-and-write-are-separate-verbs.md,r23-responses-obey-hard-and-soft-caps.md,r24-limit-does-not-bound-the-answer.md,r31-refusal-envelope-incomplete.md,r32-refusals-name-a-literal-from-the-valid-domain.md,r33-refusal-codes-form-one-tested-closed-table.md,r35-requirements-are-declared-from-executable-credential-reads.md,r39-verb-sets-consolidate-intent-rather-than-mirror-endpoints.md,r4-near-neighbour-descriptions-cross-reference-each-other.md,r40-metadata-skill-body-and-resources-follow-three-level-budgets.md,r43-a-read-is-one-call-not-a-queued-job.md,r45-long-work-is-a-separately-named-job-verb.md,r5-description-is-valid-third-person-and-says-what-plus-when.md,r54-ready-without-evals.md,r55-eval-scenarios-are-multi-call-with-ground-truth.md,r57-eval-manifest-contains-a-disjoint-holdout.md,r60-loader-block-stale.md,r7-promised-and-declared-verbs-have-no-set-difference.md,r8-skill-body-and-reference-graph-stay-within-disclosure-budget.md,r9-time-sensitive-prose-is-isolated-under-old-patterns.md}
<!-- SKILL-INDEX-END -->
Nothing in the collection names this skill.
<!-- SNAPPY-CONTRACT-VERBS-START -->
Generated from api.ts HAND_CONTRACT. Do not hand-edit this block.
| Verb | Contract arguments | Effect | First call |
|---|---|---|---|
lint |
skill? |
read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint |
example |
skill, verb |
read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example <skill> <verb> |
probe |
skill? |
read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts probe |
render |
— | read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts render |
loop |
— | read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts loop |
hosts |
— | read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts hosts |
specs |
skill? |
write-reversible |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts specs |
openapi |
skill |
read |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts openapi <skill> |
fix-loader |
skill |
write-reversible |
npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader <skill> |
When an answer carries face_hint, show it with one snappy_present(<answer>) call.
See /snappy-faces for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->
---
name: snappy-tool-design
role: Contract-first ergonomics lint for every snappy-* skill.
loaded-by: the Skill tool / a room's skills folder
---
# snappy-tool-design — Agent Loader
Use this before trusting or changing a tool surface. It reads the executable
`HAND_CONTRACT`, then checks the implementation, loader, manual, probe corpus,
and render evidence against 60 sourced rules. Full citations are in `SKILL.md`.
**A whole-collection run is bounded by the pool; never run two lints or suites
in parallel on this Mac.** `lint --all`, `probe`, `hosts` and `loop` all reach
every hand's contract through `loadModels`, which loads that hand's whole
`api.ts` module graph — 98 of them. That fan-out now goes through
`../snappy-settings/spawn-pool.ts` (`pooledAll`, `MAX_COLLECTION_JOBS` =
min(4, cores/2)); pooled it is FASTER, 1.29 s vs 1.78 s with byte-identical
output ⟨measured 2026-09-09⟩. The bound is per process: three unpooled `lint
--all` passes beside two runs of the runner's suite put this Mac at load 311.
## API
```typescript
import { lintSkill, lintAll, firstExample, exampleHazard, fixLoader } from "../snappy-tool-design/api.ts";
import { runProbe } from "../snappy-tool-design/probe.ts";
import { runRender } from "../snappy-tool-design/render.ts";
import { clusterFailures, writeLoopArtifacts } from "../snappy-tool-design/loop.ts";
```
| Function | Purpose |
|---|---|
| `lintSkill(skill)` | Grade one skill across 60 static/probe/render rules without executing its verbs |
| `lintAll()` | Grade every snappy-* contract in one process and return PASS/FAIL/DEFER evidence |
| `firstExample(skill, verb)` | Return the shortest call derived from the contract |
| `exampleHazard(skill, verb)` | Name where a bare count would land when rule 59 bites, else `null` |
| `fixLoader(skill)` | Ask snappy-skill's sole writer to sync the generated verb block |
| `runProbe(options)` | Drive the real MCP, READS ONLY, and return the probe corpus the 26 measured rules grade from |
| `runRender(options)` | Draw every published face kind in a real ext-apps host at 360 and 720 px and return the render corpus |
| `clusterFailures(reports)` | Group every FAIL by MECHANISM so one fix kills many defects |
| `writeLoopArtifacts(reports, out, root)` | Write `briefs/<cluster>.md` and the ranked `LEDGER.md` |
## CLI
```bash
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint snappy-gmail
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all --summary
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example snappy-gmail list
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example snappy-imessage recent # + a rule-59 warning on stderr
npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader snappy-gmail
# the closed loop. The bearer is read from a 600-mode FILE, never an argument.
SNAPPY_RUNNER_TOKEN_FILE=<file> npx tsx .../api.ts probe --via mini
npx tsx .../api.ts render --host-dir <loop dir> --shot-dir <shots>
npx tsx .../api.ts render --host-dir <loop dir> --kind notion-append --out /tmp/one.json
SNAPPY_RUNNER_TOKEN_FILE=<file> npx tsx .../api.ts loop --via mini
```
<!-- why: probe/render/loop are the verbs that make 26 of the 60 rules gradeable;
without them lint answers DEFER forever and the feedback loop never closes
(2026-09-08). The token is a FILE because an argument lands in ps and logs. -->
`loop` clusters failures by MECHANISM and ranks them by defects-killed-per-fix,
then writes a lane brief per cluster. Re-run it after every landing — the top
row is shorter each time. That is the exponential part.
## Rules
1. Description is projected exactly from `HAND_CONTRACT.description`.
2. Promised and declared verbs have no set difference.
3. Every argument is described; enumerable domains are enums.
4. Near-miss keys refuse with the correct key from the contract.
5. Collection reads declare a limit default and maximum; answer probes prove growth is bounded.
6. Every verb declares an effect class and class-derived MCP annotations.
7. `requires` matches required credential reads; optional and host keys stay out.
8. A read declares `execution: "call"`; probes prove one request and <3 s p50.
9. UI keeps text fallback and names PNG/OpenUI/text renditions by channel.
10. The MCP publishes one skill as all three primitives — loader and manual as
the **prompt**, contract verbs as the **tools**, the files and the contract
as the **resources** — so the prompt must make the DERIVED FIRST CALL
correct. No optional positional may stand in front of a commonly-supplied
argument (`limit`, `count`, `n`, …), and the generated loader block must be
current against the contract. Measured 2026-09-08: `snappy-imessage recent`,
`snappy-krisp fetch-meetings` and `snappy-thumbnails audit` each taught
`<verb> 5` and filed the 5 as a contact, a date, or a channel.
11. Ready skills have three multi-call evals, a holdout, and fresh-client proof.
Static absence is FAIL. Missing live probe or render evidence is DEFER, never a
fabricated PASS. S19 and S20 are snippet-only and remain unquoted until verified.
`lint` is advisory and never calls the target verb. `fix-loader` is the only
write and is idempotent through `snappy-skill.syncLoaderVerbTable()`.
---
## THE DERIVATION LAW — the installed artifact is the spec (Robert 2026-08-05, LAW)
For any SDK, dependency, or API: enumerate its REAL surface from the installed package (`node_modules/<pkg>` — dist, CLI commands, shipped docs) and fetch its `llms.txt`/`llms-full.txt` BEFORE writing a line of integration. Import the package's own types; never redeclare a type a dependency exports; never write an integration from memory; never describe a surface you haven't listed. A hand-written parallel of a generated, vendored, or documented definition is a defect (DRY, original sense: one authoritative representation of every piece of knowledge). Writing from training memory instead of the installed source produces hallucinated APIs — the named killer of OpenAI's plugin ecosystem. Printing Press (spec→generated connector) is this law's special case; it applies to EVERYTHING.
## Self-correcting loader (PID feedback)
The system spec lives at `~/.claude/skills/snappy-settings/skill-spec.md`. It defines the entire Snappy operating system. You enforce it.
### Before you finish, do two things:
**1. Fix gaps (P — proportional).** If this AGENTS.md didn't cover your case — if you had to read another file, run a command not documented here, work around a wrong selector, or discover a quirk — `Edit` it in before you log.
- **Default action: edit this AGENTS.md.** Surgical. Add the missing line, table row, or code example where it belongs. No restructuring.
- **Every line you add carries its reason.** Append an HTML comment recording *why* it exists and what breaks without it:
`<!-- why: agent-browser wrote to CWD until we passed the path positionally (2026-08-05) -->`
This is the single highest-value habit in the loop. An instruction whose rationale is legible can be safely deleted later; one whose rationale is lost can only be kept, which is how loaders grow without bound. Unreasoned additions are the defect this rule exists to prevent.
- **Only LOGGED when:** the fix needs >10 lines, spans multiple files, or requires a structural rewrite (in which case the drain-skill-regen.sh queue picks it up).
- **NOOP is a first-class, blameless outcome.** If you hit no gap — the loader covered your case — record `NOOP` and move on. Do NOT manufacture a gap to have something to write. A fabricated "learning" is strictly worse than silence: it becomes ground truth for every later agent and cannot be distinguished from a real one afterward. Most runs on a mature loader should be NOOP.
- Concrete example of a valid P-fix: lesson-2 ship agent discovered `agent-browser screenshot <path>` takes path positionally, not as `--output <path>`. That's one line in a table, plus its `why:` comment. Edit it in.
- The goal: the next agent never has to leave the loader — and the agent after that can still tell which lines have earned their place.
**2. Validate structure.** Check this skill against the spec:
- `api.ts` exists with typed exports, CLI mode, `import { env } from "../snappy-settings/load.ts"`
- `api.ts` calls APIs directly (no Xano proxy, unless this skill IS a Xano DB skill)
- `AGENTS.md` has API module section with import + function table + CLI
- No bash fallbacks, no hardcoded tokens anywhere
- **Every action row carries a `certificate:` block** (premises / action / trace / evidence / conclusion). Same-session toasts are NOT evidence — must be a fresh independent read. Missing or weak certificates are a gap: P-fix them inline and log to `~/.claude/logs/verification-gaps.log`.
- **DRY:** before you add a new function or new skill, run `~/.claude/skills/snappy-settings/scripts/dry-check.sh <name-or-keyword>`. If it matches an existing skill, extend that one instead. Fragmentation is the enemy.
- If any check fails → fix it or log it
**2b. If you just reported an action as successful, verify your certificate.** You may not claim PASS unless the `evidence:` line is a fresh independent read (reload, new session, server round-trip). If your evidence is a toast, a return value, or a same-session DOM read, downgrade the claim to INCONCLUSIVE and log to `verification-gaps.log`.
**3. Log always.**
```bash
echo "[$(date -u +%FT%TZ)] <skill-name>: <what was missing or fixed> [FIXED|LOGGED|NOOP]" >> ~/.claude/logs/agents-md-feedback.log
```
`FIXED` = you patched it inline (with its `why:` comment). `LOGGED` = too large for inline fix, queued for drain-skill-regen.sh. `NOOP` = you checked and the loader covered your case — the correct and expected outcome on a mature loader.
**Report what actually happened.** Every agent run must leave the system better than it found it — and a run that found nothing to fix has already done that by confirming the loader holds. Accuracy is the contribution; volume is not.
<!-- SKILL-INDEX-START -->
[snappy-tool-design Index]|root: ~/.claude/skills/snappy-tool-design|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{HOSTS-LEDGER.md,SKILL.md,hosts-battery.md,skill-check-rung.md}|briefs:{hosts-family-has-no-read.md,hosts-first-call-refuses.md,hosts-no-face-family.md,hosts-not-driven-chatgpt.md,hosts-not-driven-claude.md,hosts-not-driven-codex.md,hosts-not-driven-menubar.md,hosts-not-driven-telegram.md,hosts-road-names-no-face.md,hosts-verb-folds-onto-no-shape.md,r10-one-term-names-each-concept.md,r11-each-task-presents-one-default-not-a-menu.md,r12-description-contains-no-unauthorised-imperative.md,r13-argument-without-a-description.md,r15-near-miss-keys-refuse-with-the-correct-contract-key.md,r16-runner-known-values-are-not-required-arguments.md,r18-verb-without-an-effect-class.md,r19-annotations-not-derived-from-class.md,r20-read-and-write-are-separate-verbs.md,r23-responses-obey-hard-and-soft-caps.md,r24-limit-does-not-bound-the-answer.md,r31-refusal-envelope-incomplete.md,r32-refusals-name-a-literal-from-the-valid-domain.md,r33-refusal-codes-form-one-tested-closed-table.md,r35-requirements-are-declared-from-executable-credential-reads.md,r39-verb-sets-consolidate-intent-rather-than-mirror-endpoints.md,r4-near-neighbour-descriptions-cross-reference-each-other.md,r40-metadata-skill-body-and-resources-follow-three-level-budgets.md,r43-a-read-is-one-call-not-a-queued-job.md,r45-long-work-is-a-separately-named-job-verb.md,r5-description-is-valid-third-person-and-says-what-plus-when.md,r54-ready-without-evals.md,r55-eval-scenarios-are-multi-call-with-ground-truth.md,r57-eval-manifest-contains-a-disjoint-holdout.md,r60-loader-block-stale.md,r7-promised-and-declared-verbs-have-no-set-difference.md,r8-skill-body-and-reference-graph-stay-within-disclosure-budget.md,r9-time-sensitive-prose-is-isolated-under-old-patterns.md}
<!-- SKILL-INDEX-END -->
## Used by
Nothing in the collection names this skill.
<!-- SNAPPY-CONTRACT-VERBS-START -->
## Contract verbs
Generated from `api.ts` `HAND_CONTRACT`. Do not hand-edit this block.
| Verb | Contract arguments | Effect | First call |
|---|---|---|---|
| `lint` | `skill?` | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint` |
| `example` | `skill`, `verb` | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts example <skill> <verb>` |
| `probe` | `skill?` | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts probe` |
| `render` | — | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts render` |
| `loop` | — | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts loop` |
| `hosts` | — | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts hosts` |
| `specs` | `skill?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts specs` |
| `openapi` | `skill` | `read` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts openapi <skill>` |
| `fix-loader` | `skill` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader <skill>` |
## Show the result
When an answer carries `face_hint`, show it with one `snappy_present(<answer>)` call.
See `/snappy-faces` for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->
Generated 2026-09-09T11:04:42.350Z by api.ts hosts.
hosts: 46/84 cells — counted against 1da5a73711-41156a7220a9-1879d420a2 through https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp, which is the kernel head this run was cut from. 98 hands, proved at 2026-09-09T11:03:41.310Z on https://roberts-mac-mini.tailca7f42.ts.net/runner/health.
| host | gmail-inbox | skool-feed | linkedin-profile | linkedin-decide |
|---|---|---|---|---|
| ChatGPT web app | not driven | not driven | not driven | not driven |
| Claude.ai web app | not driven | not driven | not driven | not driven |
| Codex CLI | not driven | not driven | not driven | not driven |
| Snappy menu bar | mounted 6953 ms | mounted 4944 ms | mounted 4930 ms | mounted 4569 ms |
| Telegram on the phone | mounted · standing #8387 | mounted · standing #8388 | mounted · standing #8389 | mounted · standing #8386 |
Green is a CARD ELEMENT in that host's own DOM; on linkedin-decide green also
requires the press to have reached the runner. A host that could not be driven
is a named red cell, never a blank one.
The same asks, put to the runner's own MCP door, so a red cell can be READ: a
face the runner never named could not have been drawn by any host on any row.
Not a host and deliberately NOT in the number.
| ask | hand · verb | outcome | face named | ms | hops |
|---|---|---|---|---|---|
| gmail-inbox | snappy-gmail list |
answered | gmail-list | 1745 | 1 |
| skool-feed | snappy-skool posts |
answered | skool-feed | 1053 | 1 |
| linkedin-profile | snappy-linkedin profile |
answered | linkedin-profile | 902 | 1 |
| linkedin-decide | snappy-linkedin post |
not driven | none | — | 0 |
Every family snappy-faces publishes gets a cell, and its green is A READ THAT
NAMES IT — a hand the runner's own familyForHand routes there, holding a verb
that folds onto a shape. A family with no read draws for nobody, and that is
counted, because a library that grows without reads grows the picture and not
the product.
| family | the read that names it | |
|---|---|---|
agent |
snappy-agent-host status → agent-run |
reached |
blocks |
none | no hand named snappy-blocks exists and no hand's contract declares a "blocks" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "blocks" faces, however well they draw. If a hand already makes this thing, it says so with verbs.<verb>.face; if none does, this family is made by no hand yet and that is the honest red |
calendar |
snappy-calendar list → list |
reached |
code |
none | no hand named snappy-code exists and no hand's contract declares a "code" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "code" faces, however well they draw. If a hand already makes this thing, it says so with verbs.<verb>.face; if none does, this family is made by no hand yet and that is the honest red |
data |
snappy-database query → data-table |
reached |
deploy |
snappy-deploy status → deploy-receipt |
reached |
doc |
snappy-docs page → doc-card |
reached |
freshbooks |
snappy-freshbooks list → list |
reached |
github |
snappy-github comments → list |
reached |
gmail |
snappy-gmail list → list |
reached |
imageset |
snappy-image logos → imageset-grid |
reached |
imessage |
snappy-imessage recent → list |
reached |
instagram |
none | no hand named snappy-instagram exists and no hand's contract declares a "instagram" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "instagram" faces, however well they draw. If a hand already makes this thing, it says so with verbs.<verb>.face; if none does, this family is made by no hand yet and that is the honest red |
knowledge |
snappy-knowledge get → one |
reached |
krisp |
snappy-krisp fetch-action-items → krisp-actions |
reached |
linkedin |
snappy-linkedin profile → profile |
reached |
media |
snappy-ffmpeg probe → media-video |
reached |
notion |
snappy-notion me → profile |
reached |
schedule |
snappy-scheduling propose → schedule-pick |
reached |
sheets |
none | no hand named snappy-sheets exists and no hand's contract declares a "sheets" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "sheets" faces, however well they draw. If a hand already makes this thing, it says so with verbs.<verb>.face; if none does, this family is made by no hand yet and that is the honest red |
skool |
snappy-skool me → profile |
reached |
slack |
snappy-slack messages → list |
reached |
statechange |
snappy-statechange posts → list |
reached |
stripe |
none | no hand named snappy-stripe exists and no hand's contract declares a "stripe" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "stripe" faces, however well they draw. If a hand already makes this thing, it says so with verbs.<verb>.face; if none does, this family is made by no hand yet and that is the honest red |
telegram |
snappy-telegram read → one |
reached |
thumbnails |
snappy-thumbnails variants → thumbnails-set |
reached |
transcripts |
snappy-transcripts list → list |
reached |
walkthrough |
snappy-walkthrough steps → walkthrough-steps |
reached |
whatsapp |
snappy-whatsapp read → one |
reached |
x |
none | no hand named snappy-x exists and no hand's contract declares a "x" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "x" faces, however well they draw. If a hand already makes this thing, it says so with verbs.<verb>.face; if none does, this family is made by no hand yet and that is the honest red |
youtube |
snappy-youtube comments → list |
reached |
| # | cluster | cells | what has to change |
|---|---|---|---|
| 1 | hosts-road-names-no-face |
1 | 1 of the four asks never reach a named face on the runner itself |
| 2 | hosts-not-driven-chatgpt |
4 | ChatGPT web app could not be driven at all |
| 3 | hosts-not-driven-claude |
4 | Claude.ai web app could not be driven at all |
| 4 | hosts-not-driven-codex |
4 | Codex CLI could not be driven at all |
| 5 | hosts-verb-folds-onto-no-shape |
56 | 56 hand(s) have no verb the face road can even shape |
| 6 | hosts-no-face-family |
18 | 18 platform hand(s) name no face family |
| 7 | hosts-family-has-no-read |
6 | 6 published face family(ies) no read can reach |
bash agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt
bash agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude
bash open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>## hosts — the same four asks, every host, counted Generated 2026-09-09T11:04:42.350Z by `api.ts hosts`. **hosts: 46/84 cells** — counted against `1da5a73711-41156a7220a9-1879d420a2` through `https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp`, which is the kernel head this run was cut from. 98 hands, proved at 2026-09-09T11:03:41.310Z on `https://roberts-mac-mini.tailca7f42.ts.net/runner/health`. | host | gmail-inbox | skool-feed | linkedin-profile | linkedin-decide | |---|---|---|---|---| | ChatGPT web app | not driven | not driven | not driven | not driven | | Claude.ai web app | not driven | not driven | not driven | not driven | | Codex CLI | not driven | not driven | not driven | not driven | | Snappy menu bar | **mounted** 6953 ms | **mounted** 4944 ms | **mounted** 4930 ms | **mounted** 4569 ms | | Telegram on the phone | **mounted** · standing #8387 | **mounted** · standing #8388 | **mounted** · standing #8389 | **mounted** · standing #8386 | Green is a CARD ELEMENT in that host's own DOM; on `linkedin-decide` green also requires the press to have reached the runner. A host that could not be driven is a named red cell, never a blank one. ### the road under the cells — what the runner itself answers The same asks, put to the runner's own MCP door, so a red cell can be READ: a face the runner never named could not have been drawn by any host on any row. Not a host and deliberately NOT in the number. | ask | hand · verb | outcome | face named | ms | hops | |---|---|---|---|---:|---:| | gmail-inbox | `snappy-gmail list` | answered | gmail-list | 1745 | 1 | | skool-feed | `snappy-skool posts` | answered | skool-feed | 1053 | 1 | | linkedin-profile | `snappy-linkedin profile` | answered | linkedin-profile | 902 | 1 | | linkedin-decide | `snappy-linkedin post` | not driven | **none** | — | 0 | ### the families, and the read that reaches each Every family `snappy-faces` publishes gets a cell, and its green is A READ THAT NAMES IT — a hand the runner's own `familyForHand` routes there, holding a verb that folds onto a shape. A family with no read draws for nobody, and that is counted, because a library that grows without reads grows the picture and not the product. | family | the read that names it | | |---|---|---| | `agent` | `snappy-agent-host status → agent-run` | reached | | `blocks` | **none** | no hand named snappy-blocks exists and no hand's contract declares a "blocks" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "blocks" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red | | `calendar` | `snappy-calendar list → list` | reached | | `code` | **none** | no hand named snappy-code exists and no hand's contract declares a "code" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "code" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red | | `data` | `snappy-database query → data-table` | reached | | `deploy` | `snappy-deploy status → deploy-receipt` | reached | | `doc` | `snappy-docs page → doc-card` | reached | | `freshbooks` | `snappy-freshbooks list → list` | reached | | `github` | `snappy-github comments → list` | reached | | `gmail` | `snappy-gmail list → list` | reached | | `imageset` | `snappy-image logos → imageset-grid` | reached | | `imessage` | `snappy-imessage recent → list` | reached | | `instagram` | **none** | no hand named snappy-instagram exists and no hand's contract declares a "instagram" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "instagram" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red | | `knowledge` | `snappy-knowledge get → one` | reached | | `krisp` | `snappy-krisp fetch-action-items → krisp-actions` | reached | | `linkedin` | `snappy-linkedin profile → profile` | reached | | `media` | `snappy-ffmpeg probe → media-video` | reached | | `notion` | `snappy-notion me → profile` | reached | | `schedule` | `snappy-scheduling propose → schedule-pick` | reached | | `sheets` | **none** | no hand named snappy-sheets exists and no hand's contract declares a "sheets" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "sheets" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red | | `skool` | `snappy-skool me → profile` | reached | | `slack` | `snappy-slack messages → list` | reached | | `statechange` | `snappy-statechange posts → list` | reached | | `stripe` | **none** | no hand named snappy-stripe exists and no hand's contract declares a "stripe" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "stripe" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red | | `telegram` | `snappy-telegram read → one` | reached | | `thumbnails` | `snappy-thumbnails variants → thumbnails-set` | reached | | `transcripts` | `snappy-transcripts list → list` | reached | | `walkthrough` | `snappy-walkthrough steps → walkthrough-steps` | reached | | `whatsapp` | `snappy-whatsapp read → one` | reached | | `x` | **none** | no hand named snappy-x exists and no hand's contract declares a "x" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "x" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red | | `youtube` | `snappy-youtube comments → list` | reached | ### red clusters | # | cluster | cells | what has to change | |---:|---|---:|---| | 1 | `hosts-road-names-no-face` | 1 | 1 of the four asks never reach a named face on the runner itself | | 2 | `hosts-not-driven-chatgpt` | 4 | ChatGPT web app could not be driven at all | | 3 | `hosts-not-driven-claude` | 4 | Claude.ai web app could not be driven at all | | 4 | `hosts-not-driven-codex` | 4 | Codex CLI could not be driven at all | | 5 | `hosts-verb-folds-onto-no-shape` | 56 | 56 hand(s) have no verb the face road can even shape | | 6 | `hosts-no-face-family` | 18 | 18 platform hand(s) name no face family | | 7 | `hosts-family-has-no-read` | 6 | 6 published face family(ies) no read can reach | ### what only the owner can run - **ChatGPT web app** — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. ```bash agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt ``` - **Claude.ai web app** — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. ```bash agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude ``` - **Codex CLI** — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) ```bash open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file> ```
A tool is a contract between a deterministic system and a non-deterministic
reader. The reader's failure modes are the specification. This skill applies
21 sources and 14 local incidents to the shape an AI reads, calls, recovers
from, and shows.
bashnpx tsx ~/.claude/skills/snappy-tool-design/api.ts lint snappy-gmail
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all --summary
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example snappy-gmail list
npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader snappy-gmail
npx tsx ~/.claude/skills/snappy-tool-design/api.ts probe --via mini
npx tsx ~/.claude/skills/snappy-tool-design/api.ts render --host-dir <loop dir>
npx tsx ~/.claude/skills/snappy-tool-design/api.ts loop --via mini
npx tsx ~/.claude/skills/snappy-tool-design/api.ts hosts --token-file <600-mode token file>
lint never executes a target verb. It has three evidence modes:
| Mode | Evidence | Result when absent |
|---|---|---|
static |
HAND_CONTRACT, implementation, SKILL.md, AGENTS.md, eval manifest |
FAIL when the required artifact is absent |
probe |
tool-design-probe.json, from a fresh real client |
DEFER, never a fabricated PASS |
render |
tool-design-render.json, from a real host loop |
DEFER, never renderable: true as proof |
The two sources marked [snippet], S19 and S20, remain paraphrased and
unquoted until their primary text and result tables are verified.
| ID | Source |
|---|---|
| S1 | Anthropic, Writing effective tools for AI agents |
| S2 | Anthropic, Building effective agents |
| S3 | Anthropic, Code execution with MCP |
| S4 | Anthropic, Equipping agents with Agent Skills |
| S5 | Claude Agent Skills overview |
| S6 | Claude Skill authoring best practices |
| S7 | Open Agent Skills specification |
| S8 | MCP Tools specification |
| S9 | MCP ToolAnnotations schema |
| S10 | MCP Apps extension |
| S11 | OpenAI function calling |
| S12 | OpenAI Apps SDK tool planning |
| S13 | OpenAI Apps SDK design guidelines |
| S14 | SWE-agent ACI paper |
| S15 | Bits-over-Random tool retrieval paper |
| S16 | Cloudflare Code Mode |
| S17 | Arcade, GitHub MCP launch lessons |
| S18 | OWASP MCP03 Tool Poisoning and Simon Willison's incident analysis |
S19 [snippet] |
Practitioner error-handling course page |
S20 [snippet] |
Self-Reflective APIs paper landing artifact |
| S21 | Claude Code's installed tool descriptions in the session system prompt |
| S22 | OpenAPI Specification 3.1 and openai/openai-openapi |
| S23 | Google API Discovery Service (kind: discovery#restDescription) |
Local sources are CLAUDE.md, owner rulings, and incidents I1–I13 in
RESEARCH.md. The open Agent Skills spec is the interoperability floor.
Snappy's executable contract is the ceiling.
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 1 | Name is 1–64 lowercase/digit/hyphen characters, has no edge/double hyphen, and equals the directory | S7 | Parse frontmatter and compare to basename |
| 2 | Verb identity is namespaced by hand; equal raw verbs with different grammars remain isolated | S1 | Build a collection verb/grammar index |
| 3 | Ban vague names or tails: helper, utils, tools, data, files, misc | S6 | Wordlist match |
| 4 | Near-neighbour descriptions name each other and say when to use the other | S12 | Description token-overlap clustering plus cross-reference check |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 5 | Description is non-empty, ≤1024 chars, tag-free, third-person, and says what plus when | S5, S6, S7, S21 | Frontmatter length, regex, and use-marker checks |
| 6 | Description is authored once in HAND_CONTRACT.description and projected exactly |
CLAUDE.md R4, S18 | Normalize and diff frontmatter against contract |
| 7 | Every promised verb is declared and every declared verb appears in AGENTS.md | CLAUDE.md R4 | Set-diff concrete calls and verb phrases against contract |
| 8 | SKILL.md is ≤500 lines and <5k tokens; references are one level deep and long references have a ToC | S5, S6, S7 | Count and walk local markdown links |
| 9 | Time-sensitive instructions live only under ## Old patterns |
S6 | Date plus stale-instruction marker scan |
| 10 | One term names each concept | S6 | Domain synonym-set scan |
| 11 | One default with an escape hatch, not three equivalent choices | S6 | Flag three-way alternative menus |
| 12 | Description contains no imperative absent from the contract | S18 | Compare imperative verbs to declared verbs |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 13 | Every argument has a description and a specific name | S1, S11 | Walk inputSchema.properties; flag bare id/user/data/input |
| 14 | Enumerable domains are enums, not prose lists | S2, S11 | Detect enumerating descriptions without enum |
| 15 | Near-miss keys refuse by naming the correct contract key | S6, S19 [snippet], S20 [snippet] |
Validate recorded synonym-key fuzz cases |
| 16 | Do not require values the runner already knows | S11 | Denylist chat_id/account/cwd/revision required args |
| 17 | Every collection read declares limit with a default and maximum |
S1 | Inspect read input schemas |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 18 | Every verb declares read, additive-write, destructive, spend, or send-to-a-person | S12, CLAUDE.md R6 | Require class per verb |
| 19 | MCP annotations are derived from class, not guessed from hostile defaults | S9 | Diff readOnly/destructive/idempotent/openWorld hints |
| 20 | Read and write are separate verbs, never a mode flag | S12 | Flag apply/dry_run/commit/send/now mode arguments |
| 21 | Annotation hints never enforce stage policy | S8, S9 | Detect implementation branches on annotation hints |
| 22 | Unmet requirements refuse in <50 ms with zero child processes | S14, I8 | Validate timed preflight probes |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 23 | Runner enforces a 25k-token hard cap and a measured soft default | S1 | Measure recorded answer sizes |
| 24 | Answer size grows monotonically and boundedly with requested limit | S1, I2, I13 | Compare limit 1/5/25 probe series |
| 25 | Over-cap answers summarize and return cursor/call_id | S14 | Validate truncated probe envelope |
| 26 | Truncation names the cursor, filter, limit, or narrower verb | S1 | Inspect truncation guidance |
| 27 | Default density uses human names; opaque ids are for detailed chaining | S1 | Scan recorded fields for orphan opaque IDs |
| 28 | Structured answers validate against outputSchema and retain serialized text | S8 | Validate structuredContent plus content |
| 29 | Facts carry source timestamps and stale values are labelled | CLAUDE.md R10, I9 | Check as_of age against stale label |
| 30 | Third-party text is structurally marked as data, never instructions | S18 | Require evidence-envelope semantics for credentialed reads |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 31 | Refusal envelope has outcome, code, message, and fix | S8, S19 [snippet], S20 [snippet] |
Schema-check refusal corpus |
| 32 | Refusal names a valid key, enum member, or missing env key | S1, S6 | Match refusal text against contract literals |
| 33 | Refusal codes form one closed table and each row has coverage | S1, S19 [snippet] |
Inspect HAND_CONTRACT.refusals and test/reference sites |
| 34 | Refusals name credentials by key only and never leak values | CLAUDE.md | Scan for token-shaped values |
| 35 | requires exactly matches executable credential reads |
S6, CLAUDE.md R4, I6 | Follow local imports; compare required env reads and deny host flags |
| 36 | env(K, false) never makes K required |
I6 measured | Compare optional reads to requires |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 37 | Published MCP catalog stays ≤10 tools; scale through search | S11, S15, S16, S17 | Count a fresh tools/list result |
| 38 | Design for callers enabling everything; tools/list stays in budget | S17 | Measure tools/list tokens |
| 39 | Verbs consolidate intent instead of mirroring vendor endpoints | S1, S17 | Compare verb count to literal endpoint surface |
| 40 | Metadata ≈100 tokens, body <5k, resources on demand | S3, S4, S5, S7 | Count each disclosure level |
| 41 | Search ranks every hand top-three for its name and job | S15, S16 | Validate recorded rank corpus |
| 42 | Readless hands explicitly declare instruction-only or write-only kind | S12, I7 | Compare read verbs to kind/frontmatter |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 43 | A read is one call, not a job: one request and <3 s p50 | S14, owner 18:4x, I1 | Validate timed read probes and request counts |
| 44 | Each verb publishes and meets an expected latency band | S1 | Diff contract band against probe timing |
| 45 | Long work is a separately named job verb; reads declare execution: call |
S2, I1 | Inspect execution class and queue call sites |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 46 | Return a face only when visual interaction improves the workflow | S10, S13 | Probe: no face over a <200 B answer, and no hint that names no kind |
| 47 | UI-bearing results retain meaningful standalone text | S10 | Check non-empty content fallback |
| 48 | UI binds a preregistered ui:// resource with exact MCP Apps MIME |
S10 | Validate resource URI and mimeType |
| 49 | Channel decides artifact: picture→PNG, MCP→OpenUI program, plain→text | S10, owner 20:5x, I5 | Check rendition declaration |
| 50 | Faces shrink and grow without nested scrolling | S13, I11 | Two-size real-host render probe |
| 51 | Cards have ≤2 primary actions; carousels have 3–8 items | S13 | Count real composed program nodes |
| 52 | View listens before host delivery | I11 measured | about:blank then src handshake loop |
| 53 | Every face family has a dated screenshot from the real bundle and host | CLAUDE.md R10, I10 | Check render registry artifact |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 54 | At least three eval scenarios exist before ready/complete status | S6 | Read evals.json |
| 55 | Eval scenarios are multi-call and carry verifiable ground truth | S1 | Count calls and require ground_truth |
| 56 | Probe run records accuracy, runtime, tool-call count, tokens, and errors | S1 | Validate metric manifest |
| 57 | A disjoint holdout exists | S1 | Compare scenario ids by split |
| 58 | Proof comes from a fresh client whose handshake postdates deploy | CLAUDE.md R7, I10 | Validate client/deploy evidence |
The MCP publishes one skill as all three primitives: the loader and manual
become the prompt, HAND_CONTRACT.verbs become the tools, and the
skill's files plus the contract itself become the resources (S3, S4, S8).
The prompt is therefore where the model's FIRST call comes from, and a first
call that refuses is a defect of the projection, not of the caller.
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 59 | No optional positional stands in front of a commonly-supplied argument (limit, count, n, top, max, size, per-page, first, take) |
S11, S1, I14 measured | Scan verbs.<verb>.args for an optional word before the count |
| 60 | The loader carries the generated contract block, byte-identical to what the contract renders today | S3, S4, S8, CLAUDE.md R4 | Re-render snappy-skill.renderLoaderVerbTable() and compare |
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 61 | The vendor's spec still has what this hand offers, and this hand offers what the vendor added | S22, S23, owner 16:5x | Diff HAND_CONTRACT.spec.operations and the hand's hard-coded enum literals against the committed spec corpus |
Rule 61 is the first rule in this table that compares the collection to the
WORLD rather than to itself. R7 asks whether a hand has the verb it promises;
R60 asks whether the loader is current with the contract. Both are internal.
The world is the only party in this system that changes without a commit here,
and until 2026-09-09 nothing noticed: OpenAI had shipped
gpt-image-2.5-sunburst and gpt-image-2.5-flare, snappy-image still taught
gpt-image-1, and every rule was green.
Its evidence mode is spec: the committed corpus
tool-design-specs.json, written by api.ts specs (which
snappy-specwatch check calls — one road). No corpus, or no spec on the
hand, is a DEFER, never a PASS: a rule that vouched for a hand nobody had
checked is a status truer than its artifact.
api.ts openapi <skill> is the inverse of the same idea — a hand's own
HAND_CONTRACT emitted as an OpenAPI 3.1 document, so a skill can reach
ChatGPT Actions, Codex or curl without our MCP in the middle
(snappy-settings/contract-openapi.ts is the fold).
example prints the safe first call on stdout and nothing else, so it
pipes. When the verb's grammar trips rule 59 it adds one line on stderr
naming where a bare count would land and the call that works:
`rule 59: recent(contact?, limit?) — a bare limit lands in contact?. Supply the
earlier words: recent "" <limit>`.
Rule 59 is the one an AI feels first. A person says "the last 5 messages"; the
model writes the count and nothing else. Measured 2026-09-08: `snappy-imessage
recent(contact?, limit?), snappy-krisp fetch-meetings(after-date?, limit?)`
and snappy-thumbnails audit(channel?, limit?) each taught recent 5 — and
5 landed in the FIRST slot, so the read answered about a contact named "5".
The fix is contract-shaped, not prose-shaped: put the count first, or give it
a flag. Rule 60 is why fix-loader exists: the block is the prompt half of
the projection, and a stale block ships a stale first call to every reader.
A complete verb can express the host-facing facts without prose inference:
typescript{
args: ["limit?"],
effect: "read",
class: "read",
execution: "call",
openWorld: true,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
},
inputSchema: {
properties: {
limit: { type: "integer", description: "Maximum rows", default: 5, maximum: 100 }
}
}
}
MCP annotations are advertisements, not enforcement. Snappy deliberately uses
a narrower human gate than MCP's general recommendation: sends, spends,
posts, deletes, and irreversible work stage; reads and reversible drafts run.
loop runs probe → render → lint --all, clusters every FAIL by
mechanism, ranks clusters by how many defects one fix kills, writes a lane
brief each plus a ranked LEDGER.md, and is re-run after each landing.
A list of failures is a list. A list of mechanisms compounds: one mechanism
repaired kills every defect that shares it — rule 59 is the worked example,
six skills, one grammar mistake, one lane. Launch lanes from the ledger's top
row and re-run. The bearer is read from a 600-mode file, never an argument.
Every skill card in the menu-bar app carries a field: *"What should Calendar do
better?"* and a Note it button. That line is a lane, and this is the whole
road it travels:
| where | what it does |
|---|---|
apps/snappy-bar (runner repo) |
posts {skill, words, verb?, face_kind?} to the runner |
POST /fix-request (src/http.ts) |
bounds the words at 2 000 characters, refuses by name over it, stamps the clock and the collection revision |
fileFixRequest (src/store.ts) |
the ONE writer — appends one JSON line |
~/.snappy-skills/fix-requests.jsonl |
the file both ends agree on |
readFixRequests / fetchFixRequests (loop.ts) |
one body's file, by path or through its door |
readAsks (loop.ts) |
the ONE reader — visits the bodies --fix-requests-from names |
LEDGER.md + briefs/person-<skill>.md |
his ask, ranked above every rule |
THE PATH IS A CONVENTION, STATED IN BOTH PLACES. This repo is the runner's
bundle/ submodule and cannot import from the superproject, so there is no
constant to share — only an agreement, and an agreement that is written down in
exactly one file is an agreement the other side breaks silently. It is:
${SNAPPY_SKILLS_HOME:-~/.snappy-skills}/fix-requests.jsonl
resolved the same way on both sides (loop.ts fixRequestsPath(), and the
SKILLS_HOME block at the top of the runner's src/store.ts, which states the
same sentence about this file). The line is
{at, skill, words, verb, face_kind, revision}; verb and face_kind are
null when the card was not showing one, never "".
READING THE OTHER MAC'S FILE. He types on whichever Mac the bar is running
on, and the loop is usually run somewhere else. --fix-requests-from mini reads
the mini's own GET /fix-requests?since= — the SAME road probe --via mini
already uses (MINI_MCP_URL with /mcp swapped for /fix-requests), behind
the SAME SNAPPY_RUNNER_TOKEN_FILE bearer. There is no second credential and no
ssh.
**--fix-requests-from both IS THE ROAD BETWEEN THE TWO MACS, AND IT IS THE
ONLY ONE.** Nothing syncs the file ⟨measured 2026-09-09, lane menu-bar-13: the
runner's update pulls git and this is runtime state⟩, and copying it would
make a second writer of a directory the runner's store.ts owns — the duplicate
road CLAUDE.md §4 bans by name. So the FILE never travels; the READER visits
both bodies, each through the door that body already answers, and folds what
comes back on at+skill+words (the same file read twice — once by path,
once through its own door, which is what both run ON the mini does — is one
sentence, not two).
EACH BODY IS READ ON ITS OWN. readAsks is the one reader and it catches
per body, because a shared catch — which is what it replaced — let an
unreachable mini throw away every note typed on THIS Mac and print a ledger
ranking nothing, over a file holding his words. A body that fails is named out
loud with its own line: `no ask from <body> is ranked below — that is an
absence, not a zero`.
MEASURED 2026-09-09 12:4x: THE WHOLE ROAD HAS NOW FIRED, ON THE REAL APP.
Note it was pressed on the Calendar card in the running bar (proof
apps/snappy-bar/proof/43-note-it-on-the-real-app-files-the-line.png), and
~/.snappy-skills/fix-requests.jsonl on the central Mac holds the line —
`{"at":…,"skill":"snappy-calendar","words":"the week view should start on
Monday","verb":null,"face_kind":null,"revision":"8db40c4f86-…"}` — which this
loop ranked as row 1 of LEDGER.md, above a rule that kills 95 defects.
Two things stay true from the earlier measurement: verb and face_kind are
null because the PAGE does not send them (bar-host.mjs posts only
{skill, words} while the runner's door and Swift both accept the other two),
and the mini's own file still does not exist. --fix-requests-from both run
from the central Mac reports the mini as HTTP 401: the mini's runner holds a
different bearer, so a loop that wants both bodies must be given the MINI's
token file, not this Mac's.
HIS WORDS OUTRANK EVERY RULE. Person clusters are placed at the top of the
ledger by RANK, not by score: sorted against defect counts, a note about one
hand would fall below any rule that happened to fire on four, which is the
ledger quietly deciding a linter outranks him. The brief quotes him verbatim and
asks for a change on his glass — there is no FAIL count to fall.
HIS WORDS NEVER REACH GIT. briefs/person-*.md and this skill's LEDGER.md
are gitignored: they are his sentences about his own mail, calendar and money.
The lint briefs beside them are generated from rules and stay tracked.
hosts — the same four asks, every host, counted#probe measures the wire; render measures ONE host this repo wrote itself.
Neither can see a face that draws in our own AppBridge host and never appears
in ChatGPT. hosts is that third measurement — a verb on this same loop,
writing into this same ledger, never a second loop. It colours each cell
mounted / text / not-driven, keeps every undrivable host IN the
denominator with its reason and the owner's one line, and prints
hosts: <green>/<total> cells. Mechanics and anti-fixes:
Both are evidence, not configuration — ONE file each for the collection,
keyed by skill. corpus.ts is their only reader and declares both schemas.
tool-design-hosts.json is the third, and hosts.ts declares and reads it.
The probe calls READ verbs only; a send, post, write or delete is never called,
not even to fuzz a near-miss key — those land in near_miss_skipped. The
render corpus reduces per-kind measurements at 360 px and 720 px to the four
booleans rules 50–53 read, per face FAMILY, the unit rule 53 names.
TWO WITNESSES DECIDE WHETHER A VERB IS CALLED, because effect is the
SERVER'S CLAIM about a verb and a verb that reaches a person while declaring
itself a read is the exact failure this repository names — a status truer than
its artifact, where the artifact is somebody's phone buzzing. SAFE_EFFECTS
reads the declared effect; NEVER_CALLED reads the verb's own name for the
words that actually reach a person or spend (send, post, reply, dm,
publish, pay, charge, delete). A disagreement is resolved by NOT
CALLING, and the skip is written into the corpus as safety_skipped — not only
into a log nobody keeps, because a measurement that is silently absent reads as
a measurement that found nothing. Measured 2026-09-09: one cell, `snappy-skool
post-detail, which is a read whose name carries post` as a noun. It costs one
read sample and it fails in the safe direction.
| Incident | Failure |
|---|---|
| I1 | LinkedIn read took 10.5 s and 11 polls, later 40.8 s |
| I2 | limit 5 returned 63 KB |
| I3 | body vs text refused in 24 ms and named the grammar |
| I4 | Telegram prose promised voice before the contract did |
| I5 | Gmail card needed a PNG for Telegram and took four calls |
| I6 | Host flag and optional-env inference falsely required credentials |
| I7 | Readless hands looked broken until their kind was explicit |
| I8 | Preflight moved refusal from 2,376 ms to 0.37 ms |
| I9 | FreshBooks status was 1,843 minutes stale |
| I10 | Stored OpenUI existed while presentation said not renderable |
| I11 | Real-host driving found handshake, collapse, and shrink defects |
| I12 | Gmail duplicated a retired road already owned by email |
| I13 | Sales nested page fell from 127 KB to 3 KB after bounding |
| I14 | Three loaders taught a refusing first call: an optional positional sat before limit |
response_format arguments.skill-check.sh runs this lint LAST and can never turn the gate red: it
resolves $SKILLS_DIR/snappy-tool-design/api.ts, skips in silence when that is
absent, prints tool-design lint (TD1 advisory)..., runs lint --all --summary
(or lint <skill> --summary for a single target), and swallows every failure
into one non-blocking warning line. It adds nothing to FAIL, to
FAILED_SKILLS, or to the exit code. Advisory on purpose: the collection's
rule-6 and rule-60 debt is ~97 skills wide, and a gate that goes red on day one
is a gate somebody switches off.
skill-check grades $SKILLS_DIR, and ~/.claude/skills/snappy-* are symlinks
into the MAIN checkout — to grade a worktree, pass
SNAPPY_SKILLS_DIR=$PWD/skills. This skill's own lint needs no flag: it
resolves its root as the parent of its own directory.
The rung's exact steps, and the dated per-rule census of the 98-skill
collection, are in skill-check-rung.md.
fix-loader delegates to snappy-skill.syncLoaderVerbTable(). That function
calls scaffold() and owns the only marked-block writer. Re-running an
unchanged contract writes nothing.
| Skill | Relationship |
|---|---|
snappy-skill |
Owns scaffold and generated-loader writes |
snappy-settings |
Runs this lint as a warning rung |
snappy-artifact-loop |
Manages implementation feedback, not tool-surface ergonomics |
snappy-faces |
Owns face selection and channel-fit presentation |
snappy-hands |
Derives and serves executable hand contracts |
snappy-agent-host |
Runs agent CLIs over ACP and prints their steps; this skill grades the tool surface those agents read, and never launches one |
Skill Status: COMPLETE. Artifact: the API evaluates all 60 sourced rules;
probe and render produce the evidence the 26 measured rules grade from, and
loop turns what failed into ranked lane briefs.
Measured 2026-09-09 12:2x, one full loop --via mini over the 98-skill
collection (78 hands answered info on the runner), against the 02:19 run of
the same night:
| 09-09 02:19 | 09-09 12:25 | |
|---|---|---|
| collection PASS | 4 022 | 4 085 |
| collection FAIL | 636 | 633 |
| collection DEFER | 1 222 | 1 162 |
| probe accuracy | 0.7005 | 0.7186 |
The 60 gradings that left DEFER left it because the fresh probe answered them:
R15 +16 PASS, R24 +14 PASS, R47/R48/R49 +10 PASS each, R43 +3 PASS. This
skill's own line is 48 pass, 5 fail, 7 defer — the seven are R22, R25, R26,
R44, R47, R48 and R49, each one a measurement no run has taken yet rather than
a rule it fails. The earlier claim on this line, "the skill's own DEFER count
fell from 26 to 0", was measured before those rules had a census and is
replaced rather than appended to.
---
name: snappy-tool-design
instruction-only: false
reports_to: tool
head: false
description: "Contract-first ergonomics lint for AI-operated skills, unlike snappy-artifact-loop which manages implementation feedback. Use when auditing tool descriptions, verbs, arguments, effects, refusals, response bounds, progressive disclosure, latency, faces, the derived first call, or eval evidence against the sourced 61-rule research table. Triggers on: snappy-tool-design, tool design, lint a skill, tool ergonomics, verb table, derived first call, AGENTS.md drift, refusal shape, response bounds."
---
# snappy-tool-design
A tool is a contract between a deterministic system and a non-deterministic
reader. The reader's failure modes are the specification. This skill applies
21 sources and 14 local incidents to the shape an AI reads, calls, recovers
from, and shows.
## Use it
```bash
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint snappy-gmail
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all
npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint --all --summary
npx tsx ~/.claude/skills/snappy-tool-design/api.ts example snappy-gmail list
npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader snappy-gmail
npx tsx ~/.claude/skills/snappy-tool-design/api.ts probe --via mini
npx tsx ~/.claude/skills/snappy-tool-design/api.ts render --host-dir <loop dir>
npx tsx ~/.claude/skills/snappy-tool-design/api.ts loop --via mini
npx tsx ~/.claude/skills/snappy-tool-design/api.ts hosts --token-file <600-mode token file>
```
`lint` never executes a target verb. It has three evidence modes:
| Mode | Evidence | Result when absent |
|---|---|---|
| `static` | `HAND_CONTRACT`, implementation, SKILL.md, AGENTS.md, eval manifest | FAIL when the required artifact is absent |
| `probe` | `tool-design-probe.json`, from a fresh real client | DEFER, never a fabricated PASS |
| `render` | `tool-design-render.json`, from a real host loop | DEFER, never `renderable: true` as proof |
The two sources marked `[snippet]`, S19 and S20, remain paraphrased and
unquoted until their primary text and result tables are verified.
## Sources
| ID | Source |
|---|---|
| S1 | [Anthropic, Writing effective tools for AI agents](https://www.anthropic.com/engineering/writing-tools-for-agents) |
| S2 | [Anthropic, Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) |
| S3 | [Anthropic, Code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp) |
| S4 | [Anthropic, Equipping agents with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) |
| S5 | [Claude Agent Skills overview](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) |
| S6 | [Claude Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) |
| S7 | [Open Agent Skills specification](https://agentskills.io/specification) |
| S8 | [MCP Tools specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) |
| S9 | [MCP ToolAnnotations schema](https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/2025-11-25/schema.ts) |
| S10 | [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx) |
| S11 | [OpenAI function calling](https://developers.openai.com/api/docs/guides/function-calling) |
| S12 | [OpenAI Apps SDK tool planning](https://developers.openai.com/apps-sdk/plan/tools) |
| S13 | [OpenAI Apps SDK design guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines) |
| S14 | [SWE-agent ACI paper](https://arxiv.org/html/2405.15793v2) |
| S15 | [Bits-over-Random tool retrieval paper](https://arxiv.org/html/2605.24660v1) |
| S16 | [Cloudflare Code Mode](https://blog.cloudflare.com/code-mode-mcp/) |
| S17 | [Arcade, GitHub MCP launch lessons](https://www.arcade.dev/blog/enterprise-mcp-lessons-from-githubs-mcp-server-launch/) |
| S18 | [OWASP MCP03 Tool Poisoning](https://owasp.org/www-project-mcp-top-10/2025/MCP03-2025%E2%80%93Tool-Poisoning) and [Simon Willison's incident analysis](https://simonwillison.net/2025/Apr/9/mcp-prompt-injection/) |
| S19 `[snippet]` | [Practitioner error-handling course page](https://apxml.com/courses/building-advanced-llm-agent-tools/chapter-1-llm-agent-tooling-foundations/tool-error-handling) |
| S20 `[snippet]` | [Self-Reflective APIs paper landing artifact](https://arxiv.org/pdf/2606.05037) |
| S21 | Claude Code's installed tool descriptions in the session system prompt |
| S22 | [OpenAPI Specification 3.1](https://spec.openapis.org/oas/latest.html) and [openai/openai-openapi](https://github.com/openai/openai-openapi) |
| S23 | [Google API Discovery Service](https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest) (`kind: discovery#restDescription`) |
Local sources are `CLAUDE.md`, owner rulings, and incidents I1–I13 in
`RESEARCH.md`. The open Agent Skills spec is the interoperability floor.
Snappy's executable contract is the ceiling.
## The 61 rules
### B1. Naming and identity
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 1 | Name is 1–64 lowercase/digit/hyphen characters, has no edge/double hyphen, and equals the directory | S7 | Parse frontmatter and compare to basename |
| 2 | Verb identity is namespaced by hand; equal raw verbs with different grammars remain isolated | S1 | Build a collection verb/grammar index |
| 3 | Ban vague names or tails: helper, utils, tools, data, files, misc | S6 | Wordlist match |
| 4 | Near-neighbour descriptions name each other and say when to use the other | S12 | Description token-overlap clustering plus cross-reference check |
### B2. Descriptions and progressive loading
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 5 | Description is non-empty, ≤1024 chars, tag-free, third-person, and says what plus when | S5, S6, S7, S21 | Frontmatter length, regex, and use-marker checks |
| 6 | Description is authored once in `HAND_CONTRACT.description` and projected exactly | CLAUDE.md R4, S18 | Normalize and diff frontmatter against contract |
| 7 | Every promised verb is declared and every declared verb appears in AGENTS.md | CLAUDE.md R4 | Set-diff concrete calls and verb phrases against contract |
| 8 | SKILL.md is ≤500 lines and <5k tokens; references are one level deep and long references have a ToC | S5, S6, S7 | Count and walk local markdown links |
| 9 | Time-sensitive instructions live only under `## Old patterns` | S6 | Date plus stale-instruction marker scan |
| 10 | One term names each concept | S6 | Domain synonym-set scan |
| 11 | One default with an escape hatch, not three equivalent choices | S6 | Flag three-way alternative menus |
| 12 | Description contains no imperative absent from the contract | S18 | Compare imperative verbs to declared verbs |
### B3. Arguments
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 13 | Every argument has a description and a specific name | S1, S11 | Walk `inputSchema.properties`; flag bare id/user/data/input |
| 14 | Enumerable domains are enums, not prose lists | S2, S11 | Detect enumerating descriptions without `enum` |
| 15 | Near-miss keys refuse by naming the correct contract key | S6, S19 `[snippet]`, S20 `[snippet]` | Validate recorded synonym-key fuzz cases |
| 16 | Do not require values the runner already knows | S11 | Denylist chat_id/account/cwd/revision required args |
| 17 | Every collection read declares `limit` with a default and maximum | S1 | Inspect read input schemas |
### B4. Effect classes and annotations
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 18 | Every verb declares read, additive-write, destructive, spend, or send-to-a-person | S12, CLAUDE.md R6 | Require `class` per verb |
| 19 | MCP annotations are derived from class, not guessed from hostile defaults | S9 | Diff readOnly/destructive/idempotent/openWorld hints |
| 20 | Read and write are separate verbs, never a mode flag | S12 | Flag apply/dry_run/commit/send/now mode arguments |
| 21 | Annotation hints never enforce stage policy | S8, S9 | Detect implementation branches on annotation hints |
| 22 | Unmet requirements refuse in <50 ms with zero child processes | S14, I8 | Validate timed preflight probes |
### B5. Response shape and size
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 23 | Runner enforces a 25k-token hard cap and a measured soft default | S1 | Measure recorded answer sizes |
| 24 | Answer size grows monotonically and boundedly with requested limit | S1, I2, I13 | Compare limit 1/5/25 probe series |
| 25 | Over-cap answers summarize and return cursor/call_id | S14 | Validate truncated probe envelope |
| 26 | Truncation names the cursor, filter, limit, or narrower verb | S1 | Inspect truncation guidance |
| 27 | Default density uses human names; opaque ids are for detailed chaining | S1 | Scan recorded fields for orphan opaque IDs |
| 28 | Structured answers validate against outputSchema and retain serialized text | S8 | Validate structuredContent plus content |
| 29 | Facts carry source timestamps and stale values are labelled | CLAUDE.md R10, I9 | Check as_of age against stale label |
| 30 | Third-party text is structurally marked as data, never instructions | S18 | Require evidence-envelope semantics for credentialed reads |
### B6. Refusals
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 31 | Refusal envelope has outcome, code, message, and fix | S8, S19 `[snippet]`, S20 `[snippet]` | Schema-check refusal corpus |
| 32 | Refusal names a valid key, enum member, or missing env key | S1, S6 | Match refusal text against contract literals |
| 33 | Refusal codes form one closed table and each row has coverage | S1, S19 `[snippet]` | Inspect `HAND_CONTRACT.refusals` and test/reference sites |
| 34 | Refusals name credentials by key only and never leak values | CLAUDE.md | Scan for token-shaped values |
| 35 | `requires` exactly matches executable credential reads | S6, CLAUDE.md R4, I6 | Follow local imports; compare required env reads and deny host flags |
| 36 | `env(K, false)` never makes K required | I6 measured | Compare optional reads to `requires` |
### B7. Tool count and disclosure
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 37 | Published MCP catalog stays ≤10 tools; scale through search | S11, S15, S16, S17 | Count a fresh tools/list result |
| 38 | Design for callers enabling everything; tools/list stays in budget | S17 | Measure tools/list tokens |
| 39 | Verbs consolidate intent instead of mirroring vendor endpoints | S1, S17 | Compare verb count to literal endpoint surface |
| 40 | Metadata ≈100 tokens, body <5k, resources on demand | S3, S4, S5, S7 | Count each disclosure level |
| 41 | Search ranks every hand top-three for its name and job | S15, S16 | Validate recorded rank corpus |
| 42 | Readless hands explicitly declare instruction-only or write-only kind | S12, I7 | Compare read verbs to kind/frontmatter |
### B8. Latency and shape-fit
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 43 | A read is one call, not a job: one request and <3 s p50 | S14, owner 18:4x, I1 | Validate timed read probes and request counts |
| 44 | Each verb publishes and meets an expected latency band | S1 | Diff contract band against probe timing |
| 45 | Long work is a separately named job verb; reads declare `execution: call` | S2, I1 | Inspect execution class and queue call sites |
### B9. UI and channel artifacts
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 46 | Return a face only when visual interaction improves the workflow | S10, S13 | Probe: no face over a <200 B answer, and no hint that names no kind |
| 47 | UI-bearing results retain meaningful standalone text | S10 | Check non-empty content fallback |
| 48 | UI binds a preregistered `ui://` resource with exact MCP Apps MIME | S10 | Validate resource URI and mimeType |
| 49 | Channel decides artifact: picture→PNG, MCP→OpenUI program, plain→text | S10, owner 20:5x, I5 | Check rendition declaration |
| 50 | Faces shrink and grow without nested scrolling | S13, I11 | Two-size real-host render probe |
| 51 | Cards have ≤2 primary actions; carousels have 3–8 items | S13 | Count real composed program nodes |
| 52 | View listens before host delivery | I11 measured | about:blank then src handshake loop |
| 53 | Every face family has a dated screenshot from the real bundle and host | CLAUDE.md R10, I10 | Check render registry artifact |
### B10. Evals
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 54 | At least three eval scenarios exist before ready/complete status | S6 | Read `evals.json` |
| 55 | Eval scenarios are multi-call and carry verifiable ground truth | S1 | Count calls and require ground_truth |
| 56 | Probe run records accuracy, runtime, tool-call count, tokens, and errors | S1 | Validate metric manifest |
| 57 | A disjoint holdout exists | S1 | Compare scenario ids by split |
| 58 | Proof comes from a fresh client whose handshake postdates deploy | CLAUDE.md R7, I10 | Validate client/deploy evidence |
### B11. MCP projection and the derived first call
The MCP publishes one skill as all three primitives: the loader and manual
become the **prompt**, `HAND_CONTRACT.verbs` become the **tools**, and the
skill's files plus the contract itself become the **resources** (S3, S4, S8).
The prompt is therefore where the model's FIRST call comes from, and a first
call that refuses is a defect of the projection, not of the caller.
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 59 | No optional positional stands in front of a commonly-supplied argument (`limit`, `count`, `n`, `top`, `max`, `size`, `per-page`, `first`, `take`) | S11, S1, I14 measured | Scan `verbs.<verb>.args` for an optional word before the count |
| 60 | The loader carries the generated contract block, byte-identical to what the contract renders today | S3, S4, S8, CLAUDE.md R4 | Re-render `snappy-skill.renderLoaderVerbTable()` and compare |
### B12. The vendor's own spec
| # | Rule | Source | Mechanical lint |
|---|---|---|---|
| 61 | The vendor's spec still has what this hand offers, and this hand offers what the vendor added | S22, S23, owner 16:5x | Diff `HAND_CONTRACT.spec.operations` and the hand's hard-coded enum literals against the committed spec corpus |
Rule 61 is the first rule in this table that compares the collection to the
WORLD rather than to itself. R7 asks whether a hand has the verb it promises;
R60 asks whether the loader is current with the contract. Both are internal.
The world is the only party in this system that changes without a commit here,
and until 2026-09-09 nothing noticed: OpenAI had shipped
`gpt-image-2.5-sunburst` and `gpt-image-2.5-flare`, `snappy-image` still taught
`gpt-image-1`, and every rule was green.
Its evidence mode is `spec`: the committed corpus
`tool-design-specs.json`, written by `api.ts specs` (which
`snappy-specwatch check` calls — one road). No corpus, or no `spec` on the
hand, is a **DEFER**, never a PASS: a rule that vouched for a hand nobody had
checked is a status truer than its artifact.
`api.ts openapi <skill>` is the inverse of the same idea — a hand's own
`HAND_CONTRACT` emitted as an OpenAPI 3.1 document, so a skill can reach
ChatGPT Actions, Codex or curl without our MCP in the middle
(`snappy-settings/contract-openapi.ts` is the fold).
`example` prints the safe first call on **stdout** and nothing else, so it
pipes. When the verb's grammar trips rule 59 it adds one line on **stderr**
naming where a bare count would land and the call that works:
`rule 59: recent(contact?, limit?) — a bare limit lands in contact?. Supply the
earlier words: recent "" <limit>`.
Rule 59 is the one an AI feels first. A person says "the last 5 messages"; the
model writes the count and nothing else. Measured 2026-09-08: `snappy-imessage
recent(contact?, limit?)`, `snappy-krisp fetch-meetings(after-date?, limit?)`
and `snappy-thumbnails audit(channel?, limit?)` each taught `recent 5` — and
5 landed in the FIRST slot, so the read answered about a contact named "5".
The fix is contract-shaped, not prose-shaped: put the count first, or give it
a flag. Rule 60 is why `fix-loader` exists: the block is the prompt half of
the projection, and a stale block ships a stale first call to every reader.
## Contract fields introduced by the lint
A complete verb can express the host-facing facts without prose inference:
```typescript
{
args: ["limit?"],
effect: "read",
class: "read",
execution: "call",
openWorld: true,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
},
inputSchema: {
properties: {
limit: { type: "integer", description: "Maximum rows", default: 5, maximum: 100 }
}
}
}
```
MCP annotations are advertisements, not enforcement. Snappy deliberately uses
a narrower human gate than MCP's general recommendation: sends, spends,
posts, deletes, and irreversible work stage; reads and reversible drafts run.
## The closed loop, and why it compounds
`loop` runs `probe` → `render` → `lint --all`, clusters every FAIL by
**mechanism**, ranks clusters by how many defects one fix kills, writes a lane
brief each plus a ranked `LEDGER.md`, and is re-run after each landing.
A list of failures is a list. A list of mechanisms compounds: one mechanism
repaired kills every defect that shares it — rule 59 is the worked example,
six skills, one grammar mistake, one lane. Launch lanes from the ledger's top
row and re-run. The bearer is read from a 600-mode file, never an argument.
## THE PERSON'S FIX FIELD — the one constant this repo shares with the runner
Every skill card in the menu-bar app carries a field: *"What should Calendar do
better?"* and a **Note it** button. That line is a lane, and this is the whole
road it travels:
| where | what it does |
|---|---|
| `apps/snappy-bar` (runner repo) | posts `{skill, words, verb?, face_kind?}` to the runner |
| `POST /fix-request` (`src/http.ts`) | bounds the words at 2 000 characters, refuses **by name** over it, stamps the clock and the collection revision |
| `fileFixRequest` (`src/store.ts`) | the ONE writer — appends one JSON line |
| `~/.snappy-skills/fix-requests.jsonl` | the file both ends agree on |
| `readFixRequests` / `fetchFixRequests` (`loop.ts`) | one body's file, by path or through its door |
| `readAsks` (`loop.ts`) | the ONE reader — visits the bodies `--fix-requests-from` names |
| `LEDGER.md` + `briefs/person-<skill>.md` | his ask, ranked above every rule |
**THE PATH IS A CONVENTION, STATED IN BOTH PLACES.** This repo is the runner's
`bundle/` submodule and cannot import from the superproject, so there is no
constant to share — only an agreement, and an agreement that is written down in
exactly one file is an agreement the other side breaks silently. It is:
```
${SNAPPY_SKILLS_HOME:-~/.snappy-skills}/fix-requests.jsonl
```
resolved the same way on both sides (`loop.ts fixRequestsPath()`, and the
`SKILLS_HOME` block at the top of the runner's `src/store.ts`, which states the
same sentence about this file). The line is
`{at, skill, words, verb, face_kind, revision}`; `verb` and `face_kind` are
`null` when the card was not showing one, never `""`.
**READING THE OTHER MAC'S FILE.** He types on whichever Mac the bar is running
on, and the loop is usually run somewhere else. `--fix-requests-from mini` reads
the mini's own `GET /fix-requests?since=` — the SAME road `probe --via mini`
already uses (`MINI_MCP_URL` with `/mcp` swapped for `/fix-requests`), behind
the SAME `SNAPPY_RUNNER_TOKEN_FILE` bearer. There is no second credential and no
ssh.
**`--fix-requests-from both` IS THE ROAD BETWEEN THE TWO MACS, AND IT IS THE
ONLY ONE.** Nothing syncs the file ⟨measured 2026-09-09, lane menu-bar-13: the
runner's `update` pulls git and this is runtime state⟩, and copying it would
make a second writer of a directory the runner's `store.ts` owns — the duplicate
road CLAUDE.md §4 bans by name. So the FILE never travels; the READER visits
both bodies, each through the door that body already answers, and folds what
comes back on `at`+`skill`+`words` (the same file read twice — once by path,
once through its own door, which is what `both` run ON the mini does — is one
sentence, not two).
**EACH BODY IS READ ON ITS OWN.** `readAsks` is the one reader and it catches
per body, because a shared catch — which is what it replaced — let an
unreachable mini throw away every note typed on THIS Mac and print a ledger
ranking nothing, over a file holding his words. A body that fails is named out
loud with its own line: `no ask from <body> is ranked below — that is an
absence, not a zero`.
**MEASURED 2026-09-09 12:4x: THE WHOLE ROAD HAS NOW FIRED, ON THE REAL APP.**
Note it was pressed on the Calendar card in the running bar (proof
`apps/snappy-bar/proof/43-note-it-on-the-real-app-files-the-line.png`), and
`~/.snappy-skills/fix-requests.jsonl` on the central Mac holds the line —
`{"at":…,"skill":"snappy-calendar","words":"the week view should start on
Monday","verb":null,"face_kind":null,"revision":"8db40c4f86-…"}` — which this
loop ranked as row **1** of `LEDGER.md`, above a rule that kills 95 defects.
Two things stay true from the earlier measurement: `verb` and `face_kind` are
`null` because the PAGE does not send them (`bar-host.mjs` posts only
`{skill, words}` while the runner's door and Swift both accept the other two),
and the mini's own file still does not exist. `--fix-requests-from both` run
from the central Mac reports the mini as `HTTP 401`: the mini's runner holds a
different bearer, so a loop that wants both bodies must be given the MINI's
token file, not this Mac's.
**HIS WORDS OUTRANK EVERY RULE.** Person clusters are placed at the top of the
ledger by RANK, not by score: sorted against defect counts, a note about one
hand would fall below any rule that happened to fire on four, which is the
ledger quietly deciding a linter outranks him. The brief quotes him verbatim and
asks for a change on his glass — there is no FAIL count to fall.
**HIS WORDS NEVER REACH GIT.** `briefs/person-*.md` and this skill's `LEDGER.md`
are gitignored: they are his sentences about his own mail, calendar and money.
The lint briefs beside them are generated from rules and stay tracked.
## `hosts` — the same four asks, every host, counted
`probe` measures the wire; `render` measures ONE host this repo wrote itself.
Neither can see a face that draws in our own AppBridge host and never appears
in ChatGPT. `hosts` is that third measurement — a verb on this same loop,
writing into this same ledger, never a second loop. It colours each cell
**mounted** / `text` / `not-driven`, keeps every undrivable host IN the
denominator with its reason and the owner's one line, and prints
`hosts: <green>/<total> cells`. Mechanics and anti-fixes:
[hosts-battery.md](hosts-battery.md).
## The two corpora
Both are evidence, not configuration — **ONE file each for the collection**,
keyed by skill. `corpus.ts` is their only reader and declares both schemas.
`tool-design-hosts.json` is the third, and `hosts.ts` declares and reads it.
The probe calls READ verbs only; a send, post, write or delete is never called,
not even to fuzz a near-miss key — those land in `near_miss_skipped`. The
render corpus reduces per-kind measurements at 360 px and 720 px to the four
booleans rules 50–53 read, **per face FAMILY**, the unit rule 53 names.
**TWO WITNESSES DECIDE WHETHER A VERB IS CALLED**, because `effect` is the
SERVER'S CLAIM about a verb and a verb that reaches a person while declaring
itself a read is the exact failure this repository names — a status truer than
its artifact, where the artifact is somebody's phone buzzing. `SAFE_EFFECTS`
reads the declared effect; `NEVER_CALLED` reads the verb's own name for the
words that actually reach a person or spend (`send`, `post`, `reply`, `dm`,
`publish`, `pay`, `charge`, `delete`). A disagreement is resolved by NOT
CALLING, and the skip is written into the corpus as `safety_skipped` — not only
into a log nobody keeps, because a measurement that is silently absent reads as
a measurement that found nothing. Measured 2026-09-09: one cell, `snappy-skool
post-detail`, which is a read whose name carries `post` as a noun. It costs one
read sample and it fails in the safe direction.
## Incidents this table must keep visible
| Incident | Failure |
|---|---|
| I1 | LinkedIn read took 10.5 s and 11 polls, later 40.8 s |
| I2 | `limit 5` returned 63 KB |
| I3 | `body` vs `text` refused in 24 ms and named the grammar |
| I4 | Telegram prose promised voice before the contract did |
| I5 | Gmail card needed a PNG for Telegram and took four calls |
| I6 | Host flag and optional-env inference falsely required credentials |
| I7 | Readless hands looked broken until their kind was explicit |
| I8 | Preflight moved refusal from 2,376 ms to 0.37 ms |
| I9 | FreshBooks status was 1,843 minutes stale |
| I10 | Stored OpenUI existed while presentation said not renderable |
| I11 | Real-host driving found handshake, collapse, and shrink defects |
| I12 | Gmail duplicated a retired road already owned by email |
| I13 | Sales nested page fell from 127 KB to 3 KB after bounding |
| I14 | Three loaders taught a refusing first call: an optional positional sat before `limit` |
## Deliberate divergences
- Snappy keeps five runner tools and typed hand contracts, not code-as-argument.
- Verbosity lives in one runner paging road, not 95 `response_format` arguments.
- Tool annotations never decide staging.
- Picture channels require PNG, a channel MCP Apps does not model.
- Brand-accurate outside-channel faces are intentional despite host-neutral UI advice.
- S19 and S20 contribute only unquoted directional checks until verified.
## The warning rung inside skill-check
`skill-check.sh` runs this lint LAST and can never turn the gate red: it
resolves `$SKILLS_DIR/snappy-tool-design/api.ts`, skips in silence when that is
absent, prints `tool-design lint (TD1 advisory)...`, runs `lint --all --summary`
(or `lint <skill> --summary` for a single target), and swallows every failure
into one non-blocking warning line. It adds nothing to `FAIL`, to
`FAILED_SKILLS`, or to the exit code. Advisory on purpose: the collection's
rule-6 and rule-60 debt is ~97 skills wide, and a gate that goes red on day one
is a gate somebody switches off.
`skill-check` grades `$SKILLS_DIR`, and `~/.claude/skills/snappy-*` are symlinks
into the MAIN checkout — to grade a worktree, pass
`SNAPPY_SKILLS_DIR=$PWD/skills`. This skill's own `lint` needs no flag: it
resolves its root as the parent of its own directory.
The rung's exact steps, and the dated per-rule census of the 98-skill
collection, are in [skill-check-rung.md](skill-check-rung.md).
## Loader repair
`fix-loader` delegates to `snappy-skill.syncLoaderVerbTable()`. That function
calls `scaffold()` and owns the only marked-block writer. Re-running an
unchanged contract writes nothing.
## Related
| Skill | Relationship |
|---|---|
| `snappy-skill` | Owns scaffold and generated-loader writes |
| `snappy-settings` | Runs this lint as a warning rung |
| `snappy-artifact-loop` | Manages implementation feedback, not tool-surface ergonomics |
| `snappy-faces` | Owns face selection and channel-fit presentation |
| `snappy-hands` | Derives and serves executable hand contracts |
| `snappy-agent-host` | Runs agent CLIs over ACP and prints their steps; this skill grades the tool surface those agents read, and never launches one |
**Skill Status**: COMPLETE. Artifact: the API evaluates all 60 sourced rules;
`probe` and `render` produce the evidence the 26 measured rules grade from, and
`loop` turns what failed into ranked lane briefs.
Measured 2026-09-09 12:2x, one full `loop --via mini` over the 98-skill
collection (78 hands answered `info` on the runner), against the 02:19 run of
the same night:
| | 09-09 02:19 | 09-09 12:25 |
|---|---:|---:|
| collection PASS | 4 022 | **4 085** |
| collection FAIL | 636 | **633** |
| collection DEFER | 1 222 | **1 162** |
| probe accuracy | 0.7005 | **0.7186** |
The 60 gradings that left DEFER left it because the fresh probe answered them:
R15 +16 PASS, R24 +14 PASS, R47/R48/R49 +10 PASS each, R43 +3 PASS. This
skill's own line is `48 pass, 5 fail, 7 defer` — the seven are R22, R25, R26,
R44, R47, R48 and R49, each one a measurement no run has taken yet rather than
a rule it fails. The earlier claim on this line, "the skill's own DEFER count
fell from 26 to 0", was measured before those rules had a census and is
replaced rather than appended to.
import assert from "node:assert/strict";
import test from "node:test";
import { HAND_CONTRACT, descriptionSync, exampleFor, exampleHazard, firstExample, lintAll, lintSkill } from "./api.ts";
import { renderLoaderVerbTable, type HandContract } from "../snappy-skill/api.ts";
test("example is derived from the Gmail contract", async () => {
assert.equal(await firstExample("snappy-gmail", "list"), "npx tsx ~/.claude/skills/snappy-gmail/api.ts list");
});
/* A LIVE HAND'S SHAPE IS NOT A RULE ⟨lane requires-scope, 2026-09-09⟩. These two
* tests used to be spelled against snappy-gmail: one pinned its six verbs in a
* regex, the other asserted its rule-6 status was FAIL. Both went stale the same
* way — gmail gained `reply` and had its description synced, so a hand getting
* BETTER turned the suite red, and the second test's whole subject (a legacy
* manual copy) stopped existing in the collection, which would have left the
* rule's FAIL arm with no test at all. The rule is what is under test, so the
* fixture is synthetic and this file owns it. */
const FIXTURE: HandContract = {
skill: "snappy-fixture",
description: "Two verbs, one of them taking a required word, so the example road has something to build.",
verbs: {
list: { args: ["limit?"], effect: "read" },
send: { args: ["to", "body"], effect: "send" },
},
};
test("the first call is built out of the contract's required words alone", () => {
assert.equal(exampleFor(FIXTURE, "list"), "npx tsx ~/.claude/skills/snappy-fixture/api.ts list");
assert.match(exampleFor(FIXTURE, "send"), /^npx tsx ~\/\.claude\/skills\/snappy-fixture\/api\.ts send \S+ \S+$/);
});
test("unknown example verb names the correcting contract slice", () => {
assert.throws(() => exampleFor(FIXTURE, "missing"), /snappy-fixture: verb "missing" is absent; contract slice verbs = \[list, send\]/);
assert.throws(() => exampleFor({ skill: "snappy-empty", verbs: {} }, "missing"), /contract slice verbs = \[none\]/);
});
test("example is derived from a live contract too, through the one road", async () => {
const contract = await import("../snappy-gmail/api.ts").then((m) => m.HAND_CONTRACT as unknown as HandContract);
assert.equal(await firstExample("snappy-gmail", "list"), exampleFor(contract, "list"));
});
test("the closed refusal table has explicit test coverage", () => {
assert.ok(HAND_CONTRACT.refusals.unknown_verb);
assert.ok(HAND_CONTRACT.refusals.missing_argument);
});
test("generated loader block carries contract words and show road", () => {
const contract: HandContract = { skill: "snappy-test", verbs: { list: { args: ["limit?"], effect: "read" } } };
const block = renderLoaderVerbTable(contract);
assert.match(block, /`list` \| `limit\?` \| `read`/);
assert.match(block, /snappy_present\(<answer>\)/);
assert.match(block, /crop to the\nelement, render at 2x on Retina/);
assert.match(block, /\/snappy-faces/);
});
test("self lint evaluates all 61 sourced rules", async () => {
const report = await lintSkill("snappy-tool-design");
assert.equal(report.rules.length, 61);
assert.equal(report.rules.find((rule) => rule.id === 6)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 17)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 19)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 45)?.status, "PASS");
// R15 was DEFER until `probe` existed to fuzz a near-miss key for real.
assert.equal(report.rules.find((rule) => rule.id === 15)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 59)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 60)?.status, "PASS");
});
test("contract-derived description catches a legacy manual copy", () => {
const contract = "Read and send Gmail through the owner's own login.";
assert.equal(descriptionSync(contract, contract).pass, true);
assert.equal(descriptionSync(contract, ` ${contract.replace(" ", "\n ")} `).pass, true, "whitespace is not drift");
const legacy = descriptionSync(contract, "Reads Gmail. Triggers on: email, inbox, unread.");
assert.equal(legacy.pass, false, "a manual copy that says something else is the defect the rule exists for");
assert.match(legacy.detail, /differs from contract source/);
assert.equal(descriptionSync(undefined, contract).detail, "HAND_CONTRACT.description missing");
});
test("lint-all reads the collection in one process and cites every rule", async () => {
const reports = await lintAll();
assert.ok(reports.length >= 96);
assert.ok(reports.every((report) => report.rules.length === 61));
assert.ok(reports.every((report) => report.rules.every((rule) => rule.sources.length > 0)));
});
// R59 CLOSED 2026-09-08. These two tests used to PIN the defect: they asserted
// that `snappy-imessage recent(contact?, limit?)` still shadowed its count. All
// six measured offenders now carry the count as `--limit N`, so the collection
// holds no live fixture for the detector's positive path — the assertions here
// are the ratchet that says so. Give the detector a pure fixture (a `hazardFor`
// over an args array) before the next grammar change, or its FAIL arm has no
// test at all.
test("the last optional positional in front of a count is gone from the collection", async () => {
const report = await lintSkill("snappy-imessage");
const rule = report.rules.find((entry) => entry.id === 59);
assert.equal(rule?.status, "PASS");
assert.match(rule!.detail, /no optional positional shadows a count/);
const reports = await lintAll();
const offenders = reports.filter((entry) => entry.rules.find((rule59) => rule59.id === 59)?.status === "FAIL");
assert.deepEqual(offenders.map((entry) => entry.skill), []);
});
test("every stale or absent loader block fails rule 60 and names the one repair", async () => {
const reports = await lintAll();
const failures = reports.flatMap((report) => report.rules.filter((rule) => rule.id === 60 && rule.status === "FAIL"));
assert.ok(failures.length > 0, "the collection still holds loaders with no generated block");
assert.ok(failures.every((rule) => /run fix-loader/.test(rule.detail)));
const self = await lintSkill("snappy-tool-design");
assert.equal(self.rules.find((rule) => rule.id === 60)?.status, "PASS");
});
test("no first call in the collection still hands the reader a count trap", async () => {
assert.equal(await exampleHazard("snappy-gmail", "list"), null);
assert.equal(await exampleHazard("snappy-tool-design", "example"), null);
// The six that taught `<verb> 5` and filed the 5 as a contact, a date or a
// channel. Each one's count is now `--limit N`; each answers null.
for (const [skill, verb] of [
["snappy-imessage", "recent"],
["snappy-krisp", "fetch-meetings"],
["snappy-thumbnails", "audit"],
["snappy-corpus", "read"],
["snappy-transcripts", "read"],
["snappy-youtube", "recent"],
] as const) {
assert.equal(await exampleHazard(skill, verb), null, `${skill} ${verb} still carries a rule-59 hazard`);
}
});
test("rule 17 grades the answer's SHAPE, never the verb's spelling", async () => {
const reports = await lintAll();
const rule17 = (skill: string) => reports.find((entry) => entry.skill === skill)?.rules.find((rule) => rule.id === 17);
// THE FOUR THAT ANSWER ONE RECORD ⟨lane r17-2, 2026-09-09⟩. The substring
// test matched "read"/"logs" in their NAMES and demanded a `limit` each: a
// single Gmail message, a single mastermind topic, Circle's unread COUNTERS
// (the arm publishes `count: 0`) and a log string. A bound on any of them
// would be a declaration nothing could honour, which is the defect this lint
// exists to catch — committed by the lint itself.
for (const [skill, verb] of [
["snappy-email", "read"],
["snappy-mastermind-model", "read"],
["snappy-statechange", "unread"],
["snappy-xano-dashboard", "logs"],
] as const) {
const graded = rule17(skill);
assert.equal(graded?.status, "PASS", `${skill} ${verb} is still charged with being a collection: ${graded?.detail}`);
assert.ok(!/limit default\/maximum missing/.test(graded?.detail ?? ""), `${skill}: rule 17 still demands a bound of ${verb}`);
}
// AND THE ROADS THAT DO ANSWER LISTS ARE STILL HELD. Each of these takes the
// bound through the ONE road (`snappy-settings/read-limit.ts`), so each must
// still be counted as a collection read and still pass.
for (const [skill, verb] of [
["snappy-gmail", "thread"],
["snappy-telegram", "updates"],
["snappy-calendar", "events"],
["snappy-database", "query"],
["snappy-skool", "messages"],
] as const) {
const graded = rule17(skill);
assert.equal(graded?.status, "PASS", `${skill} ${verb}: ${graded?.detail}`);
assert.match(graded?.detail ?? "", /collection read\(s\) bounded/, `${skill} ${verb} stopped being graded at all`);
}
// THE POPULATION GREW. A narrowed test that graded FEWER verbs would be a
// rule going green by looking away ⟨CLAUDE.md R10⟩; the substring test saw 57
// read verbs and this one sees more, because names like `freshbooks clients`,
// `github repos` and `update commits` carry none of its letters.
const bounded = reports
.map((entry) => entry.rules.find((rule) => rule.id === 17)?.detail ?? "")
.map((detail) => Number(detail.match(/^(\d+) collection read\(s\) bounded$/)?.[1] ?? 0))
.reduce((sum, count) => sum + count, 0);
assert.ok(bounded >= 57, `only ${bounded} collection reads are graded; the substring test graded 57`);
});
import assert from "node:assert/strict";
import test from "node:test";
import { HAND_CONTRACT, descriptionSync, exampleFor, exampleHazard, firstExample, lintAll, lintSkill } from "./api.ts";
import { renderLoaderVerbTable, type HandContract } from "../snappy-skill/api.ts";
test("example is derived from the Gmail contract", async () => {
assert.equal(await firstExample("snappy-gmail", "list"), "npx tsx ~/.claude/skills/snappy-gmail/api.ts list");
});
/* A LIVE HAND'S SHAPE IS NOT A RULE ⟨lane requires-scope, 2026-09-09⟩. These two
* tests used to be spelled against snappy-gmail: one pinned its six verbs in a
* regex, the other asserted its rule-6 status was FAIL. Both went stale the same
* way — gmail gained `reply` and had its description synced, so a hand getting
* BETTER turned the suite red, and the second test's whole subject (a legacy
* manual copy) stopped existing in the collection, which would have left the
* rule's FAIL arm with no test at all. The rule is what is under test, so the
* fixture is synthetic and this file owns it. */
const FIXTURE: HandContract = {
skill: "snappy-fixture",
description: "Two verbs, one of them taking a required word, so the example road has something to build.",
verbs: {
list: { args: ["limit?"], effect: "read" },
send: { args: ["to", "body"], effect: "send" },
},
};
test("the first call is built out of the contract's required words alone", () => {
assert.equal(exampleFor(FIXTURE, "list"), "npx tsx ~/.claude/skills/snappy-fixture/api.ts list");
assert.match(exampleFor(FIXTURE, "send"), /^npx tsx ~\/\.claude\/skills\/snappy-fixture\/api\.ts send \S+ \S+$/);
});
test("unknown example verb names the correcting contract slice", () => {
assert.throws(() => exampleFor(FIXTURE, "missing"), /snappy-fixture: verb "missing" is absent; contract slice verbs = \[list, send\]/);
assert.throws(() => exampleFor({ skill: "snappy-empty", verbs: {} }, "missing"), /contract slice verbs = \[none\]/);
});
test("example is derived from a live contract too, through the one road", async () => {
const contract = await import("../snappy-gmail/api.ts").then((m) => m.HAND_CONTRACT as unknown as HandContract);
assert.equal(await firstExample("snappy-gmail", "list"), exampleFor(contract, "list"));
});
test("the closed refusal table has explicit test coverage", () => {
assert.ok(HAND_CONTRACT.refusals.unknown_verb);
assert.ok(HAND_CONTRACT.refusals.missing_argument);
});
test("generated loader block carries contract words and show road", () => {
const contract: HandContract = { skill: "snappy-test", verbs: { list: { args: ["limit?"], effect: "read" } } };
const block = renderLoaderVerbTable(contract);
assert.match(block, /`list` \| `limit\?` \| `read`/);
assert.match(block, /snappy_present\(<answer>\)/);
assert.match(block, /crop to the\nelement, render at 2x on Retina/);
assert.match(block, /\/snappy-faces/);
});
test("self lint evaluates all 61 sourced rules", async () => {
const report = await lintSkill("snappy-tool-design");
assert.equal(report.rules.length, 61);
assert.equal(report.rules.find((rule) => rule.id === 6)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 17)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 19)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 45)?.status, "PASS");
// R15 was DEFER until `probe` existed to fuzz a near-miss key for real.
assert.equal(report.rules.find((rule) => rule.id === 15)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 59)?.status, "PASS");
assert.equal(report.rules.find((rule) => rule.id === 60)?.status, "PASS");
});
test("contract-derived description catches a legacy manual copy", () => {
const contract = "Read and send Gmail through the owner's own login.";
assert.equal(descriptionSync(contract, contract).pass, true);
assert.equal(descriptionSync(contract, ` ${contract.replace(" ", "\n ")} `).pass, true, "whitespace is not drift");
const legacy = descriptionSync(contract, "Reads Gmail. Triggers on: email, inbox, unread.");
assert.equal(legacy.pass, false, "a manual copy that says something else is the defect the rule exists for");
assert.match(legacy.detail, /differs from contract source/);
assert.equal(descriptionSync(undefined, contract).detail, "HAND_CONTRACT.description missing");
});
test("lint-all reads the collection in one process and cites every rule", async () => {
const reports = await lintAll();
assert.ok(reports.length >= 96);
assert.ok(reports.every((report) => report.rules.length === 61));
assert.ok(reports.every((report) => report.rules.every((rule) => rule.sources.length > 0)));
});
// R59 CLOSED 2026-09-08. These two tests used to PIN the defect: they asserted
// that `snappy-imessage recent(contact?, limit?)` still shadowed its count. All
// six measured offenders now carry the count as `--limit N`, so the collection
// holds no live fixture for the detector's positive path — the assertions here
// are the ratchet that says so. Give the detector a pure fixture (a `hazardFor`
// over an args array) before the next grammar change, or its FAIL arm has no
// test at all.
test("the last optional positional in front of a count is gone from the collection", async () => {
const report = await lintSkill("snappy-imessage");
const rule = report.rules.find((entry) => entry.id === 59);
assert.equal(rule?.status, "PASS");
assert.match(rule!.detail, /no optional positional shadows a count/);
const reports = await lintAll();
const offenders = reports.filter((entry) => entry.rules.find((rule59) => rule59.id === 59)?.status === "FAIL");
assert.deepEqual(offenders.map((entry) => entry.skill), []);
});
test("every stale or absent loader block fails rule 60 and names the one repair", async () => {
const reports = await lintAll();
const failures = reports.flatMap((report) => report.rules.filter((rule) => rule.id === 60 && rule.status === "FAIL"));
assert.ok(failures.length > 0, "the collection still holds loaders with no generated block");
assert.ok(failures.every((rule) => /run fix-loader/.test(rule.detail)));
const self = await lintSkill("snappy-tool-design");
assert.equal(self.rules.find((rule) => rule.id === 60)?.status, "PASS");
});
test("no first call in the collection still hands the reader a count trap", async () => {
assert.equal(await exampleHazard("snappy-gmail", "list"), null);
assert.equal(await exampleHazard("snappy-tool-design", "example"), null);
// The six that taught `<verb> 5` and filed the 5 as a contact, a date or a
// channel. Each one's count is now `--limit N`; each answers null.
for (const [skill, verb] of [
["snappy-imessage", "recent"],
["snappy-krisp", "fetch-meetings"],
["snappy-thumbnails", "audit"],
["snappy-corpus", "read"],
["snappy-transcripts", "read"],
["snappy-youtube", "recent"],
] as const) {
assert.equal(await exampleHazard(skill, verb), null, `${skill} ${verb} still carries a rule-59 hazard`);
}
});
test("rule 17 grades the answer's SHAPE, never the verb's spelling", async () => {
const reports = await lintAll();
const rule17 = (skill: string) => reports.find((entry) => entry.skill === skill)?.rules.find((rule) => rule.id === 17);
// THE FOUR THAT ANSWER ONE RECORD ⟨lane r17-2, 2026-09-09⟩. The substring
// test matched "read"/"logs" in their NAMES and demanded a `limit` each: a
// single Gmail message, a single mastermind topic, Circle's unread COUNTERS
// (the arm publishes `count: 0`) and a log string. A bound on any of them
// would be a declaration nothing could honour, which is the defect this lint
// exists to catch — committed by the lint itself.
for (const [skill, verb] of [
["snappy-email", "read"],
["snappy-mastermind-model", "read"],
["snappy-statechange", "unread"],
["snappy-xano-dashboard", "logs"],
] as const) {
const graded = rule17(skill);
assert.equal(graded?.status, "PASS", `${skill} ${verb} is still charged with being a collection: ${graded?.detail}`);
assert.ok(!/limit default\/maximum missing/.test(graded?.detail ?? ""), `${skill}: rule 17 still demands a bound of ${verb}`);
}
// AND THE ROADS THAT DO ANSWER LISTS ARE STILL HELD. Each of these takes the
// bound through the ONE road (`snappy-settings/read-limit.ts`), so each must
// still be counted as a collection read and still pass.
for (const [skill, verb] of [
["snappy-gmail", "thread"],
["snappy-telegram", "updates"],
["snappy-calendar", "events"],
["snappy-database", "query"],
["snappy-skool", "messages"],
] as const) {
const graded = rule17(skill);
assert.equal(graded?.status, "PASS", `${skill} ${verb}: ${graded?.detail}`);
assert.match(graded?.detail ?? "", /collection read\(s\) bounded/, `${skill} ${verb} stopped being graded at all`);
}
// THE POPULATION GREW. A narrowed test that graded FEWER verbs would be a
// rule going green by looking away ⟨CLAUDE.md R10⟩; the substring test saw 57
// read verbs and this one sees more, because names like `freshbooks clients`,
// `github repos` and `update commits` carry none of its letters.
const bounded = reports
.map((entry) => entry.rules.find((rule) => rule.id === 17)?.detail ?? "")
.map((detail) => Number(detail.match(/^(\d+) collection read\(s\) bounded$/)?.[1] ?? 0))
.reduce((sum, count) => sum + count, 0);
assert.ok(bounded >= 57, `only ${bounded} collection reads are graded; the substring test graded 57`);
});
#!/usr/bin/env npx tsx
/**
* snappy-tool-design: sourced, mechanical ergonomics lint for AI-operated tools.
* The reader's failure modes are the specification. Rules and source IDs are
* rebased from RESEARCH.md; probe/render checks defer until evidence exists.
*/
import {
existsSync,
readFileSync,
readdirSync,
realpathSync,
statSync,
writeFileSync,
} from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { pooledAll } from "../snappy-settings/spawn-pool.ts";
import { RefusedError, isRefusedError, printRefusal, refusalTable } from "../snappy-settings/refusal-codes.ts";
import { collectLocalSource, collectOwnSource, envReads, evidenceEnvelopeDeclared } from "../snappy-settings/env-reads.ts";
import { printAll, printOne } from "./report.ts";
import {
SPEC_FILE,
driftOf,
driftSentence,
readSpecFile,
specFor,
writeSpecFile,
vendorOf,
type SpecCorpus,
} from "./spec-corpus.ts";
import type { SpecProjection } from "../snappy-settings/spec-read.ts";
import {
PROBE_FILE,
RENDER_FILE,
probeFor,
readProbeFile,
readRenderFile,
faceKindsDrawn,
renderFor,
type ProbeCorpus,
type ProbeRead,
type RenderFile,
} from "./corpus.ts";
import {
contractExample,
renderLoaderVerbTable,
syncLoaderVerbTable,
type HandContract,
type HandVerb,
} from "../snappy-skill/api.ts";
const HERE = dirname(realpathSync(fileURLToPath(import.meta.url)));
const SKILLS_ROOT = dirname(HERE);
type RuleMode = "static" | "probe" | "render" | "spec";
type RuleStatus = "PASS" | "FAIL" | "DEFER";
type EffectClass = NonNullable<HandVerb["class"]>;
export interface RuleDefinition {
id: number;
group: string;
rule: string;
sources: string[];
mode: RuleMode;
}
export interface RuleResult extends RuleDefinition {
status: RuleStatus;
pass: boolean;
detail: string;
}
export interface LintResult {
skill: string;
pass: number;
fail: number;
defer: number;
rules: RuleResult[];
}
interface SkillModel {
name: string;
dir: string;
contract: HandContract;
api: string;
source: string;
/** The hand's OWN api.ts and its own local modules — never another skill's. */
ownSource: string;
agents: string;
manual: string;
description: string;
frontmatter: Record<string, string>;
probe: ProbeCorpus | null;
renderProbe: { values: Record<string, boolean>; families: string[] } | null;
/** The vendor document this hand pinned, as last read. Null when the hand
* names no machine-readable spec, or when no corpus has been written. */
vendor: SpecProjection | null;
}
export const RULES: RuleDefinition[] = [
{ id: 1, group: "B1 Naming", rule: "Skill name satisfies the open spec and equals its directory", sources: ["S7"], mode: "static" },
{ id: 2, group: "B1 Naming", rule: "Verb identity is namespaced by hand and collisions keep one grammar", sources: ["S1"], mode: "static" },
{ id: 3, group: "B1 Naming", rule: "Vague generic skill names are banned", sources: ["S6"], mode: "static" },
{ id: 4, group: "B1 Naming", rule: "Near-neighbour descriptions cross-reference each other", sources: ["S12"], mode: "static" },
{ id: 5, group: "B2 Description", rule: "Description is valid, third-person, and says what plus when", sources: ["S5", "S6", "S7", "S21"], mode: "static" },
{ id: 6, group: "B2 Description", rule: "Description is contract-derived with zero manual delta", sources: ["CLAUDE.md R4", "S18"], mode: "static" },
{ id: 7, group: "B2 Description", rule: "Promised and declared verbs have no set difference", sources: ["CLAUDE.md R4"], mode: "static" },
{ id: 8, group: "B2 Description", rule: "Skill body and reference graph stay within disclosure budgets", sources: ["S5", "S6", "S7"], mode: "static" },
{ id: 9, group: "B2 Description", rule: "Time-sensitive prose is isolated under Old patterns", sources: ["S6"], mode: "static" },
{ id: 10, group: "B2 Description", rule: "One term names each concept", sources: ["S6"], mode: "static" },
{ id: 11, group: "B2 Description", rule: "Each task presents one default, not a menu", sources: ["S6"], mode: "static" },
{ id: 12, group: "B2 Description", rule: "Description contains no unauthorised imperative", sources: ["S18"], mode: "static" },
{ id: 13, group: "B3 Arguments", rule: "Every argument is described and unambiguous", sources: ["S1", "S11"], mode: "static" },
{ id: 14, group: "B3 Arguments", rule: "Enumerable domains use enums", sources: ["S2", "S11"], mode: "static" },
{ id: 15, group: "B3 Arguments", rule: "Near-miss keys refuse with the correct contract key", sources: ["S6", "S19 [snippet]", "S20 [snippet]"], mode: "probe" },
{ id: 16, group: "B3 Arguments", rule: "Runner-known values are not required arguments", sources: ["S11"], mode: "static" },
{ id: 17, group: "B3 Arguments", rule: "Collection reads declare limit default and maximum", sources: ["S1"], mode: "static" },
{ id: 18, group: "B4 Effects", rule: "Every verb declares a side-effect class", sources: ["S12", "CLAUDE.md R6"], mode: "static" },
{ id: 19, group: "B4 Effects", rule: "Published annotations are derived from the effect class", sources: ["S9"], mode: "static" },
{ id: 20, group: "B4 Effects", rule: "Read and write are separate verbs", sources: ["S12"], mode: "static" },
{ id: 21, group: "B4 Effects", rule: "Annotations never enforce stage policy", sources: ["S8", "S9"], mode: "static" },
{ id: 22, group: "B4 Effects", rule: "Unmet requirements refuse before spawn in under 50 ms", sources: ["S14"], mode: "probe" },
{ id: 23, group: "B5 Response", rule: "Responses obey hard and soft caps", sources: ["S1"], mode: "probe" },
{ id: 24, group: "B5 Response", rule: "Answer size is bounded by requested limit", sources: ["S1"], mode: "probe" },
{ id: 25, group: "B5 Response", rule: "Over-cap answers summarize and provide a cursor", sources: ["S14"], mode: "probe" },
{ id: 26, group: "B5 Response", rule: "Truncation names the actionable next move", sources: ["S1"], mode: "probe" },
{ id: 27, group: "B5 Response", rule: "Default density returns names rather than opaque IDs", sources: ["S1"], mode: "probe" },
{ id: 28, group: "B5 Response", rule: "Structured output validates and retains text compatibility", sources: ["S8"], mode: "probe" },
{ id: 29, group: "B5 Response", rule: "Facts carry source timestamps and stale labels", sources: ["CLAUDE.md R10"], mode: "probe" },
{ id: 30, group: "B5 Response", rule: "Third-party text is wrapped as data, not instructions", sources: ["S18"], mode: "static" },
{ id: 31, group: "B6 Refusal", rule: "Refusals have outcome, code, message, and fix", sources: ["S8", "S19 [snippet]", "S20 [snippet]"], mode: "probe" },
{ id: 32, group: "B6 Refusal", rule: "Refusals name a literal from the valid domain", sources: ["S1", "S6"], mode: "probe" },
{ id: 33, group: "B6 Refusal", rule: "Refusal codes form one tested closed table", sources: ["S1", "S19 [snippet]"], mode: "static" },
{ id: 34, group: "B6 Refusal", rule: "Refusals never contain secret values", sources: ["CLAUDE.md"], mode: "static" },
{ id: 35, group: "B6 Refusal", rule: "Requirements are declared from executable credential reads", sources: ["S6", "CLAUDE.md R4"], mode: "static" },
{ id: 36, group: "B6 Refusal", rule: "Optional credentials do not become requirements", sources: ["I6 measured"], mode: "static" },
{ id: 37, group: "B7 Disclosure", rule: "Published MCP catalog stays at ten tools or fewer", sources: ["S11", "S15", "S16", "S17"], mode: "probe" },
{ id: 38, group: "B7 Disclosure", rule: "Tools-list token cost fits the catalog budget", sources: ["S17"], mode: "probe" },
{ id: 39, group: "B7 Disclosure", rule: "Verb sets consolidate intent rather than mirror endpoints", sources: ["S1", "S17"], mode: "static" },
{ id: 40, group: "B7 Disclosure", rule: "Metadata, skill body, and resources follow three-level budgets", sources: ["S3", "S4", "S5", "S7"], mode: "static" },
{ id: 41, group: "B7 Disclosure", rule: "Search ranks each hand in the top three", sources: ["S15", "S16"], mode: "probe" },
{ id: 42, group: "B7 Disclosure", rule: "Readless hands declare instruction-only or write-only kind", sources: ["S12"], mode: "static" },
{ id: 43, group: "B8 Latency", rule: "A read is one call, not a queued job", sources: ["S14", "owner 18:4x"], mode: "probe" },
{ id: 44, group: "B8 Latency", rule: "Each verb publishes and meets an expected latency band", sources: ["S1"], mode: "probe" },
{ id: 45, group: "B8 Latency", rule: "Long work is a separately named job verb", sources: ["S2"], mode: "static" },
{ id: 46, group: "B9 UI", rule: "A face exists only when visual interaction improves work", sources: ["S10", "S13"], mode: "probe" },
{ id: 47, group: "B9 UI", rule: "UI-bearing results retain standalone text", sources: ["S10"], mode: "probe" },
{ id: 48, group: "B9 UI", rule: "UI resources are preregistered with exact URI and MIME", sources: ["S10"], mode: "probe" },
{ id: 49, group: "B9 UI", rule: "Channel chooses PNG, OpenUI program, or text", sources: ["S10", "owner 20:5x"], mode: "probe" },
{ id: 50, group: "B9 UI", rule: "Faces shrink and grow without nested scrolling", sources: ["S13"], mode: "render" },
{ id: 51, group: "B9 UI", rule: "Cards have at most two actions and carousels three to eight items", sources: ["S13"], mode: "render" },
{ id: 52, group: "B9 UI", rule: "Host handshake listens before host delivery", sources: ["I11 measured"], mode: "render" },
{ id: 53, group: "B9 UI", rule: "Every face family has a dated real-host screenshot", sources: ["CLAUDE.md R10"], mode: "render" },
{ id: 54, group: "B10 Evals", rule: "At least three eval scenarios precede ready status", sources: ["S6"], mode: "static" },
{ id: 55, group: "B10 Evals", rule: "Eval scenarios are multi-call with ground truth", sources: ["S1"], mode: "static" },
{ id: 56, group: "B10 Evals", rule: "Probe runs record accuracy, runtime, calls, tokens, errors", sources: ["S1"], mode: "probe" },
{ id: 57, group: "B10 Evals", rule: "Eval manifest contains a disjoint holdout", sources: ["S1"], mode: "static" },
{ id: 58, group: "B10 Evals", rule: "Proof comes from a fresh post-deploy client", sources: ["CLAUDE.md R7"], mode: "probe" },
{ id: 59, group: "B11 MCP projection", rule: "No optional positional precedes a commonly-supplied argument", sources: ["S11", "S1", "I14 measured"], mode: "static" },
{ id: 60, group: "B11 MCP projection", rule: "Loader publishes the contract's derived first call, current", sources: ["S3", "S4", "S8", "CLAUDE.md R4"], mode: "static" },
{ id: 61, group: "B12 Vendor spec", rule: "The vendor's own spec still has what this hand offers, and this hand offers what the vendor added", sources: ["S22", "S23", "owner 16:5x"], mode: "spec" },
];
/** THE ARGUMENTS AN AI SUPPLIES WITHOUT BEING ASKED. "the last 5 messages" is
* a count in every language a person uses, so the model writes the count and
* nothing else. Measured 2026-09-08 (I14): three loaders taught a refusal —
* `snappy-imessage recent`, `snappy-krisp fetch-meetings` and
* `snappy-thumbnails audit` each put an OPTIONAL positional in front of the
* count, so `recent 5` filed 5 as the contact and answered wrong or empty. */
const COMMONLY_SUPPLIED = /^(limit|count|n|top|max|size|per-?page|first|take)\??$/i;
const contractCache = new Map<string, Promise<HandContract>>();
export async function loadContract(skill: string): Promise<HandContract> {
const name = normalizeSkill(skill);
const cached = contractCache.get(name);
if (cached) return cached;
const promise = (async () => {
const api = join(SKILLS_ROOT, name, "api.ts");
if (!existsSync(api)) throw new Error(`${name}: missing api.ts (contract slice: HAND_CONTRACT)`);
const module = await import(pathToFileURL(api).href);
const contract = module.HAND_CONTRACT as HandContract | undefined;
if (!contract || typeof contract !== "object") throw new Error(`${name}: api.ts does not export HAND_CONTRACT`);
if (contract.skill !== name) throw new Error(`${name}: HAND_CONTRACT.skill is ${JSON.stringify(contract.skill)}`);
if (!contract.verbs || typeof contract.verbs !== "object") throw new Error(`${name}: HAND_CONTRACT.verbs is missing`);
return contract;
})();
contractCache.set(name, promise);
return promise;
}
export async function lintSkill(skill: string): Promise<LintResult> {
const models = await loadModels();
const name = normalizeSkill(skill);
const model = models.find((candidate) => candidate.name === name);
if (!model) throw new Error(`${name}: skill model not found`);
return evaluate(model, models);
}
export async function lintAll(): Promise<LintResult[]> {
const models = await loadModels();
return models.map((model) => evaluate(model, models));
}
/** THE FIRST CALL, OUT OF A CONTRACT AND NOTHING ELSE. Pure, so the rule it
* carries — a wrong verb is answered with the slice that CORRECTS it, never a
* bare "not found" the caller has to go looking after — can be tested against a
* fixture contract instead of whichever live hand happened to have the shape
* ⟨lane requires-scope, 2026-09-09: the test that pinned this asserted
* snappy-gmail's six verbs and went red the day gmail gained `reply`, which is
* the hand improving and the test calling it a regression⟩. */
export function exampleFor(contract: HandContract, verb: string): string {
if (!contract.verbs[verb]) {
const choices = Object.keys(contract.verbs).join(", ") || "none";
throw new Error(`${contract.skill}: verb ${JSON.stringify(verb)} is absent; contract slice verbs = [${choices}]`);
}
return contractExample(contract, verb);
}
export async function firstExample(skill: string, verb: string): Promise<string> {
return exampleFor(await loadContract(skill), verb);
}
/** THE SECOND SENTENCE THE CALLER NEEDS. The first call is safe because it
* omits every optional word — but the next thing an AI writes is the count it
* was asked for, and rule 59 says where that count actually lands. Returns
* null when the verb's grammar has no such trap. */
export async function exampleHazard(skill: string, verb: string): Promise<string | null> {
const contract = await loadContract(skill);
const args = [...(contract.verbs[verb]?.args ?? [])];
const target = args.findIndex((arg) => COMMONLY_SUPPLIED.test(arg));
if (target <= 0) return null;
const shadow = args.slice(0, target).filter((arg) => arg.endsWith("?"));
if (!shadow.length) return null;
const bare = args[target].replace(/\?$/, "");
const fillers = args.slice(0, target).map((arg) => (arg.endsWith("?") ? '""' : `<${arg.replace(/\?$/, "")}>`));
return `rule 59: ${verb}(${args.join(", ")}) — a bare ${bare} lands in ${shadow[0]}. Supply the earlier words: ${verb} ${fillers.join(" ")} <${bare}>`;
}
/**
* READ EVERY PINNED SPEC AND WRITE THE CORPUS. One road: `snappy-specwatch`
* calls this, and so does `api.ts specs` — there is no second fetcher.
* A hand whose spec is `docs`, `mini` or `none` is SKIPPED and said so; only
* `openapi` and `discovery` are ever reached for, and never with a credential.
*/
export async function readSpecs(only?: string): Promise<{ written: string; read: string[]; skipped: Array<{ skill: string; why: string }> }> {
const { readHandSpec } = await import("../snappy-settings/spec-read.ts");
const models = only ? [await loadModel(normalizeSkill(only), null, null, null)] : await loadModels();
const read: string[] = [];
const skipped: Array<{ skill: string; why: string }> = [];
const projections = [];
for (const model of models) {
const spec = model.contract.spec;
if (!spec) { if (only) skipped.push({ skill: model.name, why: "declares no HAND_CONTRACT.spec" }); continue; }
if (spec.kind !== "openapi" && spec.kind !== "discovery") { skipped.push({ skill: model.name, why: `kind "${spec.kind}" is declared, never fetched${spec.reason ? `: ${spec.reason}` : ""}` }); continue; }
try {
projections.push(await readHandSpec(model.name, spec));
read.push(model.name);
} catch (error) {
// EACH HAND IS READ ON ITS OWN. A shared catch would let one unreachable
// vendor throw away every other reading in the same pass.
skipped.push({ skill: model.name, why: error instanceof Error ? error.message : String(error) });
}
}
const written = projections.length ? writeSpecFile(join(SKILLS_ROOT, "snappy-tool-design"), projections).path : "(nothing read, nothing written)";
return { written, read, skipped };
}
/** THE HAND'S OWN CONTRACT, AS AN OPENAPI DOCUMENT. The fold is in
* snappy-settings; this is the one door onto it, because this file already
* owns the collection's one contract loader. */
export async function handOpenApi(skill: string) {
const { contractOpenApi } = await import("../snappy-settings/contract-openapi.ts");
return contractOpenApi(await loadContract(skill) as never);
}
export async function fixLoader(skill: string) {
const contract = await loadContract(skill);
return syncLoaderVerbTable(contract.skill, contract, { quiet: true });
}
async function loadModels(): Promise<SkillModel[]> {
const names = readdirSync(SKILLS_ROOT, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith("snappy-") && existsSync(join(SKILLS_ROOT, entry.name, "api.ts")))
.map((entry) => entry.name)
.sort();
// ONE probe file and ONE render file for the whole collection, read once.
const probe = readProbeFile(join(SKILLS_ROOT, "snappy-tool-design"));
const render = readRenderFile(join(SKILLS_ROOT, "snappy-tool-design"));
const specs = readSpecFile(join(SKILLS_ROOT, "snappy-tool-design"));
// BOUNDED, NOT ALL AT ONCE. Every one of these loads a hand's whole api.ts
// module graph; 87 in one Promise.all is the fan-out that put this Mac at
// load 311 beside two runner suites ⟨2026-09-09 04:2x⟩. The pool is the one
// in snappy-settings, whose twin is the runner's src/spawn-pool.ts.
return pooledAll(names, (name) => loadModel(name, probe, render, specs));
}
async function loadModel(
name: string,
probe: ReturnType<typeof readProbeFile>,
render: ReturnType<typeof readRenderFile>,
specs: SpecCorpus | null,
): Promise<SkillModel> {
const dir = join(SKILLS_ROOT, name);
const apiPath = join(dir, "api.ts");
const api = readRequired(apiPath, `${name}: missing api.ts`);
const manual = readRequired(join(dir, "SKILL.md"), `${name}: missing SKILL.md`);
const agents = readRequired(join(dir, "AGENTS.md"), `${name}: missing AGENTS.md`);
const frontmatter = parseFrontmatter(manual);
return {
name,
dir,
contract: await loadContract(name),
api,
source: collectLocalSource(apiPath, new Set<string>(), SKILLS_ROOT),
ownSource: collectOwnSource(apiPath, dir),
manual,
agents,
description: frontmatter.description ?? "",
frontmatter,
probe: probeFor(probe, name),
renderProbe: renderFor(render, name, faceKindsDrawn(probeFor(probe, name))),
vendor: specFor(specs, name),
};
}
function evaluate(model: SkillModel, all: SkillModel[]): LintResult {
const rules = RULES.map((definition) => ({ ...definition, ...evaluateRule(definition.id, model, all) }));
return {
skill: model.name,
pass: rules.filter((rule) => rule.status === "PASS").length,
fail: rules.filter((rule) => rule.status === "FAIL").length,
defer: rules.filter((rule) => rule.status === "DEFER").length,
rules,
};
}
function evaluateRule(id: number, m: SkillModel, all: SkillModel[]): { status: RuleStatus; pass: boolean; detail: string } {
const verbs = Object.entries(m.contract.verbs);
const readVerbs = verbs.filter(([, spec]) => effectClass(spec) === "read");
const probeReads = m.probe?.reads ?? [];
const done = (ok: boolean, detail: string) => outcome(ok ? "PASS" : "FAIL", detail);
const probe = (detail: string) => outcome("DEFER", detail);
switch (id) {
case 1: {
const valid = m.name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(m.name) && m.frontmatter.name === m.name;
return done(valid, valid ? `${m.name} matches directory and open-spec grammar` : `frontmatter name=${JSON.stringify(m.frontmatter.name)} directory=${m.name}`);
}
case 2: {
const collisions = all.flatMap((other) => Object.entries(other.contract.verbs).map(([verb, spec]) => ({ owner: other.name, verb, grammar: JSON.stringify(spec.args ?? []) })))
.filter((row) => row.owner !== m.name && verbs.some(([verb, spec]) => verb === row.verb && JSON.stringify(spec.args ?? []) !== row.grammar));
const namespaced = m.contract.skill === m.name;
return done(namespaced, `${verbs.length} effective names use ${m.name}.<verb>; ${collisions.length} raw cross-hand grammar collision(s) are isolated by hand namespace`);
}
case 3: {
const bad = /(?:^|-)(helper|utils?|tools?|data|files?|misc)$/.test(m.name.replace(/^snappy-/, ""));
return done(!bad, bad ? "generic banned name/tail" : "specific noun phrase");
}
case 4: {
const peers = all.filter((other) => other.name !== m.name && descriptionOverlap(m.description, other.description) >= 6);
const missing = peers.filter((peer) => !m.manual.includes(peer.name) && !m.description.includes(peer.name));
return done(missing.length === 0, missing.length ? `near neighbours not named: ${missing.slice(0, 5).map((peer) => peer.name).join(", ")}` : `${peers.length} near neighbour(s) cross-referenced or none`);
}
case 5: {
const desc = m.description;
const problems = [
!desc && "empty",
desc.length > 1024 && `${desc.length} chars`,
/<[^>]+>/.test(desc) && "XML/HTML tag",
/(?:^|\s)(?:I|we|you|your)\b/i.test(desc) && "first/second person",
!/(?:use|triggers?)\s+(?:when|for|on)/i.test(desc) && "no when/use marker",
].filter(Boolean);
return done(problems.length === 0, problems.length ? problems.join("; ") : `${desc.length} chars; third-person what+when`);
}
case 6: {
const graded = descriptionSync(m.contract.description, m.description);
return done(graded.pass, graded.detail);
}
case 7: {
const promised = extractPromisedVerbs(`${m.agents}\n${m.manual}`, m.name);
const declared = new Set(Object.keys(m.contract.verbs));
const unknown = [...promised].filter((verb) => !declared.has(verb));
const absent = [...declared].filter((verb) => !m.agents.includes(`\`${verb}\``) && !m.agents.includes(` ${verb} `));
return done(unknown.length === 0 && absent.length === 0, `promised-only=[${unknown.join(", ")}]; loader-missing=[${absent.join(", ")}]`);
}
case 8: {
const lines = m.manual.split("\n").length;
const tokens = approxTokens(m.manual);
const linkProblems = referenceProblems(m);
return done(lines <= 500 && tokens < 5000 && linkProblems.length === 0, `${lines} lines; ~${tokens} tokens${linkProblems.length ? `; ${linkProblems.join("; ")}` : ""}`);
}
case 9: {
const body = stripOldPatterns(m.manual);
const stale = body.split("\n").filter((line) => /\b(?:before|after|until|currently|as of|old api|deprecated|version)\b/i.test(line) && /\b20\d{2}[-/]\d{1,2}(?:[-/]\d{1,2})?\b/.test(line));
return done(stale.length === 0, stale.length ? `${stale.length} time-sensitive instruction line(s) outside Old patterns` : "no time-sensitive instruction dates");
}
case 10: {
const sets = [["endpoint", "url", "route", "path"], ["field", "box", "element"]];
const mixed = sets.filter((set) => set.filter((word) => new RegExp(`\\b${word}s?\\b`, "i").test(m.manual)).length > 1);
return done(mixed.length === 0, mixed.length ? `mixed synonym sets: ${mixed.map((set) => set.join("/")).join(", ")}` : "one term per checked concept");
}
case 11: {
const menus = m.manual.split("\n").filter((line) => (line.match(/\bor\b/gi) ?? []).length >= 2);
return done(menus.length === 0, menus.length ? `${menus.length} line(s) present three-way alternative menus` : "no three-way alternative menu");
}
case 12: {
const imperatives = ["send", "delete", "pay", "post", "execute", "upload", "read", "write"];
const unauthorised = imperatives.filter((verb) => new RegExp(`(?:^|[.!?]\\s+)${verb}\\b`, "i").test(m.description) && !m.contract.verbs[verb]);
return done(unauthorised.length === 0, unauthorised.length ? `unauthorised description imperatives: ${unauthorised.join(", ")}` : "no unauthorised imperative detected");
}
case 13: {
const gaps: string[] = [];
for (const [verb, spec] of verbs) {
const properties = schemaProperties(spec);
for (const raw of spec.args ?? []) {
const arg = raw.replace(/\?$/, "");
if (["id", "user", "data", "input"].includes(arg)) gaps.push(`${verb}.${arg} ambiguous`);
if (!properties[arg]?.description) gaps.push(`${verb}.${arg} missing description`);
}
}
return done(gaps.length === 0, gaps.length ? gaps.slice(0, 8).join("; ") : "all arguments described and specific");
}
case 14: {
const gaps: string[] = [];
for (const [verb, spec] of verbs) for (const [arg, schema] of Object.entries(schemaProperties(spec))) {
const description = String(schema.description ?? "");
if (/(?:one of|allowed|values?)(?:\s+are|:)/i.test(description) && !Array.isArray(schema.enum)) gaps.push(`${verb}.${arg}`);
}
return done(gaps.length === 0, gaps.length ? `enumerated in prose without enum: ${gaps.join(", ")}` : "no prose-only enumerable domains");
}
case 15: {
const cases = m.probe?.near_miss;
if (!cases?.length) return probe("no recorded near-miss key probes");
const expected = expectedNearMisses(m.contract);
const skipped = m.probe?.near_miss_skipped ?? [];
const covered = (item: { verb: string; provided: string; expected: string }) =>
cases.some((c) => c.verb === item.verb && c.provided === item.provided && c.expected === item.expected) ||
skipped.some((c) => c.verb === item.verb && c.provided === item.provided && c.expected === item.expected);
const uncovered = expected.filter((item) => !covered(item));
const bad = cases.filter((item) => !item.refusal.includes(item.expected));
return done(bad.length === 0 && uncovered.length === 0, `${bad.length} refusal(s) omit the correcting key; ${uncovered.length} uncovered; ${skipped.length} case(s) left unfuzzed because the verb has an outside effect`);
}
case 16: {
const known = /^(chat_id|account|cwd|revision|current_revision|home|working_directory)$/;
const bad = verbs.flatMap(([verb, spec]) => (spec.args ?? []).filter((arg) => !arg.endsWith("?") && known.test(arg)).map((arg) => `${verb}.${arg}`));
return done(bad.length === 0, bad.length ? `runner-known required args: ${bad.join(", ")}` : "no runner-known required argument");
}
case 17: {
const collections = readVerbs.filter(([verb, spec]) => isCollectionRead(verb, spec));
const bad = collections.filter(([, spec]) => {
const limit = schemaProperties(spec).limit;
return !limit || typeof limit.default !== "number" || typeof limit.maximum !== "number";
});
return done(bad.length === 0, bad.length ? `limit default/maximum missing: ${bad.map(([verb]) => verb).join(", ")}` : `${collections.length} collection read(s) bounded`);
}
case 18: {
const bad = verbs.filter(([, spec]) => !spec.class);
return done(bad.length === 0, bad.length ? `missing class: ${bad.map(([verb]) => verb).join(", ")}` : `${verbs.length} verb(s) classified`);
}
case 19: {
const bad = verbs.filter(([, spec]) => !annotationsMatch(spec));
return done(bad.length === 0, bad.length ? `annotation defaults mismatch: ${bad.map(([verb]) => verb).join(", ")}` : "annotations match declared classes");
}
case 20: {
const modeArgs = /^(apply|dry_run|commit|execute|write|delete|send|now)\??$/;
const bad = verbs.filter(([, spec]) => (spec.args ?? []).some((arg) => modeArgs.test(arg)) || Object.keys(spec.flags ?? {}).some((arg) => modeArgs.test(arg)));
return done(bad.length === 0, bad.length ? `mode-changing args on: ${bad.map(([verb]) => verb).join(", ")}` : "no read/write mode switch");
}
case 21: {
const enforcedByHint = /if\s*\([^\n]*(?:readOnlyHint|destructiveHint|idempotentHint)/.test(m.api);
return done(!enforcedByHint, enforcedByHint ? "implementation branches stage policy on annotation hint" : "no annotation-driven enforcement branch");
}
case 22: {
const cases = m.probe?.unmet_requirement;
if (!cases?.length) return probe("no unmet-requirement timing probe");
const bad = cases.filter((item) => item.elapsed_ms >= 50 || item.child_processes !== 0);
return done(bad.length === 0, bad.length ? `${bad.length} preflight refusal(s) exceeded 50 ms or spawned` : `${cases.length} refusal(s) under 50 ms with zero children`);
}
case 23: {
if (!probeReads.length) return probe("no recorded answer sizes");
const hard = probeReads.filter((read) => answerTokens(read) > 25000);
const soft = probeReads.filter((read) => (read.limit ?? 5) <= 5 && answerTokens(read) > 4000 && !read.truncated);
return done(hard.length === 0 && soft.length === 0, `${hard.length} hard-cap breach(es); ${soft.length} unannounced default-read soft-cap breach(es)`);
}
case 24: {
if (!probeReads.some((read) => read.limit)) return probe("no limit 1/5/25 answer-size series");
const bad = growthViolations(probeReads);
return done(bad.length === 0, bad.length ? `unbounded growth: ${bad.join(", ")}` : "answer growth is monotone and bounded by limit");
}
case 25: {
const over = probeReads.filter((read) => read.truncated);
if (!over.length) return probe("no recorded over-cap answer");
return done(over.every((read) => Boolean(read.summary && read.cursor)), "truncated answers require both summary and cursor");
}
case 26: {
const over = probeReads.filter((read) => read.truncated);
if (!over.length) return probe("no recorded truncation notice");
return done(over.every((read) => Boolean(read.cursor || read.summary?.match(/limit|filter|narrow/i))), "truncation notices name cursor/filter/limit");
}
case 27: return probeReads.length ? done(!JSON.stringify(probeReads).match(/[0-9a-f]{8}-[0-9a-f-]{27,}/i), "opaque-id scan over recorded answers") : probe("no recorded answer fields");
case 28: return probeReads.length ? done(probeReads.every((read) => read.structuredContent === undefined || (read.outputSchemaValid && (read.content?.length ?? 0) > 0)), "structured outputs validate and retain content") : probe("no structured-output probes");
case 29: return probeReads.length ? done(probeReads.every((read) => read.as_of_age_minutes === undefined || read.as_of_age_minutes <= 60 || read.stale_label), "recorded stale facts are labelled") : probe("no freshness probes");
case 30: {
// THE HAND'S OWN FILE, NOT THE COLLECTION'S ⟨lane loose-ends, 2026-09-09⟩.
// `m.source` follows imports across every skill, so a re-export of a
// marked hand carried that hand's declaration back as if it were this
// one's. `m.ownSource` stops at this hand's directory: the boundary has
// to be stated where the text is handed over.
const vendorRead = readVerbs.length > 0 && (m.contract.requires?.length ?? 0) > 0;
const wrapped = evidenceEnvelopeDeclared(m.ownSource);
return done(!vendorRead || wrapped, vendorRead ? (wrapped ? "vendor text has evidence-envelope semantics in the hand's own source" : "vendor read lacks evidence-envelope semantics in the hand's own source") : "no credentialed vendor read");
}
case 31: {
const refusals = m.probe?.refusals;
if (!refusals?.length) return probe("no refusal corpus");
return done(refusals.every((item) => item.outcome === "refused" && item.code && item.message && item.fix), "refusals schema-checked for outcome/code/message/fix");
}
case 32: {
const refusals = m.probe?.refusals;
if (!refusals?.length) return probe("no refusal corpus");
const literals = [...Object.keys(m.contract.verbs), ...(m.contract.requires ?? []), ...verbs.flatMap(([, spec]) => spec.args ?? []).map((arg) => arg.replace(/\?$/, ""))];
return done(refusals.every((item) => literals.some((literal) => item.message?.includes(literal) || item.fix?.includes(literal))), "refusals checked against contract literals");
}
case 33: {
const table = m.contract.refusals;
if (!table || !Object.keys(table).length) return done(false, "HAND_CONTRACT.refusals missing");
const tests = readTestSource(m.dir);
const uncovered = Object.keys(table).filter((code) => !tests.includes(code));
return done(uncovered.length === 0, `${Object.keys(table).length} refusal code(s); uncovered tests=[${uncovered.join(", ")}]`);
}
case 34: {
const text = JSON.stringify(m.contract.refusals ?? {});
const leaked = /(?:sk-[A-Za-z0-9]{20,}|xox[baprs]-|ghp_|AIza[0-9A-Za-z_-]{20,})/.test(text);
return done(!leaked, leaked ? "token-shaped value in refusal table" : "no token-shaped refusal value");
}
// ONE ENV-READ READER ⟨lane loose-ends, 2026-09-09⟩. The regexes and the
// never-a-credential set used to live here AND in
// snappy-hands/contract-derive.ts, which MINTS the `requires` these two
// rules grade. Two readers of one fact; the deriver counted every
// `process.env.X` as a requirement and these rules counted none, so a hand
// could be minted red and there was no one place to fix it. Both now import
// `snappy-settings/env-reads.ts`.
case 35: {
const required = new Set(m.contract.requires ?? []);
const reads = envReads(m.source);
const missing = [...required].filter((key) => !reads.required.has(key));
const undeclared = [...reads.required].filter((key) => !required.has(key));
return done(missing.length === 0 && undeclared.length === 0, `declared-not-required=[${missing.join(", ")}]; required-not-declared=[${undeclared.join(", ")}]`);
}
case 36: {
const required = new Set(m.contract.requires ?? []);
const optional = [...envReads(m.source).optional].filter((key) => required.has(key));
return done(optional.length === 0, optional.length ? `optional but required: ${optional.join(", ")}` : "optional env reads excluded from requires");
}
case 37: return m.probe?.published_tools === undefined ? probe("no fresh tools/list count") : done(m.probe.published_tools <= 10, `${m.probe.published_tools} published tools`);
case 38: return m.probe?.tools_list_tokens === undefined ? probe("no tools/list token measurement") : done(m.probe.tools_list_tokens <= 2000, `${m.probe.tools_list_tokens} tools/list tokens`);
case 39: {
const endpointMentions = (m.api.match(/https?:\/\/[^\s`"']+/g) ?? []).length;
const mirrors = endpointMentions >= 3 && Math.abs(verbs.length - endpointMentions) <= 1;
return done(!mirrors, `${verbs.length} verbs, ${endpointMentions} literal endpoint(s); one-to-one heuristic=${mirrors}`);
}
case 40: return done(approxTokens(m.description) <= 150 && approxTokens(m.manual) < 5000, `metadata ~${approxTokens(m.description)} tokens; body ~${approxTokens(m.manual)} tokens`);
case 41: return m.probe?.search_ranks?.length ? done(m.probe.search_ranks.every((rank) => rank <= 3), `${m.probe.search_ranks.length} search rank probe(s)`) : probe("no name+job search rank probes");
case 42: {
const kind = m.contract.kind ?? m.frontmatter["instruction-only"];
return done(readVerbs.length > 0 || kind === "true" || kind === "instruction-only" || kind === "write-only", readVerbs.length ? `${readVerbs.length} read verb(s)` : `readless kind=${JSON.stringify(kind)}`);
}
case 43: {
if (!probeReads.length) return probe("no timed read round-trip probes");
const bad = readLatencyViolations(probeReads);
return done(bad.length === 0, bad.length ? bad.join(", ") : `${probeReads.length} read sample(s) are one round trip with p50 under 3 s`);
}
case 44: {
const bands = verbs.filter(([, spec]) => typeof spec.latency === "object");
if (!bands.length || !probeReads.length) return probe("latency bands or timing probes missing");
const violations = bands.flatMap(([verb, spec]) => {
const samples = probeReads.filter((read) => read.verb === verb && typeof read.duration_ms === "number").map((read) => read.duration_ms as number);
if (!samples.length) return [`${verb}:no samples`];
const p50 = median(samples);
return p50 > (spec.latency?.p50Ms ?? Infinity) || Math.max(...samples) > (spec.latency?.maxMs ?? Infinity) ? [`${verb}:band exceeded`] : [];
});
return done(violations.length === 0, violations.length ? violations.join(", ") : `${bands.length} declared latency band(s) met`);
}
case 45: {
const jobReads = readVerbs.filter(([, spec]) => spec.execution !== "call");
const queueTouch = readVerbs.length > 0 && /\b(?:claimTick|enqueueJob|queueJob|pollUntil)\s*\(/.test(m.api);
return done(jobReads.length === 0 && !queueTouch, `${jobReads.length} read(s) not execution=call; queue implementation=${queueTouch}`);
}
case 46: {
if (!probeReads.length) return probe("no face-vs-text answer probes");
const faces = probeReads.filter((read) => read.face);
// A FACE OVER NOTHING. A picture of a two-line answer costs a render and
// adds no interaction, and a hint that names no kind promises a drawing
// nothing can make — I10's shape, caught at the answer instead of the host.
const thin = faces.filter((read) => (read.bytes ?? read.chars ?? 0) < 200);
const kindless = faces.filter((read) => !read.face?.kind);
return done(thin.length === 0 && kindless.length === 0, faces.length
? `${faces.length} face-bearing answer(s); ${thin.length} over a <200 B answer; ${kindless.length} name no kind`
: `${probeReads.length} answer(s), none returned a face`);
}
case 47: {
const faces = probeReads.filter((read) => read.face);
if (!faces.length) return probe("no UI-bearing result probes");
return done(faces.every((read) => read.face?.textFallback && (read.content?.length ?? 0) > 0), "UI results retain standalone text content");
}
case 48: {
const faces = probeReads.filter((read) => read.face);
if (!faces.length) return probe("no UI resource probes");
return done(faces.every((read) => read.face?.resourceUri?.startsWith("ui://") && read.face?.mimeType === "text/html;profile=mcp-app"), "UI URI and MIME checked");
}
case 49: {
const faces = probeReads.filter((read) => read.face);
if (!faces.length) return probe("no channel rendition probes");
return done(faces.every((read) => ["png", "lang", "text"].every((kind) => read.face?.renditions?.includes(kind))), "PNG/lang/text rendition declarations checked");
}
case 50: return renderOutcome(m, "auto_fit");
case 51: return renderOutcome(m, "action_item_bounds");
case 52: return renderOutcome(m, "handshake_race_free");
case 53: return renderOutcome(m, "dated_real_host_screenshot");
case 54: {
const evals = readEvalManifest(m.dir);
const ready = /\*\*Skill Status\*\*:\s*(?:COMPLETE|READY)/i.test(m.manual);
return done(!ready || evals.scenarios.length >= 3, `${evals.scenarios.length} eval scenario(s); ready=${ready}`);
}
case 55: {
const evals = readEvalManifest(m.dir);
if (!evals.scenarios.length) return done(false, "eval manifest missing");
const weak = evals.scenarios.filter((scenario) => !Array.isArray(scenario.calls) || scenario.calls.length < 2 || !scenario.ground_truth);
return done(weak.length === 0, `${weak.length} single-call or ground-truth-free scenario(s)`);
}
case 56: {
const expected = ["accuracy", "runtime", "tool_call_count", "tokens", "errors"];
const metrics = m.probe?.metrics ?? [];
if (!metrics.length) return probe("no probe metric manifest");
const missing = expected.filter((metric) => !metrics.includes(metric));
return done(missing.length === 0, `missing metrics=[${missing.join(", ")}]`);
}
case 57: {
const evals = readEvalManifest(m.dir);
const train = new Set(evals.scenarios.filter((scenario) => scenario.split !== "holdout").map((scenario) => scenario.id));
const holdout = evals.scenarios.filter((scenario) => scenario.split === "holdout").map((scenario) => scenario.id);
return done(holdout.length > 0 && holdout.every((id) => !train.has(id)), `${holdout.length} disjoint holdout scenario(s)`);
}
case 58: return m.probe?.fresh_client === undefined ? probe("no client/deploy handshake evidence") : done(m.probe.fresh_client && m.probe.handshake_after_deploy === true, "fresh client handshake postdates deploy");
case 59: {
const offenders: string[] = [];
for (const [verb, spec] of verbs) {
const args = [...(spec.args ?? [])];
const target = args.findIndex((arg) => COMMONLY_SUPPLIED.test(arg));
if (target <= 0) continue;
const shadow = args.slice(0, target).filter((arg) => arg.endsWith("?"));
if (shadow.length) offenders.push(`${verb}(${args.join(", ")}) — ${shadow.join(", ")} before ${args[target]}`);
}
return done(offenders.length === 0, offenders.length ? offenders.join("; ") : `${verbs.length} verb(s): no optional positional shadows a count`);
}
case 60: {
const block = renderLoaderVerbTable(m.contract);
if (!m.agents.includes("<!-- SNAPPY-CONTRACT-VERBS-START -->")) return done(false, "AGENTS.md carries no generated contract block; run fix-loader");
const present = m.agents.slice(m.agents.indexOf("<!-- SNAPPY-CONTRACT-VERBS-START -->"), m.agents.indexOf("<!-- SNAPPY-CONTRACT-VERBS-END -->") + "<!-- SNAPPY-CONTRACT-VERBS-END -->".length);
return done(present === block, present === block ? `${verbs.length} verb(s) projected with their derived first call` : "generated block is stale against HAND_CONTRACT; run fix-loader");
}
/**
* R61 — THE VENDOR CHANGED AND NOBODY NOTICED.
*
* ⟨owner, 2026-09-09 16:5x⟩ "wouldn't it be good for the skills to check
* themselves against OpenAPI? GPT Image 2.5 got released — the OpenAPI
* spec could be lined up against api.ts, and if there's a difference it's
* programmatically noticed and then updated."
*
* The table already knew "a verb it promises and does not have" (R7) and
* "a loader whose first call is out of date" (R60). Both compare this
* repository to itself. R61 is the first rule that compares it to the
* WORLD, and the world is the only party in this system that changes
* without a commit.
*
* FIVE ARMS, and which one runs is decided by what the hand declared:
*
* no `spec` → DEFER. Not a FAIL: 97 of 98 hands name no spec today, and
* a rule that fails all of them on day one is 97 rows of
* debt with a receipt — the ratchet this repo bans. DEFER is
* the honest word for "nobody has looked yet", and it is the
* same word R15 and R50 use for evidence that does not exist.
* `none` → the hand says the world publishes nothing to check it
* against. PASS with its reason; FAIL without one, because
* "unmapped on purpose" and "forgot" must not look alike.
* `mini` → ⟨owner 17:0x⟩ "Libretto, iMessage has no API, WhatsApp only
* has a business API — that's why we have the mini with full
* computer capability to fill the gaps." Its check is its own
* recorded read replaying (watch-me-once), never a vendor
* document, so this rule asks only for the reason and stands
* aside.
* `docs` → prose only, so there is nothing to diff. The weaker check:
* a url, a reason, and a `pinned.checked_at` that is not
* ancient.
* `openapi` / `discovery` → the real rule, over the committed corpus.
*
* The corpus is EVIDENCE and its absence is DEFER, never PASS — a rule
* that vouched for a hand nobody had checked would be a status truer than
* its artifact ⟨CLAUDE.md §10⟩.
*/
case 61: {
const spec = m.contract.spec;
if (!spec) return outcome("DEFER", "names no vendor spec; add HAND_CONTRACT.spec (kind openapi | discovery | docs | mini | none)");
const reason = typeof spec.reason === "string" ? spec.reason.trim() : "";
if (spec.kind === "none") return done(reason.length > 0, reason ? `unmapped on purpose: ${reason}` : 'kind "none" gives no reason, so "nobody publishes one" cannot be told from "nobody has looked"');
if (spec.kind === "mini") return done(reason.length > 0, reason ? `filled on the mini, checked by its own recorded read: ${reason}` : 'kind "mini" gives no reason naming the recorded read or computer-use road that stands in for a vendor document');
if (spec.kind === "docs") {
if (!spec.url || !reason) return done(false, 'kind "docs" needs both a url and one sentence saying why the vendor publishes no machine-readable spec');
const age = spec.pinned?.checked_at ? Math.floor((Date.now() - Date.parse(spec.pinned.checked_at)) / 86_400_000) : null;
return done(true, `docs only at ${spec.url}${age === null ? " (never checked by a person)" : `, read by a person ${age}d ago`} — ${reason}`);
}
if (!m.vendor) return outcome("DEFER", `pins ${spec.kind} ${spec.url ?? "(no url)"} but ${SPEC_FILE} holds no reading of it; run \`snappy-specwatch check ${m.name}\``);
const drift = driftOf(spec, m.vendor, [m.ownSource, m.manual, m.agents, JSON.stringify(m.contract)].join("\n"));
const moved = drift.moved ? ` · ${vendorOf(spec.url ?? "")} moved from ${drift.moved.from} to ${drift.moved.to} since ${drift.checkedAt}` : "";
const sentence = driftSentence(vendorOf(spec.url ?? ""), drift);
if (drift.gone.length || drift.enums.length) return done(false, `${sentence}${moved}`);
// THE STORE CARD'S TRUST LINE comes out of this same detail: how many of
// the vendor's operations this hand actually covers, over how many it
// publishes, as of when.
return done(true, `covers ${Object.keys(m.vendor.operations).length} of ${m.vendor.operations_total} operation(s) in ${vendorOf(spec.url ?? "")} ${m.vendor.family} ${m.vendor.version}, read ${m.vendor.fetched_at.slice(0, 10)}${moved}`);
}
default: return outcome("DEFER", "rule implementation missing");
}
}
function outcome(status: RuleStatus, detail: string) {
return { status, pass: status === "PASS", detail };
}
function effectClass(spec: HandVerb): EffectClass | undefined {
if (spec.class) return spec.class;
if (spec.effect === "read") return "read";
return undefined;
}
function expectedAnnotations(spec: HandVerb): Record<string, boolean> | null {
const cls = spec.class;
if (!cls) return null;
return {
readOnlyHint: cls === "read",
destructiveHint: cls === "destructive" || cls === "spend" || cls === "send-to-a-person",
idempotentHint: cls !== "read" && spec.idempotent === true,
openWorldHint: spec.openWorld ?? true,
};
}
function annotationsMatch(spec: HandVerb): boolean {
const expected = expectedAnnotations(spec);
if (!expected || !spec.annotations) return false;
return Object.entries(expected).every(([key, value]) => spec.annotations?.[key] === value);
}
function schemaProperties(spec: HandVerb): Record<string, Record<string, unknown>> {
const schema = spec.inputSchema as { properties?: Record<string, Record<string, unknown>> } | undefined;
return schema?.properties ?? {};
}
/** WHOSE ANSWER IS A LIST — READ OFF THE VERB'S GRAMMAR, NEVER ITS SPELLING.
*
* WHY IT CHANGED ⟨lane r17-2, 2026-09-09⟩. This test used to match the
* SUBSTRING "read" / "logs" / "all" / "events" in a verb's NAME, and a name is
* not a shape. It accused four verbs that answer exactly ONE record —
* `snappy-email read` (one message), `snappy-mastermind-model read` (one
* topic), `snappy-statechange unread` (Circle's counters, which the arm itself
* publishes as `count: 0`) and `snappy-xano-dashboard logs` (a string) — of
* being unbounded collections. Each could only have answered the charge by
* declaring a `limit` that nothing on its road could honour, which is the
* defect this lint exists to catch, committed by the lint itself. And while it
* was guessing at letters it never looked at SIXTEEN verbs that do answer
* lists, because their names carry none of those letters: `freshbooks clients`,
* `github repos`, `imessage contacts`, `linkedin comments`, `update commits`,
* `krisp fetch-meetings` and ten more.
*
* THE GRAMMAR IS THE SHAPE. A verb that admits a COUNT — a `limit` positional,
* a `--limit` flag, or a `limit` property in its inputSchema — is the hand's
* own statement that its answer carries a variable number of rows. Whether it
* then declares that bound COMPLETELY (default AND maximum, through the one
* road in `snappy-settings/read-limit.ts`) is exactly what this rule grades.
* The count vocabulary is `COMMONLY_SUPPLIED`, the SAME one rule 59 reads —
* one vocabulary, two rules ⟨CLAUDE.md R4, duplicate roads⟩.
*
* WHAT THIS RULE NO LONGER PRETENDS TO KNOW is whether a verb that names no
* count OUGHT to. Nothing static can see that, and a name never could. A verb
* that hands back a hundred rows while admitting no count is caught where the
* answer is — rules 23 and 24, graded from a RECORDED read rather than from a
* guess about letters ⟨CLAUDE.md R10: a status is only as true as the artifact
* it implies⟩.
*
* MEASURED 2026-09-09 across the 98-hand collection: the substring test graded
* 57 verbs, this one grades 69, and the four single-record verbs are in
* neither. The population GREW; nothing was hidden to make the rule green. */
function isCollectionRead(verb: string, spec: HandVerb): boolean {
void verb;
if ((spec.args ?? []).some((arg) => COMMONLY_SUPPLIED.test(arg))) return true;
if (Object.keys(spec.flags ?? {}).some((flag) => COMMONLY_SUPPLIED.test(flag))) return true;
return Object.keys(schemaProperties(spec)).some((key) => COMMONLY_SUPPLIED.test(key));
}
function parseFrontmatter(markdown: string): Record<string, string> {
const match = markdown.match(/^---\n([\s\S]*?)\n---/);
if (!match) return {};
const result: Record<string, string> = {};
const lines = match[1].split("\n");
for (let index = 0; index < lines.length; index++) {
const key = lines[index].match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
if (!key) continue;
let value = key[2].trim();
if (value === ">" || value === "|") {
const body: string[] = [];
while (index + 1 < lines.length && /^\s+/.test(lines[index + 1])) body.push(lines[++index].trim());
value = body.join(" ");
}
result[key[1]] = unquote(value);
}
return result;
}
function unquote(value: string): string {
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
return value;
}
/** RULE 6, AS A RULE ⟨lane requires-scope, 2026-09-09⟩. The contract is the one
* source of what a hand says it is; SKILL.md's frontmatter is a COPY, and a
* copy that drifts is the whole defect — the loader that picks the hand reads
* the frontmatter, the caller that validates it reads the contract, and they
* describe two different tools. Pure, and graded off two strings, so the FAIL
* arm has a test that does not depend on some live hand still being wrong: the
* test that used to hold it asserted snappy-gmail FAILS, and went green-by-
* looking-away the day gmail's description was synced. */
export function descriptionSync(contractDescription: string | undefined, frontmatter: string): { pass: boolean; detail: string } {
const expected = normalizeSpace(contractDescription ?? "");
const actual = normalizeSpace(frontmatter);
if (!expected) return { pass: false, detail: "HAND_CONTRACT.description missing" };
return { pass: expected === actual, detail: `description ${expected === actual ? "matches" : "differs from"} contract source` };
}
function normalizeSpace(value: string): string {
return value.replace(/\s+/g, " ").trim();
}
function normalizeSkill(input: string): string {
const name = input.split("/").filter(Boolean).at(-1) ?? "";
if (!/^snappy-[a-z0-9-]+$/.test(name)) throw new Error(`${JSON.stringify(input)} is not a snappy-* skill name`);
if (!existsSync(join(SKILLS_ROOT, name))) throw new Error(`${name}: skill directory not found`);
return name;
}
function readRequired(path: string, message: string): string {
if (!existsSync(path)) throw new Error(message);
return readFileSync(path, "utf8");
}
function readJson<T>(path: string): T | null {
if (!existsSync(path)) return null;
try { return JSON.parse(readFileSync(path, "utf8")) as T; }
catch { return null; }
}
/** THE ONE WALK, AND WHY IT IS NOT HERE ⟨lane requires-scope, 2026-09-09⟩.
* `collectLocalSource` and `collectOwnSource` used to live in this file, and
* rules 35/36 were their only caller — while `snappy-hands/contract-derive.ts`,
* which MINTS the `requires` those rules grade, read a hand's own api.ts alone.
* One word, two scopes: the grader walked into snappy-email and the deriver did
* not, so 23 of 98 hands would have been minted short. Both roads now call the
* same walk in `snappy-settings/env-reads.ts` — the module that already owns
* what a requirement IS now also owns which text it is read out of. Re-exported
* here because r30-scope.test.ts names this file as the rule's owner. */
export { collectOwnSource, evidenceEnvelopeDeclared };
function extractPromisedVerbs(markdown: string, currentSkill: string): Set<string> {
const verbs = new Set<string>();
for (const line of markdown.split("\n")) {
const call = line.match(/(?:npx\s+tsx|node|bun)\s+([^\s`]*api\.ts)\s+([A-Za-z][A-Za-z0-9_-]*)/);
if (call) {
const named = call[1].match(/skills\/(snappy-[a-z0-9-]+)\/api\.ts/)?.[1];
if (!named || named === currentSkill) verbs.add(call[2]);
}
for (const match of line.matchAll(/(?:verb|command)\s+`([a-z][a-z0-9_-]*)`/gi)) verbs.add(match[1]);
}
return verbs;
}
function descriptionOverlap(a: string, b: string): number {
const stop = new Set(["the", "and", "for", "with", "when", "use", "from", "that", "this", "skill", "snappy"]);
const words = (text: string) => new Set(text.toLowerCase().match(/[a-z][a-z0-9-]{2,}/g)?.filter((word) => !stop.has(word)) ?? []);
const aa = words(a); const bb = words(b);
return [...aa].filter((word) => bb.has(word)).length;
}
function approxTokens(text: string): number {
return Math.ceil(text.length / 4);
}
function stripOldPatterns(markdown: string): string {
return markdown.replace(/^## Old patterns\b[\s\S]*?(?=^## |\z)/gim, "");
}
function referenceProblems(m: SkillModel): string[] {
const problems: string[] = [];
for (const match of m.manual.matchAll(/\[[^\]]+]\(([^)#]+\.md)(?:#[^)]+)?\)/g)) {
if (match[1].includes("../") || match[1].split("/").length > 2) problems.push(`deep reference ${match[1]}`);
const path = resolve(m.dir, match[1]);
if (existsSync(path) && statSync(path).isFile()) {
const text = readFileSync(path, "utf8");
if (text.split("\n").length > 100 && !/(?:^|\n)## (?:Contents|Table of contents)/i.test(text.slice(0, 3000))) problems.push(`${match[1]} >100 lines without ToC`);
}
}
return problems;
}
function growthViolations(reads: ProbeRead[]): string[] {
const byVerb = new Map<string, ProbeRead[]>();
for (const read of reads.filter((item) => item.limit && item.chars)) byVerb.set(read.verb, [...(byVerb.get(read.verb) ?? []), read]);
const bad: string[] = [];
for (const [verb, series] of byVerb) {
series.sort((a, b) => (a.limit ?? 0) - (b.limit ?? 0));
for (let index = 1; index < series.length; index++) if ((series[index].chars ?? 0) < (series[index - 1].chars ?? 0)) bad.push(`${verb}:non-monotone`);
const one = series.find((item) => item.limit === 1)?.chars;
const five = series.find((item) => item.limit === 5)?.chars;
if (one && five && five > one * 4) bad.push(`${verb}:1→5 over 4x`);
}
return bad;
}
function expectedNearMisses(contract: HandContract): Array<{ verb: string; provided: string; expected: string }> {
const pairs: Array<[string, string]> = [
["body", "text"], ["text", "body"], ["recipient", "to"], ["to", "recipient"],
["msg", "message"], ["message", "msg"], ["count", "limit"], ["limit", "count"],
];
const cases: Array<{ verb: string; provided: string; expected: string }> = [];
for (const [verb, spec] of Object.entries(contract.verbs)) {
const args = new Set((spec.args ?? []).map((arg) => arg.replace(/\?$/, "")));
for (const [provided, expected] of pairs) if (args.has(expected) && !args.has(provided)) cases.push({ verb, provided, expected });
}
return cases;
}
function answerTokens(read: ProbeRead): number {
return read.tokens ?? Math.ceil((read.chars ?? 0) / 4);
}
function readTestSource(dir: string): string {
return readdirSync(dir, { withFileTypes: true })
.filter((entry) => entry.isFile() && /(?:test|spec)\.(?:ts|js|mjs|json)$/.test(entry.name))
.map((entry) => readFileSync(join(dir, entry.name), "utf8"))
.join("\n");
}
function readLatencyViolations(reads: ProbeRead[]): string[] {
const byVerb = new Map<string, ProbeRead[]>();
for (const read of reads) byVerb.set(read.verb, [...(byVerb.get(read.verb) ?? []), read]);
const violations: string[] = [];
for (const [verb, samples] of byVerb) {
const durations = samples.flatMap((sample) => typeof sample.duration_ms === "number" ? [sample.duration_ms] : []);
if (!durations.length || median(durations) > 3000) violations.push(`${verb}:p50>3s-or-missing`);
if (samples.some((sample) => sample.request_count !== 1)) violations.push(`${verb}:request-count!=1`);
}
return violations;
}
function median(values: number[]): number {
const sorted = [...values].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
}
/**
* Rules 50–53 quantify over face FAMILIES, and a skill with none was PASSing
* them on nothing ⟨fixed 2026-09-09, lane render-vacuous⟩.
*
* The old arm answered PASS with the words "…is vacuous", on the reading that
* an empty set satisfies "every". Formally true, operationally a lie: 94 of 98
* skills took that arm — because `FAMILY_OWNERS` named two families — so four
* rules read 98 PASS / 0 FAIL, were pinned on the zero floor for it, and
* vouched for skills no host had ever drawn. A PASS is a claim that something
* was measured ⟨CLAUDE.md §10: a status is only as true as the artifact it
* implies⟩, and the artifact here — a browser row for a family this skill
* owns — does not exist. So it DEFERs, exactly like a skill the run never
* reached. Zero stays zero because it is measured, not because nothing looked.
*/
function renderOutcome(m: SkillModel, key: string) {
if (!m.renderProbe) return outcome("DEFER", "no real-host render probe");
if (!m.renderProbe.families.length) return outcome("DEFER", `no face family is owned by this skill; ${key} was never measured`);
const value = m.renderProbe.values[key];
return outcome(value === true ? "PASS" : "FAIL", `${key}=${JSON.stringify(value)} over famil(ies) ${m.renderProbe.families.join(", ")}`);
}
function readEvalManifest(dir: string): { scenarios: Array<{ id: string; split?: string; calls?: unknown[]; ground_truth?: unknown }> } {
return readJson(join(dir, "evals.json")) ?? { scenarios: [] };
}
const CONTRACT_DESCRIPTION = "Contract-first ergonomics lint for AI-operated skills, unlike snappy-artifact-loop which manages implementation feedback. Use when auditing tool descriptions, verbs, arguments, effects, refusals, response bounds, progressive disclosure, latency, faces, the derived first call, or eval evidence against the sourced 61-rule research table. Triggers on: snappy-tool-design, tool design, lint a skill, tool ergonomics, verb table, derived first call, AGENTS.md drift, refusal shape, response bounds.";
export const HAND_CONTRACT = {
skill: "snappy-tool-design",
description: CONTRACT_DESCRIPTION,
kind: "tool",
managed: false,
requires: [] as string[],
refusals: refusalTable("unknown_verb", "missing_argument", "missing_credential"),
verbs: {
lint: {
args: ["skill?"], effect: "read", class: "read", execution: "call", openWorld: false,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
flags: { all: "--all", summary: "--summary", limit: "--limit" },
inputSchema: { properties: { skill: { type: "string", description: "Optional snappy-* directory name; omit with --all" }, limit: { type: "integer", description: "Maximum skill rows printed in the collection table", default: 100, maximum: 500 } } },
},
example: {
args: ["skill", "verb"], effect: "read", class: "read", execution: "call", openWorld: false,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
inputSchema: { properties: { skill: { type: "string", description: "Snappy skill directory name" }, verb: { type: "string", description: "Exact verb key from HAND_CONTRACT.verbs" } } },
},
probe: {
args: ["skill?"], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { via: "--via", out: "--out", "token-file": "--token-file" },
inputSchema: { properties: { skill: { type: "string", description: "One snappy-* hand to probe; omit for the whole collection" }, via: { type: "string", description: "Which MCP road to drive", enum: ["mini", "public"], default: "mini" }, out: { type: "string", description: "Where to write the probe corpus" } } },
},
render: {
args: [] as string[], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { out: "--out", "host-dir": "--host-dir", "shot-dir": "--shot-dir", kind: "--kind" },
inputSchema: { properties: { out: { type: "string", description: "Where to write the render corpus" }, kind: { type: "string", description: "Comma-separated face kinds to draw instead of the whole bundle; needs --out so a partial run cannot overwrite the collection's render file" } } },
},
loop: {
args: [] as string[], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { via: "--via", out: "--out", "skip-probe": "--skip-probe", "skip-render": "--skip-render" },
inputSchema: { properties: { out: { type: "string", description: "Directory that receives LEDGER.md and briefs/" } } },
},
hosts: {
args: [] as string[], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { out: "--out", "token-file": "--token-file", url: "--url", only: "--only", "codex-model": "--codex-model" },
inputSchema: { properties: { out: { type: "string", description: "Directory that receives tool-design-hosts.json, HOSTS-LEDGER.md and briefs/" }, only: { type: "string", description: "Comma-separated host ids to attempt; the rest record not-driven with a reason", enum: ["chatgpt", "claude", "codex", "menubar", "telegram"] } } },
},
specs: {
args: ["skill?"], effect: "write-reversible", class: "additive-write", execution: "call", idempotent: true, openWorld: true,
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
inputSchema: { properties: { skill: { type: "string", description: "One snappy-* hand whose pinned vendor spec is re-read; omit for every hand that pins one" } } },
},
openapi: {
args: ["skill"], effect: "read", class: "read", execution: "call", openWorld: false,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
inputSchema: { properties: { skill: { type: "string", description: "Snappy skill whose HAND_CONTRACT is emitted as an OpenAPI 3.1 document" } } },
},
"fix-loader": {
args: ["skill"], effect: "write-reversible", class: "additive-write", execution: "call", idempotent: true, openWorld: false,
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
inputSchema: { properties: { skill: { type: "string", description: "Snappy skill whose AGENTS.md contract block is regenerated" } } },
},
},
} as const satisfies HandContract & { managed: boolean };
async function main(): Promise<void> {
const [, , command, ...args] = process.argv;
const summary = args.includes("--summary");
const limitAt = args.indexOf("--limit");
const limit = limitAt >= 0 ? Number(args[limitAt + 1]) : 100;
try {
if (!command || command === "help") {
console.log("Usage:");
console.log(" npx tsx api.ts lint <skill>");
console.log(" npx tsx api.ts lint --all [--summary] [--limit N]");
console.log(" npx tsx api.ts example <skill> <verb>");
console.log(" npx tsx api.ts probe [<skill>] [--via mini|public] [--out FILE]");
console.log(" npx tsx api.ts render [--out FILE] [--host-dir DIR] [--shot-dir DIR] [--kind k1,k2]");
console.log(" npx tsx api.ts loop [--via mini|public] [--out DIR] [--skip-probe] [--skip-render]");
console.log(" npx tsx api.ts hosts [--out DIR] [--only chatgpt,claude,codex,menubar,telegram] [--url URL] [--token-file FILE]");
console.log(" npx tsx api.ts specs [<skill>]");
console.log(" npx tsx api.ts openapi <skill>");
console.log(" npx tsx api.ts fix-loader <skill>");
return;
}
if (command === "contract") { console.log(JSON.stringify(HAND_CONTRACT, null, 2)); return; }
if (command === "lint") {
if (args.includes("--all")) printAll(await lintAll(), summary, Number.isFinite(limit) ? limit : 100);
else {
const skill = args.find((arg, index) => !arg.startsWith("--") && args[index - 1] !== "--limit");
if (!skill) throw new RefusedError("missing_argument", "lint needs <skill> or --all; contract slice lint.args = [skill?]");
printOne(await lintSkill(skill), summary);
}
return;
}
if (command === "example") {
if (!args[0] || !args[1]) throw new RefusedError("missing_argument", "example needs <skill> <verb>; contract slice example.args = [skill, verb]");
console.log(await firstExample(args[0], args[1]));
const hazard = await exampleHazard(args[0], args[1]);
if (hazard) console.error(hazard);
return;
}
if (command === "hosts") {
const { runHostsCommand } = await import("./loop.ts");
await runHostsCommand(args, { skillsRoot: SKILLS_ROOT });
return;
}
if (command === "probe" || command === "render" || command === "loop") {
const { runLoopCommand } = await import("./loop.ts");
await runLoopCommand(command, args, { skillsRoot: SKILLS_ROOT, lintAll, printAll });
return;
}
if (command === "specs") {
const skill = args.find((arg) => !arg.startsWith("--"));
const answer = await readSpecs(skill);
console.log(`read ${answer.read.length} vendor spec(s): ${answer.read.join(", ") || "none"}`);
for (const row of answer.skipped) console.log(` skipped ${row.skill}: ${row.why}`);
console.log(`wrote ${answer.written}`);
return;
}
if (command === "openapi") {
if (!args[0]) throw new RefusedError("missing_argument", "openapi needs <skill>; contract slice openapi.args = [skill]");
console.log(JSON.stringify(await handOpenApi(args[0]), null, 2));
return;
}
if (command === "fix-loader") {
if (!args[0]) throw new RefusedError("missing_argument", "fix-loader needs <skill>; contract slice fix-loader.args = [skill]");
const fixed = await fixLoader(args[0]);
console.log(`${fixed.changed ? "updated" : "unchanged"}: ${fixed.path} (${fixed.verbs} verbs)`); return;
}
throw new RefusedError("unknown_verb", `unknown verb ${JSON.stringify(command)}; contract slice verbs = [lint, example, probe, render, loop, hosts, specs, openapi, fix-loader]`);
} catch (error) {
// THE ONE PLACE THIS HAND SAYS NO. A refusal prints the closed table's
// shape on stdout and exits 1; anything else is an internal error and
// stays one, because a crash dressed as a governed refusal is the lie
// the table exists to prevent.
if (isRefusedError(error)) {
printRefusal(error.refusal);
console.error(error.message);
return;
}
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
}
if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) void main();
#!/usr/bin/env npx tsx
/**
* snappy-tool-design: sourced, mechanical ergonomics lint for AI-operated tools.
* The reader's failure modes are the specification. Rules and source IDs are
* rebased from RESEARCH.md; probe/render checks defer until evidence exists.
*/
import {
existsSync,
readFileSync,
readdirSync,
realpathSync,
statSync,
writeFileSync,
} from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { pooledAll } from "../snappy-settings/spawn-pool.ts";
import { RefusedError, isRefusedError, printRefusal, refusalTable } from "../snappy-settings/refusal-codes.ts";
import { collectLocalSource, collectOwnSource, envReads, evidenceEnvelopeDeclared } from "../snappy-settings/env-reads.ts";
import { printAll, printOne } from "./report.ts";
import {
SPEC_FILE,
driftOf,
driftSentence,
readSpecFile,
specFor,
writeSpecFile,
vendorOf,
type SpecCorpus,
} from "./spec-corpus.ts";
import type { SpecProjection } from "../snappy-settings/spec-read.ts";
import {
PROBE_FILE,
RENDER_FILE,
probeFor,
readProbeFile,
readRenderFile,
faceKindsDrawn,
renderFor,
type ProbeCorpus,
type ProbeRead,
type RenderFile,
} from "./corpus.ts";
import {
contractExample,
renderLoaderVerbTable,
syncLoaderVerbTable,
type HandContract,
type HandVerb,
} from "../snappy-skill/api.ts";
const HERE = dirname(realpathSync(fileURLToPath(import.meta.url)));
const SKILLS_ROOT = dirname(HERE);
type RuleMode = "static" | "probe" | "render" | "spec";
type RuleStatus = "PASS" | "FAIL" | "DEFER";
type EffectClass = NonNullable<HandVerb["class"]>;
export interface RuleDefinition {
id: number;
group: string;
rule: string;
sources: string[];
mode: RuleMode;
}
export interface RuleResult extends RuleDefinition {
status: RuleStatus;
pass: boolean;
detail: string;
}
export interface LintResult {
skill: string;
pass: number;
fail: number;
defer: number;
rules: RuleResult[];
}
interface SkillModel {
name: string;
dir: string;
contract: HandContract;
api: string;
source: string;
/** The hand's OWN api.ts and its own local modules — never another skill's. */
ownSource: string;
agents: string;
manual: string;
description: string;
frontmatter: Record<string, string>;
probe: ProbeCorpus | null;
renderProbe: { values: Record<string, boolean>; families: string[] } | null;
/** The vendor document this hand pinned, as last read. Null when the hand
* names no machine-readable spec, or when no corpus has been written. */
vendor: SpecProjection | null;
}
export const RULES: RuleDefinition[] = [
{ id: 1, group: "B1 Naming", rule: "Skill name satisfies the open spec and equals its directory", sources: ["S7"], mode: "static" },
{ id: 2, group: "B1 Naming", rule: "Verb identity is namespaced by hand and collisions keep one grammar", sources: ["S1"], mode: "static" },
{ id: 3, group: "B1 Naming", rule: "Vague generic skill names are banned", sources: ["S6"], mode: "static" },
{ id: 4, group: "B1 Naming", rule: "Near-neighbour descriptions cross-reference each other", sources: ["S12"], mode: "static" },
{ id: 5, group: "B2 Description", rule: "Description is valid, third-person, and says what plus when", sources: ["S5", "S6", "S7", "S21"], mode: "static" },
{ id: 6, group: "B2 Description", rule: "Description is contract-derived with zero manual delta", sources: ["CLAUDE.md R4", "S18"], mode: "static" },
{ id: 7, group: "B2 Description", rule: "Promised and declared verbs have no set difference", sources: ["CLAUDE.md R4"], mode: "static" },
{ id: 8, group: "B2 Description", rule: "Skill body and reference graph stay within disclosure budgets", sources: ["S5", "S6", "S7"], mode: "static" },
{ id: 9, group: "B2 Description", rule: "Time-sensitive prose is isolated under Old patterns", sources: ["S6"], mode: "static" },
{ id: 10, group: "B2 Description", rule: "One term names each concept", sources: ["S6"], mode: "static" },
{ id: 11, group: "B2 Description", rule: "Each task presents one default, not a menu", sources: ["S6"], mode: "static" },
{ id: 12, group: "B2 Description", rule: "Description contains no unauthorised imperative", sources: ["S18"], mode: "static" },
{ id: 13, group: "B3 Arguments", rule: "Every argument is described and unambiguous", sources: ["S1", "S11"], mode: "static" },
{ id: 14, group: "B3 Arguments", rule: "Enumerable domains use enums", sources: ["S2", "S11"], mode: "static" },
{ id: 15, group: "B3 Arguments", rule: "Near-miss keys refuse with the correct contract key", sources: ["S6", "S19 [snippet]", "S20 [snippet]"], mode: "probe" },
{ id: 16, group: "B3 Arguments", rule: "Runner-known values are not required arguments", sources: ["S11"], mode: "static" },
{ id: 17, group: "B3 Arguments", rule: "Collection reads declare limit default and maximum", sources: ["S1"], mode: "static" },
{ id: 18, group: "B4 Effects", rule: "Every verb declares a side-effect class", sources: ["S12", "CLAUDE.md R6"], mode: "static" },
{ id: 19, group: "B4 Effects", rule: "Published annotations are derived from the effect class", sources: ["S9"], mode: "static" },
{ id: 20, group: "B4 Effects", rule: "Read and write are separate verbs", sources: ["S12"], mode: "static" },
{ id: 21, group: "B4 Effects", rule: "Annotations never enforce stage policy", sources: ["S8", "S9"], mode: "static" },
{ id: 22, group: "B4 Effects", rule: "Unmet requirements refuse before spawn in under 50 ms", sources: ["S14"], mode: "probe" },
{ id: 23, group: "B5 Response", rule: "Responses obey hard and soft caps", sources: ["S1"], mode: "probe" },
{ id: 24, group: "B5 Response", rule: "Answer size is bounded by requested limit", sources: ["S1"], mode: "probe" },
{ id: 25, group: "B5 Response", rule: "Over-cap answers summarize and provide a cursor", sources: ["S14"], mode: "probe" },
{ id: 26, group: "B5 Response", rule: "Truncation names the actionable next move", sources: ["S1"], mode: "probe" },
{ id: 27, group: "B5 Response", rule: "Default density returns names rather than opaque IDs", sources: ["S1"], mode: "probe" },
{ id: 28, group: "B5 Response", rule: "Structured output validates and retains text compatibility", sources: ["S8"], mode: "probe" },
{ id: 29, group: "B5 Response", rule: "Facts carry source timestamps and stale labels", sources: ["CLAUDE.md R10"], mode: "probe" },
{ id: 30, group: "B5 Response", rule: "Third-party text is wrapped as data, not instructions", sources: ["S18"], mode: "static" },
{ id: 31, group: "B6 Refusal", rule: "Refusals have outcome, code, message, and fix", sources: ["S8", "S19 [snippet]", "S20 [snippet]"], mode: "probe" },
{ id: 32, group: "B6 Refusal", rule: "Refusals name a literal from the valid domain", sources: ["S1", "S6"], mode: "probe" },
{ id: 33, group: "B6 Refusal", rule: "Refusal codes form one tested closed table", sources: ["S1", "S19 [snippet]"], mode: "static" },
{ id: 34, group: "B6 Refusal", rule: "Refusals never contain secret values", sources: ["CLAUDE.md"], mode: "static" },
{ id: 35, group: "B6 Refusal", rule: "Requirements are declared from executable credential reads", sources: ["S6", "CLAUDE.md R4"], mode: "static" },
{ id: 36, group: "B6 Refusal", rule: "Optional credentials do not become requirements", sources: ["I6 measured"], mode: "static" },
{ id: 37, group: "B7 Disclosure", rule: "Published MCP catalog stays at ten tools or fewer", sources: ["S11", "S15", "S16", "S17"], mode: "probe" },
{ id: 38, group: "B7 Disclosure", rule: "Tools-list token cost fits the catalog budget", sources: ["S17"], mode: "probe" },
{ id: 39, group: "B7 Disclosure", rule: "Verb sets consolidate intent rather than mirror endpoints", sources: ["S1", "S17"], mode: "static" },
{ id: 40, group: "B7 Disclosure", rule: "Metadata, skill body, and resources follow three-level budgets", sources: ["S3", "S4", "S5", "S7"], mode: "static" },
{ id: 41, group: "B7 Disclosure", rule: "Search ranks each hand in the top three", sources: ["S15", "S16"], mode: "probe" },
{ id: 42, group: "B7 Disclosure", rule: "Readless hands declare instruction-only or write-only kind", sources: ["S12"], mode: "static" },
{ id: 43, group: "B8 Latency", rule: "A read is one call, not a queued job", sources: ["S14", "owner 18:4x"], mode: "probe" },
{ id: 44, group: "B8 Latency", rule: "Each verb publishes and meets an expected latency band", sources: ["S1"], mode: "probe" },
{ id: 45, group: "B8 Latency", rule: "Long work is a separately named job verb", sources: ["S2"], mode: "static" },
{ id: 46, group: "B9 UI", rule: "A face exists only when visual interaction improves work", sources: ["S10", "S13"], mode: "probe" },
{ id: 47, group: "B9 UI", rule: "UI-bearing results retain standalone text", sources: ["S10"], mode: "probe" },
{ id: 48, group: "B9 UI", rule: "UI resources are preregistered with exact URI and MIME", sources: ["S10"], mode: "probe" },
{ id: 49, group: "B9 UI", rule: "Channel chooses PNG, OpenUI program, or text", sources: ["S10", "owner 20:5x"], mode: "probe" },
{ id: 50, group: "B9 UI", rule: "Faces shrink and grow without nested scrolling", sources: ["S13"], mode: "render" },
{ id: 51, group: "B9 UI", rule: "Cards have at most two actions and carousels three to eight items", sources: ["S13"], mode: "render" },
{ id: 52, group: "B9 UI", rule: "Host handshake listens before host delivery", sources: ["I11 measured"], mode: "render" },
{ id: 53, group: "B9 UI", rule: "Every face family has a dated real-host screenshot", sources: ["CLAUDE.md R10"], mode: "render" },
{ id: 54, group: "B10 Evals", rule: "At least three eval scenarios precede ready status", sources: ["S6"], mode: "static" },
{ id: 55, group: "B10 Evals", rule: "Eval scenarios are multi-call with ground truth", sources: ["S1"], mode: "static" },
{ id: 56, group: "B10 Evals", rule: "Probe runs record accuracy, runtime, calls, tokens, errors", sources: ["S1"], mode: "probe" },
{ id: 57, group: "B10 Evals", rule: "Eval manifest contains a disjoint holdout", sources: ["S1"], mode: "static" },
{ id: 58, group: "B10 Evals", rule: "Proof comes from a fresh post-deploy client", sources: ["CLAUDE.md R7"], mode: "probe" },
{ id: 59, group: "B11 MCP projection", rule: "No optional positional precedes a commonly-supplied argument", sources: ["S11", "S1", "I14 measured"], mode: "static" },
{ id: 60, group: "B11 MCP projection", rule: "Loader publishes the contract's derived first call, current", sources: ["S3", "S4", "S8", "CLAUDE.md R4"], mode: "static" },
{ id: 61, group: "B12 Vendor spec", rule: "The vendor's own spec still has what this hand offers, and this hand offers what the vendor added", sources: ["S22", "S23", "owner 16:5x"], mode: "spec" },
];
/** THE ARGUMENTS AN AI SUPPLIES WITHOUT BEING ASKED. "the last 5 messages" is
* a count in every language a person uses, so the model writes the count and
* nothing else. Measured 2026-09-08 (I14): three loaders taught a refusal —
* `snappy-imessage recent`, `snappy-krisp fetch-meetings` and
* `snappy-thumbnails audit` each put an OPTIONAL positional in front of the
* count, so `recent 5` filed 5 as the contact and answered wrong or empty. */
const COMMONLY_SUPPLIED = /^(limit|count|n|top|max|size|per-?page|first|take)\??$/i;
const contractCache = new Map<string, Promise<HandContract>>();
export async function loadContract(skill: string): Promise<HandContract> {
const name = normalizeSkill(skill);
const cached = contractCache.get(name);
if (cached) return cached;
const promise = (async () => {
const api = join(SKILLS_ROOT, name, "api.ts");
if (!existsSync(api)) throw new Error(`${name}: missing api.ts (contract slice: HAND_CONTRACT)`);
const module = await import(pathToFileURL(api).href);
const contract = module.HAND_CONTRACT as HandContract | undefined;
if (!contract || typeof contract !== "object") throw new Error(`${name}: api.ts does not export HAND_CONTRACT`);
if (contract.skill !== name) throw new Error(`${name}: HAND_CONTRACT.skill is ${JSON.stringify(contract.skill)}`);
if (!contract.verbs || typeof contract.verbs !== "object") throw new Error(`${name}: HAND_CONTRACT.verbs is missing`);
return contract;
})();
contractCache.set(name, promise);
return promise;
}
export async function lintSkill(skill: string): Promise<LintResult> {
const models = await loadModels();
const name = normalizeSkill(skill);
const model = models.find((candidate) => candidate.name === name);
if (!model) throw new Error(`${name}: skill model not found`);
return evaluate(model, models);
}
export async function lintAll(): Promise<LintResult[]> {
const models = await loadModels();
return models.map((model) => evaluate(model, models));
}
/** THE FIRST CALL, OUT OF A CONTRACT AND NOTHING ELSE. Pure, so the rule it
* carries — a wrong verb is answered with the slice that CORRECTS it, never a
* bare "not found" the caller has to go looking after — can be tested against a
* fixture contract instead of whichever live hand happened to have the shape
* ⟨lane requires-scope, 2026-09-09: the test that pinned this asserted
* snappy-gmail's six verbs and went red the day gmail gained `reply`, which is
* the hand improving and the test calling it a regression⟩. */
export function exampleFor(contract: HandContract, verb: string): string {
if (!contract.verbs[verb]) {
const choices = Object.keys(contract.verbs).join(", ") || "none";
throw new Error(`${contract.skill}: verb ${JSON.stringify(verb)} is absent; contract slice verbs = [${choices}]`);
}
return contractExample(contract, verb);
}
export async function firstExample(skill: string, verb: string): Promise<string> {
return exampleFor(await loadContract(skill), verb);
}
/** THE SECOND SENTENCE THE CALLER NEEDS. The first call is safe because it
* omits every optional word — but the next thing an AI writes is the count it
* was asked for, and rule 59 says where that count actually lands. Returns
* null when the verb's grammar has no such trap. */
export async function exampleHazard(skill: string, verb: string): Promise<string | null> {
const contract = await loadContract(skill);
const args = [...(contract.verbs[verb]?.args ?? [])];
const target = args.findIndex((arg) => COMMONLY_SUPPLIED.test(arg));
if (target <= 0) return null;
const shadow = args.slice(0, target).filter((arg) => arg.endsWith("?"));
if (!shadow.length) return null;
const bare = args[target].replace(/\?$/, "");
const fillers = args.slice(0, target).map((arg) => (arg.endsWith("?") ? '""' : `<${arg.replace(/\?$/, "")}>`));
return `rule 59: ${verb}(${args.join(", ")}) — a bare ${bare} lands in ${shadow[0]}. Supply the earlier words: ${verb} ${fillers.join(" ")} <${bare}>`;
}
/**
* READ EVERY PINNED SPEC AND WRITE THE CORPUS. One road: `snappy-specwatch`
* calls this, and so does `api.ts specs` — there is no second fetcher.
* A hand whose spec is `docs`, `mini` or `none` is SKIPPED and said so; only
* `openapi` and `discovery` are ever reached for, and never with a credential.
*/
export async function readSpecs(only?: string): Promise<{ written: string; read: string[]; skipped: Array<{ skill: string; why: string }> }> {
const { readHandSpec } = await import("../snappy-settings/spec-read.ts");
const models = only ? [await loadModel(normalizeSkill(only), null, null, null)] : await loadModels();
const read: string[] = [];
const skipped: Array<{ skill: string; why: string }> = [];
const projections = [];
for (const model of models) {
const spec = model.contract.spec;
if (!spec) { if (only) skipped.push({ skill: model.name, why: "declares no HAND_CONTRACT.spec" }); continue; }
if (spec.kind !== "openapi" && spec.kind !== "discovery") { skipped.push({ skill: model.name, why: `kind "${spec.kind}" is declared, never fetched${spec.reason ? `: ${spec.reason}` : ""}` }); continue; }
try {
projections.push(await readHandSpec(model.name, spec));
read.push(model.name);
} catch (error) {
// EACH HAND IS READ ON ITS OWN. A shared catch would let one unreachable
// vendor throw away every other reading in the same pass.
skipped.push({ skill: model.name, why: error instanceof Error ? error.message : String(error) });
}
}
const written = projections.length ? writeSpecFile(join(SKILLS_ROOT, "snappy-tool-design"), projections).path : "(nothing read, nothing written)";
return { written, read, skipped };
}
/** THE HAND'S OWN CONTRACT, AS AN OPENAPI DOCUMENT. The fold is in
* snappy-settings; this is the one door onto it, because this file already
* owns the collection's one contract loader. */
export async function handOpenApi(skill: string) {
const { contractOpenApi } = await import("../snappy-settings/contract-openapi.ts");
return contractOpenApi(await loadContract(skill) as never);
}
export async function fixLoader(skill: string) {
const contract = await loadContract(skill);
return syncLoaderVerbTable(contract.skill, contract, { quiet: true });
}
async function loadModels(): Promise<SkillModel[]> {
const names = readdirSync(SKILLS_ROOT, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith("snappy-") && existsSync(join(SKILLS_ROOT, entry.name, "api.ts")))
.map((entry) => entry.name)
.sort();
// ONE probe file and ONE render file for the whole collection, read once.
const probe = readProbeFile(join(SKILLS_ROOT, "snappy-tool-design"));
const render = readRenderFile(join(SKILLS_ROOT, "snappy-tool-design"));
const specs = readSpecFile(join(SKILLS_ROOT, "snappy-tool-design"));
// BOUNDED, NOT ALL AT ONCE. Every one of these loads a hand's whole api.ts
// module graph; 87 in one Promise.all is the fan-out that put this Mac at
// load 311 beside two runner suites ⟨2026-09-09 04:2x⟩. The pool is the one
// in snappy-settings, whose twin is the runner's src/spawn-pool.ts.
return pooledAll(names, (name) => loadModel(name, probe, render, specs));
}
async function loadModel(
name: string,
probe: ReturnType<typeof readProbeFile>,
render: ReturnType<typeof readRenderFile>,
specs: SpecCorpus | null,
): Promise<SkillModel> {
const dir = join(SKILLS_ROOT, name);
const apiPath = join(dir, "api.ts");
const api = readRequired(apiPath, `${name}: missing api.ts`);
const manual = readRequired(join(dir, "SKILL.md"), `${name}: missing SKILL.md`);
const agents = readRequired(join(dir, "AGENTS.md"), `${name}: missing AGENTS.md`);
const frontmatter = parseFrontmatter(manual);
return {
name,
dir,
contract: await loadContract(name),
api,
source: collectLocalSource(apiPath, new Set<string>(), SKILLS_ROOT),
ownSource: collectOwnSource(apiPath, dir),
manual,
agents,
description: frontmatter.description ?? "",
frontmatter,
probe: probeFor(probe, name),
renderProbe: renderFor(render, name, faceKindsDrawn(probeFor(probe, name))),
vendor: specFor(specs, name),
};
}
function evaluate(model: SkillModel, all: SkillModel[]): LintResult {
const rules = RULES.map((definition) => ({ ...definition, ...evaluateRule(definition.id, model, all) }));
return {
skill: model.name,
pass: rules.filter((rule) => rule.status === "PASS").length,
fail: rules.filter((rule) => rule.status === "FAIL").length,
defer: rules.filter((rule) => rule.status === "DEFER").length,
rules,
};
}
function evaluateRule(id: number, m: SkillModel, all: SkillModel[]): { status: RuleStatus; pass: boolean; detail: string } {
const verbs = Object.entries(m.contract.verbs);
const readVerbs = verbs.filter(([, spec]) => effectClass(spec) === "read");
const probeReads = m.probe?.reads ?? [];
const done = (ok: boolean, detail: string) => outcome(ok ? "PASS" : "FAIL", detail);
const probe = (detail: string) => outcome("DEFER", detail);
switch (id) {
case 1: {
const valid = m.name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(m.name) && m.frontmatter.name === m.name;
return done(valid, valid ? `${m.name} matches directory and open-spec grammar` : `frontmatter name=${JSON.stringify(m.frontmatter.name)} directory=${m.name}`);
}
case 2: {
const collisions = all.flatMap((other) => Object.entries(other.contract.verbs).map(([verb, spec]) => ({ owner: other.name, verb, grammar: JSON.stringify(spec.args ?? []) })))
.filter((row) => row.owner !== m.name && verbs.some(([verb, spec]) => verb === row.verb && JSON.stringify(spec.args ?? []) !== row.grammar));
const namespaced = m.contract.skill === m.name;
return done(namespaced, `${verbs.length} effective names use ${m.name}.<verb>; ${collisions.length} raw cross-hand grammar collision(s) are isolated by hand namespace`);
}
case 3: {
const bad = /(?:^|-)(helper|utils?|tools?|data|files?|misc)$/.test(m.name.replace(/^snappy-/, ""));
return done(!bad, bad ? "generic banned name/tail" : "specific noun phrase");
}
case 4: {
const peers = all.filter((other) => other.name !== m.name && descriptionOverlap(m.description, other.description) >= 6);
const missing = peers.filter((peer) => !m.manual.includes(peer.name) && !m.description.includes(peer.name));
return done(missing.length === 0, missing.length ? `near neighbours not named: ${missing.slice(0, 5).map((peer) => peer.name).join(", ")}` : `${peers.length} near neighbour(s) cross-referenced or none`);
}
case 5: {
const desc = m.description;
const problems = [
!desc && "empty",
desc.length > 1024 && `${desc.length} chars`,
/<[^>]+>/.test(desc) && "XML/HTML tag",
/(?:^|\s)(?:I|we|you|your)\b/i.test(desc) && "first/second person",
!/(?:use|triggers?)\s+(?:when|for|on)/i.test(desc) && "no when/use marker",
].filter(Boolean);
return done(problems.length === 0, problems.length ? problems.join("; ") : `${desc.length} chars; third-person what+when`);
}
case 6: {
const graded = descriptionSync(m.contract.description, m.description);
return done(graded.pass, graded.detail);
}
case 7: {
const promised = extractPromisedVerbs(`${m.agents}\n${m.manual}`, m.name);
const declared = new Set(Object.keys(m.contract.verbs));
const unknown = [...promised].filter((verb) => !declared.has(verb));
const absent = [...declared].filter((verb) => !m.agents.includes(`\`${verb}\``) && !m.agents.includes(` ${verb} `));
return done(unknown.length === 0 && absent.length === 0, `promised-only=[${unknown.join(", ")}]; loader-missing=[${absent.join(", ")}]`);
}
case 8: {
const lines = m.manual.split("\n").length;
const tokens = approxTokens(m.manual);
const linkProblems = referenceProblems(m);
return done(lines <= 500 && tokens < 5000 && linkProblems.length === 0, `${lines} lines; ~${tokens} tokens${linkProblems.length ? `; ${linkProblems.join("; ")}` : ""}`);
}
case 9: {
const body = stripOldPatterns(m.manual);
const stale = body.split("\n").filter((line) => /\b(?:before|after|until|currently|as of|old api|deprecated|version)\b/i.test(line) && /\b20\d{2}[-/]\d{1,2}(?:[-/]\d{1,2})?\b/.test(line));
return done(stale.length === 0, stale.length ? `${stale.length} time-sensitive instruction line(s) outside Old patterns` : "no time-sensitive instruction dates");
}
case 10: {
const sets = [["endpoint", "url", "route", "path"], ["field", "box", "element"]];
const mixed = sets.filter((set) => set.filter((word) => new RegExp(`\\b${word}s?\\b`, "i").test(m.manual)).length > 1);
return done(mixed.length === 0, mixed.length ? `mixed synonym sets: ${mixed.map((set) => set.join("/")).join(", ")}` : "one term per checked concept");
}
case 11: {
const menus = m.manual.split("\n").filter((line) => (line.match(/\bor\b/gi) ?? []).length >= 2);
return done(menus.length === 0, menus.length ? `${menus.length} line(s) present three-way alternative menus` : "no three-way alternative menu");
}
case 12: {
const imperatives = ["send", "delete", "pay", "post", "execute", "upload", "read", "write"];
const unauthorised = imperatives.filter((verb) => new RegExp(`(?:^|[.!?]\\s+)${verb}\\b`, "i").test(m.description) && !m.contract.verbs[verb]);
return done(unauthorised.length === 0, unauthorised.length ? `unauthorised description imperatives: ${unauthorised.join(", ")}` : "no unauthorised imperative detected");
}
case 13: {
const gaps: string[] = [];
for (const [verb, spec] of verbs) {
const properties = schemaProperties(spec);
for (const raw of spec.args ?? []) {
const arg = raw.replace(/\?$/, "");
if (["id", "user", "data", "input"].includes(arg)) gaps.push(`${verb}.${arg} ambiguous`);
if (!properties[arg]?.description) gaps.push(`${verb}.${arg} missing description`);
}
}
return done(gaps.length === 0, gaps.length ? gaps.slice(0, 8).join("; ") : "all arguments described and specific");
}
case 14: {
const gaps: string[] = [];
for (const [verb, spec] of verbs) for (const [arg, schema] of Object.entries(schemaProperties(spec))) {
const description = String(schema.description ?? "");
if (/(?:one of|allowed|values?)(?:\s+are|:)/i.test(description) && !Array.isArray(schema.enum)) gaps.push(`${verb}.${arg}`);
}
return done(gaps.length === 0, gaps.length ? `enumerated in prose without enum: ${gaps.join(", ")}` : "no prose-only enumerable domains");
}
case 15: {
const cases = m.probe?.near_miss;
if (!cases?.length) return probe("no recorded near-miss key probes");
const expected = expectedNearMisses(m.contract);
const skipped = m.probe?.near_miss_skipped ?? [];
const covered = (item: { verb: string; provided: string; expected: string }) =>
cases.some((c) => c.verb === item.verb && c.provided === item.provided && c.expected === item.expected) ||
skipped.some((c) => c.verb === item.verb && c.provided === item.provided && c.expected === item.expected);
const uncovered = expected.filter((item) => !covered(item));
const bad = cases.filter((item) => !item.refusal.includes(item.expected));
return done(bad.length === 0 && uncovered.length === 0, `${bad.length} refusal(s) omit the correcting key; ${uncovered.length} uncovered; ${skipped.length} case(s) left unfuzzed because the verb has an outside effect`);
}
case 16: {
const known = /^(chat_id|account|cwd|revision|current_revision|home|working_directory)$/;
const bad = verbs.flatMap(([verb, spec]) => (spec.args ?? []).filter((arg) => !arg.endsWith("?") && known.test(arg)).map((arg) => `${verb}.${arg}`));
return done(bad.length === 0, bad.length ? `runner-known required args: ${bad.join(", ")}` : "no runner-known required argument");
}
case 17: {
const collections = readVerbs.filter(([verb, spec]) => isCollectionRead(verb, spec));
const bad = collections.filter(([, spec]) => {
const limit = schemaProperties(spec).limit;
return !limit || typeof limit.default !== "number" || typeof limit.maximum !== "number";
});
return done(bad.length === 0, bad.length ? `limit default/maximum missing: ${bad.map(([verb]) => verb).join(", ")}` : `${collections.length} collection read(s) bounded`);
}
case 18: {
const bad = verbs.filter(([, spec]) => !spec.class);
return done(bad.length === 0, bad.length ? `missing class: ${bad.map(([verb]) => verb).join(", ")}` : `${verbs.length} verb(s) classified`);
}
case 19: {
const bad = verbs.filter(([, spec]) => !annotationsMatch(spec));
return done(bad.length === 0, bad.length ? `annotation defaults mismatch: ${bad.map(([verb]) => verb).join(", ")}` : "annotations match declared classes");
}
case 20: {
const modeArgs = /^(apply|dry_run|commit|execute|write|delete|send|now)\??$/;
const bad = verbs.filter(([, spec]) => (spec.args ?? []).some((arg) => modeArgs.test(arg)) || Object.keys(spec.flags ?? {}).some((arg) => modeArgs.test(arg)));
return done(bad.length === 0, bad.length ? `mode-changing args on: ${bad.map(([verb]) => verb).join(", ")}` : "no read/write mode switch");
}
case 21: {
const enforcedByHint = /if\s*\([^\n]*(?:readOnlyHint|destructiveHint|idempotentHint)/.test(m.api);
return done(!enforcedByHint, enforcedByHint ? "implementation branches stage policy on annotation hint" : "no annotation-driven enforcement branch");
}
case 22: {
const cases = m.probe?.unmet_requirement;
if (!cases?.length) return probe("no unmet-requirement timing probe");
const bad = cases.filter((item) => item.elapsed_ms >= 50 || item.child_processes !== 0);
return done(bad.length === 0, bad.length ? `${bad.length} preflight refusal(s) exceeded 50 ms or spawned` : `${cases.length} refusal(s) under 50 ms with zero children`);
}
case 23: {
if (!probeReads.length) return probe("no recorded answer sizes");
const hard = probeReads.filter((read) => answerTokens(read) > 25000);
const soft = probeReads.filter((read) => (read.limit ?? 5) <= 5 && answerTokens(read) > 4000 && !read.truncated);
return done(hard.length === 0 && soft.length === 0, `${hard.length} hard-cap breach(es); ${soft.length} unannounced default-read soft-cap breach(es)`);
}
case 24: {
if (!probeReads.some((read) => read.limit)) return probe("no limit 1/5/25 answer-size series");
const bad = growthViolations(probeReads);
return done(bad.length === 0, bad.length ? `unbounded growth: ${bad.join(", ")}` : "answer growth is monotone and bounded by limit");
}
case 25: {
const over = probeReads.filter((read) => read.truncated);
if (!over.length) return probe("no recorded over-cap answer");
return done(over.every((read) => Boolean(read.summary && read.cursor)), "truncated answers require both summary and cursor");
}
case 26: {
const over = probeReads.filter((read) => read.truncated);
if (!over.length) return probe("no recorded truncation notice");
return done(over.every((read) => Boolean(read.cursor || read.summary?.match(/limit|filter|narrow/i))), "truncation notices name cursor/filter/limit");
}
case 27: return probeReads.length ? done(!JSON.stringify(probeReads).match(/[0-9a-f]{8}-[0-9a-f-]{27,}/i), "opaque-id scan over recorded answers") : probe("no recorded answer fields");
case 28: return probeReads.length ? done(probeReads.every((read) => read.structuredContent === undefined || (read.outputSchemaValid && (read.content?.length ?? 0) > 0)), "structured outputs validate and retain content") : probe("no structured-output probes");
case 29: return probeReads.length ? done(probeReads.every((read) => read.as_of_age_minutes === undefined || read.as_of_age_minutes <= 60 || read.stale_label), "recorded stale facts are labelled") : probe("no freshness probes");
case 30: {
// THE HAND'S OWN FILE, NOT THE COLLECTION'S ⟨lane loose-ends, 2026-09-09⟩.
// `m.source` follows imports across every skill, so a re-export of a
// marked hand carried that hand's declaration back as if it were this
// one's. `m.ownSource` stops at this hand's directory: the boundary has
// to be stated where the text is handed over.
const vendorRead = readVerbs.length > 0 && (m.contract.requires?.length ?? 0) > 0;
const wrapped = evidenceEnvelopeDeclared(m.ownSource);
return done(!vendorRead || wrapped, vendorRead ? (wrapped ? "vendor text has evidence-envelope semantics in the hand's own source" : "vendor read lacks evidence-envelope semantics in the hand's own source") : "no credentialed vendor read");
}
case 31: {
const refusals = m.probe?.refusals;
if (!refusals?.length) return probe("no refusal corpus");
return done(refusals.every((item) => item.outcome === "refused" && item.code && item.message && item.fix), "refusals schema-checked for outcome/code/message/fix");
}
case 32: {
const refusals = m.probe?.refusals;
if (!refusals?.length) return probe("no refusal corpus");
const literals = [...Object.keys(m.contract.verbs), ...(m.contract.requires ?? []), ...verbs.flatMap(([, spec]) => spec.args ?? []).map((arg) => arg.replace(/\?$/, ""))];
return done(refusals.every((item) => literals.some((literal) => item.message?.includes(literal) || item.fix?.includes(literal))), "refusals checked against contract literals");
}
case 33: {
const table = m.contract.refusals;
if (!table || !Object.keys(table).length) return done(false, "HAND_CONTRACT.refusals missing");
const tests = readTestSource(m.dir);
const uncovered = Object.keys(table).filter((code) => !tests.includes(code));
return done(uncovered.length === 0, `${Object.keys(table).length} refusal code(s); uncovered tests=[${uncovered.join(", ")}]`);
}
case 34: {
const text = JSON.stringify(m.contract.refusals ?? {});
const leaked = /(?:sk-[A-Za-z0-9]{20,}|xox[baprs]-|ghp_|AIza[0-9A-Za-z_-]{20,})/.test(text);
return done(!leaked, leaked ? "token-shaped value in refusal table" : "no token-shaped refusal value");
}
// ONE ENV-READ READER ⟨lane loose-ends, 2026-09-09⟩. The regexes and the
// never-a-credential set used to live here AND in
// snappy-hands/contract-derive.ts, which MINTS the `requires` these two
// rules grade. Two readers of one fact; the deriver counted every
// `process.env.X` as a requirement and these rules counted none, so a hand
// could be minted red and there was no one place to fix it. Both now import
// `snappy-settings/env-reads.ts`.
case 35: {
const required = new Set(m.contract.requires ?? []);
const reads = envReads(m.source);
const missing = [...required].filter((key) => !reads.required.has(key));
const undeclared = [...reads.required].filter((key) => !required.has(key));
return done(missing.length === 0 && undeclared.length === 0, `declared-not-required=[${missing.join(", ")}]; required-not-declared=[${undeclared.join(", ")}]`);
}
case 36: {
const required = new Set(m.contract.requires ?? []);
const optional = [...envReads(m.source).optional].filter((key) => required.has(key));
return done(optional.length === 0, optional.length ? `optional but required: ${optional.join(", ")}` : "optional env reads excluded from requires");
}
case 37: return m.probe?.published_tools === undefined ? probe("no fresh tools/list count") : done(m.probe.published_tools <= 10, `${m.probe.published_tools} published tools`);
case 38: return m.probe?.tools_list_tokens === undefined ? probe("no tools/list token measurement") : done(m.probe.tools_list_tokens <= 2000, `${m.probe.tools_list_tokens} tools/list tokens`);
case 39: {
const endpointMentions = (m.api.match(/https?:\/\/[^\s`"']+/g) ?? []).length;
const mirrors = endpointMentions >= 3 && Math.abs(verbs.length - endpointMentions) <= 1;
return done(!mirrors, `${verbs.length} verbs, ${endpointMentions} literal endpoint(s); one-to-one heuristic=${mirrors}`);
}
case 40: return done(approxTokens(m.description) <= 150 && approxTokens(m.manual) < 5000, `metadata ~${approxTokens(m.description)} tokens; body ~${approxTokens(m.manual)} tokens`);
case 41: return m.probe?.search_ranks?.length ? done(m.probe.search_ranks.every((rank) => rank <= 3), `${m.probe.search_ranks.length} search rank probe(s)`) : probe("no name+job search rank probes");
case 42: {
const kind = m.contract.kind ?? m.frontmatter["instruction-only"];
return done(readVerbs.length > 0 || kind === "true" || kind === "instruction-only" || kind === "write-only", readVerbs.length ? `${readVerbs.length} read verb(s)` : `readless kind=${JSON.stringify(kind)}`);
}
case 43: {
if (!probeReads.length) return probe("no timed read round-trip probes");
const bad = readLatencyViolations(probeReads);
return done(bad.length === 0, bad.length ? bad.join(", ") : `${probeReads.length} read sample(s) are one round trip with p50 under 3 s`);
}
case 44: {
const bands = verbs.filter(([, spec]) => typeof spec.latency === "object");
if (!bands.length || !probeReads.length) return probe("latency bands or timing probes missing");
const violations = bands.flatMap(([verb, spec]) => {
const samples = probeReads.filter((read) => read.verb === verb && typeof read.duration_ms === "number").map((read) => read.duration_ms as number);
if (!samples.length) return [`${verb}:no samples`];
const p50 = median(samples);
return p50 > (spec.latency?.p50Ms ?? Infinity) || Math.max(...samples) > (spec.latency?.maxMs ?? Infinity) ? [`${verb}:band exceeded`] : [];
});
return done(violations.length === 0, violations.length ? violations.join(", ") : `${bands.length} declared latency band(s) met`);
}
case 45: {
const jobReads = readVerbs.filter(([, spec]) => spec.execution !== "call");
const queueTouch = readVerbs.length > 0 && /\b(?:claimTick|enqueueJob|queueJob|pollUntil)\s*\(/.test(m.api);
return done(jobReads.length === 0 && !queueTouch, `${jobReads.length} read(s) not execution=call; queue implementation=${queueTouch}`);
}
case 46: {
if (!probeReads.length) return probe("no face-vs-text answer probes");
const faces = probeReads.filter((read) => read.face);
// A FACE OVER NOTHING. A picture of a two-line answer costs a render and
// adds no interaction, and a hint that names no kind promises a drawing
// nothing can make — I10's shape, caught at the answer instead of the host.
const thin = faces.filter((read) => (read.bytes ?? read.chars ?? 0) < 200);
const kindless = faces.filter((read) => !read.face?.kind);
return done(thin.length === 0 && kindless.length === 0, faces.length
? `${faces.length} face-bearing answer(s); ${thin.length} over a <200 B answer; ${kindless.length} name no kind`
: `${probeReads.length} answer(s), none returned a face`);
}
case 47: {
const faces = probeReads.filter((read) => read.face);
if (!faces.length) return probe("no UI-bearing result probes");
return done(faces.every((read) => read.face?.textFallback && (read.content?.length ?? 0) > 0), "UI results retain standalone text content");
}
case 48: {
const faces = probeReads.filter((read) => read.face);
if (!faces.length) return probe("no UI resource probes");
return done(faces.every((read) => read.face?.resourceUri?.startsWith("ui://") && read.face?.mimeType === "text/html;profile=mcp-app"), "UI URI and MIME checked");
}
case 49: {
const faces = probeReads.filter((read) => read.face);
if (!faces.length) return probe("no channel rendition probes");
return done(faces.every((read) => ["png", "lang", "text"].every((kind) => read.face?.renditions?.includes(kind))), "PNG/lang/text rendition declarations checked");
}
case 50: return renderOutcome(m, "auto_fit");
case 51: return renderOutcome(m, "action_item_bounds");
case 52: return renderOutcome(m, "handshake_race_free");
case 53: return renderOutcome(m, "dated_real_host_screenshot");
case 54: {
const evals = readEvalManifest(m.dir);
const ready = /\*\*Skill Status\*\*:\s*(?:COMPLETE|READY)/i.test(m.manual);
return done(!ready || evals.scenarios.length >= 3, `${evals.scenarios.length} eval scenario(s); ready=${ready}`);
}
case 55: {
const evals = readEvalManifest(m.dir);
if (!evals.scenarios.length) return done(false, "eval manifest missing");
const weak = evals.scenarios.filter((scenario) => !Array.isArray(scenario.calls) || scenario.calls.length < 2 || !scenario.ground_truth);
return done(weak.length === 0, `${weak.length} single-call or ground-truth-free scenario(s)`);
}
case 56: {
const expected = ["accuracy", "runtime", "tool_call_count", "tokens", "errors"];
const metrics = m.probe?.metrics ?? [];
if (!metrics.length) return probe("no probe metric manifest");
const missing = expected.filter((metric) => !metrics.includes(metric));
return done(missing.length === 0, `missing metrics=[${missing.join(", ")}]`);
}
case 57: {
const evals = readEvalManifest(m.dir);
const train = new Set(evals.scenarios.filter((scenario) => scenario.split !== "holdout").map((scenario) => scenario.id));
const holdout = evals.scenarios.filter((scenario) => scenario.split === "holdout").map((scenario) => scenario.id);
return done(holdout.length > 0 && holdout.every((id) => !train.has(id)), `${holdout.length} disjoint holdout scenario(s)`);
}
case 58: return m.probe?.fresh_client === undefined ? probe("no client/deploy handshake evidence") : done(m.probe.fresh_client && m.probe.handshake_after_deploy === true, "fresh client handshake postdates deploy");
case 59: {
const offenders: string[] = [];
for (const [verb, spec] of verbs) {
const args = [...(spec.args ?? [])];
const target = args.findIndex((arg) => COMMONLY_SUPPLIED.test(arg));
if (target <= 0) continue;
const shadow = args.slice(0, target).filter((arg) => arg.endsWith("?"));
if (shadow.length) offenders.push(`${verb}(${args.join(", ")}) — ${shadow.join(", ")} before ${args[target]}`);
}
return done(offenders.length === 0, offenders.length ? offenders.join("; ") : `${verbs.length} verb(s): no optional positional shadows a count`);
}
case 60: {
const block = renderLoaderVerbTable(m.contract);
if (!m.agents.includes("<!-- SNAPPY-CONTRACT-VERBS-START -->")) return done(false, "AGENTS.md carries no generated contract block; run fix-loader");
const present = m.agents.slice(m.agents.indexOf("<!-- SNAPPY-CONTRACT-VERBS-START -->"), m.agents.indexOf("<!-- SNAPPY-CONTRACT-VERBS-END -->") + "<!-- SNAPPY-CONTRACT-VERBS-END -->".length);
return done(present === block, present === block ? `${verbs.length} verb(s) projected with their derived first call` : "generated block is stale against HAND_CONTRACT; run fix-loader");
}
/**
* R61 — THE VENDOR CHANGED AND NOBODY NOTICED.
*
* ⟨owner, 2026-09-09 16:5x⟩ "wouldn't it be good for the skills to check
* themselves against OpenAPI? GPT Image 2.5 got released — the OpenAPI
* spec could be lined up against api.ts, and if there's a difference it's
* programmatically noticed and then updated."
*
* The table already knew "a verb it promises and does not have" (R7) and
* "a loader whose first call is out of date" (R60). Both compare this
* repository to itself. R61 is the first rule that compares it to the
* WORLD, and the world is the only party in this system that changes
* without a commit.
*
* FIVE ARMS, and which one runs is decided by what the hand declared:
*
* no `spec` → DEFER. Not a FAIL: 97 of 98 hands name no spec today, and
* a rule that fails all of them on day one is 97 rows of
* debt with a receipt — the ratchet this repo bans. DEFER is
* the honest word for "nobody has looked yet", and it is the
* same word R15 and R50 use for evidence that does not exist.
* `none` → the hand says the world publishes nothing to check it
* against. PASS with its reason; FAIL without one, because
* "unmapped on purpose" and "forgot" must not look alike.
* `mini` → ⟨owner 17:0x⟩ "Libretto, iMessage has no API, WhatsApp only
* has a business API — that's why we have the mini with full
* computer capability to fill the gaps." Its check is its own
* recorded read replaying (watch-me-once), never a vendor
* document, so this rule asks only for the reason and stands
* aside.
* `docs` → prose only, so there is nothing to diff. The weaker check:
* a url, a reason, and a `pinned.checked_at` that is not
* ancient.
* `openapi` / `discovery` → the real rule, over the committed corpus.
*
* The corpus is EVIDENCE and its absence is DEFER, never PASS — a rule
* that vouched for a hand nobody had checked would be a status truer than
* its artifact ⟨CLAUDE.md §10⟩.
*/
case 61: {
const spec = m.contract.spec;
if (!spec) return outcome("DEFER", "names no vendor spec; add HAND_CONTRACT.spec (kind openapi | discovery | docs | mini | none)");
const reason = typeof spec.reason === "string" ? spec.reason.trim() : "";
if (spec.kind === "none") return done(reason.length > 0, reason ? `unmapped on purpose: ${reason}` : 'kind "none" gives no reason, so "nobody publishes one" cannot be told from "nobody has looked"');
if (spec.kind === "mini") return done(reason.length > 0, reason ? `filled on the mini, checked by its own recorded read: ${reason}` : 'kind "mini" gives no reason naming the recorded read or computer-use road that stands in for a vendor document');
if (spec.kind === "docs") {
if (!spec.url || !reason) return done(false, 'kind "docs" needs both a url and one sentence saying why the vendor publishes no machine-readable spec');
const age = spec.pinned?.checked_at ? Math.floor((Date.now() - Date.parse(spec.pinned.checked_at)) / 86_400_000) : null;
return done(true, `docs only at ${spec.url}${age === null ? " (never checked by a person)" : `, read by a person ${age}d ago`} — ${reason}`);
}
if (!m.vendor) return outcome("DEFER", `pins ${spec.kind} ${spec.url ?? "(no url)"} but ${SPEC_FILE} holds no reading of it; run \`snappy-specwatch check ${m.name}\``);
const drift = driftOf(spec, m.vendor, [m.ownSource, m.manual, m.agents, JSON.stringify(m.contract)].join("\n"));
const moved = drift.moved ? ` · ${vendorOf(spec.url ?? "")} moved from ${drift.moved.from} to ${drift.moved.to} since ${drift.checkedAt}` : "";
const sentence = driftSentence(vendorOf(spec.url ?? ""), drift);
if (drift.gone.length || drift.enums.length) return done(false, `${sentence}${moved}`);
// THE STORE CARD'S TRUST LINE comes out of this same detail: how many of
// the vendor's operations this hand actually covers, over how many it
// publishes, as of when.
return done(true, `covers ${Object.keys(m.vendor.operations).length} of ${m.vendor.operations_total} operation(s) in ${vendorOf(spec.url ?? "")} ${m.vendor.family} ${m.vendor.version}, read ${m.vendor.fetched_at.slice(0, 10)}${moved}`);
}
default: return outcome("DEFER", "rule implementation missing");
}
}
function outcome(status: RuleStatus, detail: string) {
return { status, pass: status === "PASS", detail };
}
function effectClass(spec: HandVerb): EffectClass | undefined {
if (spec.class) return spec.class;
if (spec.effect === "read") return "read";
return undefined;
}
function expectedAnnotations(spec: HandVerb): Record<string, boolean> | null {
const cls = spec.class;
if (!cls) return null;
return {
readOnlyHint: cls === "read",
destructiveHint: cls === "destructive" || cls === "spend" || cls === "send-to-a-person",
idempotentHint: cls !== "read" && spec.idempotent === true,
openWorldHint: spec.openWorld ?? true,
};
}
function annotationsMatch(spec: HandVerb): boolean {
const expected = expectedAnnotations(spec);
if (!expected || !spec.annotations) return false;
return Object.entries(expected).every(([key, value]) => spec.annotations?.[key] === value);
}
function schemaProperties(spec: HandVerb): Record<string, Record<string, unknown>> {
const schema = spec.inputSchema as { properties?: Record<string, Record<string, unknown>> } | undefined;
return schema?.properties ?? {};
}
/** WHOSE ANSWER IS A LIST — READ OFF THE VERB'S GRAMMAR, NEVER ITS SPELLING.
*
* WHY IT CHANGED ⟨lane r17-2, 2026-09-09⟩. This test used to match the
* SUBSTRING "read" / "logs" / "all" / "events" in a verb's NAME, and a name is
* not a shape. It accused four verbs that answer exactly ONE record —
* `snappy-email read` (one message), `snappy-mastermind-model read` (one
* topic), `snappy-statechange unread` (Circle's counters, which the arm itself
* publishes as `count: 0`) and `snappy-xano-dashboard logs` (a string) — of
* being unbounded collections. Each could only have answered the charge by
* declaring a `limit` that nothing on its road could honour, which is the
* defect this lint exists to catch, committed by the lint itself. And while it
* was guessing at letters it never looked at SIXTEEN verbs that do answer
* lists, because their names carry none of those letters: `freshbooks clients`,
* `github repos`, `imessage contacts`, `linkedin comments`, `update commits`,
* `krisp fetch-meetings` and ten more.
*
* THE GRAMMAR IS THE SHAPE. A verb that admits a COUNT — a `limit` positional,
* a `--limit` flag, or a `limit` property in its inputSchema — is the hand's
* own statement that its answer carries a variable number of rows. Whether it
* then declares that bound COMPLETELY (default AND maximum, through the one
* road in `snappy-settings/read-limit.ts`) is exactly what this rule grades.
* The count vocabulary is `COMMONLY_SUPPLIED`, the SAME one rule 59 reads —
* one vocabulary, two rules ⟨CLAUDE.md R4, duplicate roads⟩.
*
* WHAT THIS RULE NO LONGER PRETENDS TO KNOW is whether a verb that names no
* count OUGHT to. Nothing static can see that, and a name never could. A verb
* that hands back a hundred rows while admitting no count is caught where the
* answer is — rules 23 and 24, graded from a RECORDED read rather than from a
* guess about letters ⟨CLAUDE.md R10: a status is only as true as the artifact
* it implies⟩.
*
* MEASURED 2026-09-09 across the 98-hand collection: the substring test graded
* 57 verbs, this one grades 69, and the four single-record verbs are in
* neither. The population GREW; nothing was hidden to make the rule green. */
function isCollectionRead(verb: string, spec: HandVerb): boolean {
void verb;
if ((spec.args ?? []).some((arg) => COMMONLY_SUPPLIED.test(arg))) return true;
if (Object.keys(spec.flags ?? {}).some((flag) => COMMONLY_SUPPLIED.test(flag))) return true;
return Object.keys(schemaProperties(spec)).some((key) => COMMONLY_SUPPLIED.test(key));
}
function parseFrontmatter(markdown: string): Record<string, string> {
const match = markdown.match(/^---\n([\s\S]*?)\n---/);
if (!match) return {};
const result: Record<string, string> = {};
const lines = match[1].split("\n");
for (let index = 0; index < lines.length; index++) {
const key = lines[index].match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
if (!key) continue;
let value = key[2].trim();
if (value === ">" || value === "|") {
const body: string[] = [];
while (index + 1 < lines.length && /^\s+/.test(lines[index + 1])) body.push(lines[++index].trim());
value = body.join(" ");
}
result[key[1]] = unquote(value);
}
return result;
}
function unquote(value: string): string {
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
return value;
}
/** RULE 6, AS A RULE ⟨lane requires-scope, 2026-09-09⟩. The contract is the one
* source of what a hand says it is; SKILL.md's frontmatter is a COPY, and a
* copy that drifts is the whole defect — the loader that picks the hand reads
* the frontmatter, the caller that validates it reads the contract, and they
* describe two different tools. Pure, and graded off two strings, so the FAIL
* arm has a test that does not depend on some live hand still being wrong: the
* test that used to hold it asserted snappy-gmail FAILS, and went green-by-
* looking-away the day gmail's description was synced. */
export function descriptionSync(contractDescription: string | undefined, frontmatter: string): { pass: boolean; detail: string } {
const expected = normalizeSpace(contractDescription ?? "");
const actual = normalizeSpace(frontmatter);
if (!expected) return { pass: false, detail: "HAND_CONTRACT.description missing" };
return { pass: expected === actual, detail: `description ${expected === actual ? "matches" : "differs from"} contract source` };
}
function normalizeSpace(value: string): string {
return value.replace(/\s+/g, " ").trim();
}
function normalizeSkill(input: string): string {
const name = input.split("/").filter(Boolean).at(-1) ?? "";
if (!/^snappy-[a-z0-9-]+$/.test(name)) throw new Error(`${JSON.stringify(input)} is not a snappy-* skill name`);
if (!existsSync(join(SKILLS_ROOT, name))) throw new Error(`${name}: skill directory not found`);
return name;
}
function readRequired(path: string, message: string): string {
if (!existsSync(path)) throw new Error(message);
return readFileSync(path, "utf8");
}
function readJson<T>(path: string): T | null {
if (!existsSync(path)) return null;
try { return JSON.parse(readFileSync(path, "utf8")) as T; }
catch { return null; }
}
/** THE ONE WALK, AND WHY IT IS NOT HERE ⟨lane requires-scope, 2026-09-09⟩.
* `collectLocalSource` and `collectOwnSource` used to live in this file, and
* rules 35/36 were their only caller — while `snappy-hands/contract-derive.ts`,
* which MINTS the `requires` those rules grade, read a hand's own api.ts alone.
* One word, two scopes: the grader walked into snappy-email and the deriver did
* not, so 23 of 98 hands would have been minted short. Both roads now call the
* same walk in `snappy-settings/env-reads.ts` — the module that already owns
* what a requirement IS now also owns which text it is read out of. Re-exported
* here because r30-scope.test.ts names this file as the rule's owner. */
export { collectOwnSource, evidenceEnvelopeDeclared };
function extractPromisedVerbs(markdown: string, currentSkill: string): Set<string> {
const verbs = new Set<string>();
for (const line of markdown.split("\n")) {
const call = line.match(/(?:npx\s+tsx|node|bun)\s+([^\s`]*api\.ts)\s+([A-Za-z][A-Za-z0-9_-]*)/);
if (call) {
const named = call[1].match(/skills\/(snappy-[a-z0-9-]+)\/api\.ts/)?.[1];
if (!named || named === currentSkill) verbs.add(call[2]);
}
for (const match of line.matchAll(/(?:verb|command)\s+`([a-z][a-z0-9_-]*)`/gi)) verbs.add(match[1]);
}
return verbs;
}
function descriptionOverlap(a: string, b: string): number {
const stop = new Set(["the", "and", "for", "with", "when", "use", "from", "that", "this", "skill", "snappy"]);
const words = (text: string) => new Set(text.toLowerCase().match(/[a-z][a-z0-9-]{2,}/g)?.filter((word) => !stop.has(word)) ?? []);
const aa = words(a); const bb = words(b);
return [...aa].filter((word) => bb.has(word)).length;
}
function approxTokens(text: string): number {
return Math.ceil(text.length / 4);
}
function stripOldPatterns(markdown: string): string {
return markdown.replace(/^## Old patterns\b[\s\S]*?(?=^## |\z)/gim, "");
}
function referenceProblems(m: SkillModel): string[] {
const problems: string[] = [];
for (const match of m.manual.matchAll(/\[[^\]]+]\(([^)#]+\.md)(?:#[^)]+)?\)/g)) {
if (match[1].includes("../") || match[1].split("/").length > 2) problems.push(`deep reference ${match[1]}`);
const path = resolve(m.dir, match[1]);
if (existsSync(path) && statSync(path).isFile()) {
const text = readFileSync(path, "utf8");
if (text.split("\n").length > 100 && !/(?:^|\n)## (?:Contents|Table of contents)/i.test(text.slice(0, 3000))) problems.push(`${match[1]} >100 lines without ToC`);
}
}
return problems;
}
function growthViolations(reads: ProbeRead[]): string[] {
const byVerb = new Map<string, ProbeRead[]>();
for (const read of reads.filter((item) => item.limit && item.chars)) byVerb.set(read.verb, [...(byVerb.get(read.verb) ?? []), read]);
const bad: string[] = [];
for (const [verb, series] of byVerb) {
series.sort((a, b) => (a.limit ?? 0) - (b.limit ?? 0));
for (let index = 1; index < series.length; index++) if ((series[index].chars ?? 0) < (series[index - 1].chars ?? 0)) bad.push(`${verb}:non-monotone`);
const one = series.find((item) => item.limit === 1)?.chars;
const five = series.find((item) => item.limit === 5)?.chars;
if (one && five && five > one * 4) bad.push(`${verb}:1→5 over 4x`);
}
return bad;
}
function expectedNearMisses(contract: HandContract): Array<{ verb: string; provided: string; expected: string }> {
const pairs: Array<[string, string]> = [
["body", "text"], ["text", "body"], ["recipient", "to"], ["to", "recipient"],
["msg", "message"], ["message", "msg"], ["count", "limit"], ["limit", "count"],
];
const cases: Array<{ verb: string; provided: string; expected: string }> = [];
for (const [verb, spec] of Object.entries(contract.verbs)) {
const args = new Set((spec.args ?? []).map((arg) => arg.replace(/\?$/, "")));
for (const [provided, expected] of pairs) if (args.has(expected) && !args.has(provided)) cases.push({ verb, provided, expected });
}
return cases;
}
function answerTokens(read: ProbeRead): number {
return read.tokens ?? Math.ceil((read.chars ?? 0) / 4);
}
function readTestSource(dir: string): string {
return readdirSync(dir, { withFileTypes: true })
.filter((entry) => entry.isFile() && /(?:test|spec)\.(?:ts|js|mjs|json)$/.test(entry.name))
.map((entry) => readFileSync(join(dir, entry.name), "utf8"))
.join("\n");
}
function readLatencyViolations(reads: ProbeRead[]): string[] {
const byVerb = new Map<string, ProbeRead[]>();
for (const read of reads) byVerb.set(read.verb, [...(byVerb.get(read.verb) ?? []), read]);
const violations: string[] = [];
for (const [verb, samples] of byVerb) {
const durations = samples.flatMap((sample) => typeof sample.duration_ms === "number" ? [sample.duration_ms] : []);
if (!durations.length || median(durations) > 3000) violations.push(`${verb}:p50>3s-or-missing`);
if (samples.some((sample) => sample.request_count !== 1)) violations.push(`${verb}:request-count!=1`);
}
return violations;
}
function median(values: number[]): number {
const sorted = [...values].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
}
/**
* Rules 50–53 quantify over face FAMILIES, and a skill with none was PASSing
* them on nothing ⟨fixed 2026-09-09, lane render-vacuous⟩.
*
* The old arm answered PASS with the words "…is vacuous", on the reading that
* an empty set satisfies "every". Formally true, operationally a lie: 94 of 98
* skills took that arm — because `FAMILY_OWNERS` named two families — so four
* rules read 98 PASS / 0 FAIL, were pinned on the zero floor for it, and
* vouched for skills no host had ever drawn. A PASS is a claim that something
* was measured ⟨CLAUDE.md §10: a status is only as true as the artifact it
* implies⟩, and the artifact here — a browser row for a family this skill
* owns — does not exist. So it DEFERs, exactly like a skill the run never
* reached. Zero stays zero because it is measured, not because nothing looked.
*/
function renderOutcome(m: SkillModel, key: string) {
if (!m.renderProbe) return outcome("DEFER", "no real-host render probe");
if (!m.renderProbe.families.length) return outcome("DEFER", `no face family is owned by this skill; ${key} was never measured`);
const value = m.renderProbe.values[key];
return outcome(value === true ? "PASS" : "FAIL", `${key}=${JSON.stringify(value)} over famil(ies) ${m.renderProbe.families.join(", ")}`);
}
function readEvalManifest(dir: string): { scenarios: Array<{ id: string; split?: string; calls?: unknown[]; ground_truth?: unknown }> } {
return readJson(join(dir, "evals.json")) ?? { scenarios: [] };
}
const CONTRACT_DESCRIPTION = "Contract-first ergonomics lint for AI-operated skills, unlike snappy-artifact-loop which manages implementation feedback. Use when auditing tool descriptions, verbs, arguments, effects, refusals, response bounds, progressive disclosure, latency, faces, the derived first call, or eval evidence against the sourced 61-rule research table. Triggers on: snappy-tool-design, tool design, lint a skill, tool ergonomics, verb table, derived first call, AGENTS.md drift, refusal shape, response bounds.";
export const HAND_CONTRACT = {
skill: "snappy-tool-design",
description: CONTRACT_DESCRIPTION,
kind: "tool",
managed: false,
requires: [] as string[],
refusals: refusalTable("unknown_verb", "missing_argument", "missing_credential"),
verbs: {
lint: {
args: ["skill?"], effect: "read", class: "read", execution: "call", openWorld: false,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
flags: { all: "--all", summary: "--summary", limit: "--limit" },
inputSchema: { properties: { skill: { type: "string", description: "Optional snappy-* directory name; omit with --all" }, limit: { type: "integer", description: "Maximum skill rows printed in the collection table", default: 100, maximum: 500 } } },
},
example: {
args: ["skill", "verb"], effect: "read", class: "read", execution: "call", openWorld: false,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
inputSchema: { properties: { skill: { type: "string", description: "Snappy skill directory name" }, verb: { type: "string", description: "Exact verb key from HAND_CONTRACT.verbs" } } },
},
probe: {
args: ["skill?"], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { via: "--via", out: "--out", "token-file": "--token-file" },
inputSchema: { properties: { skill: { type: "string", description: "One snappy-* hand to probe; omit for the whole collection" }, via: { type: "string", description: "Which MCP road to drive", enum: ["mini", "public"], default: "mini" }, out: { type: "string", description: "Where to write the probe corpus" } } },
},
render: {
args: [] as string[], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { out: "--out", "host-dir": "--host-dir", "shot-dir": "--shot-dir", kind: "--kind" },
inputSchema: { properties: { out: { type: "string", description: "Where to write the render corpus" }, kind: { type: "string", description: "Comma-separated face kinds to draw instead of the whole bundle; needs --out so a partial run cannot overwrite the collection's render file" } } },
},
loop: {
args: [] as string[], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { via: "--via", out: "--out", "skip-probe": "--skip-probe", "skip-render": "--skip-render" },
inputSchema: { properties: { out: { type: "string", description: "Directory that receives LEDGER.md and briefs/" } } },
},
hosts: {
args: [] as string[], effect: "read", class: "read", execution: "call", openWorld: true,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
flags: { out: "--out", "token-file": "--token-file", url: "--url", only: "--only", "codex-model": "--codex-model" },
inputSchema: { properties: { out: { type: "string", description: "Directory that receives tool-design-hosts.json, HOSTS-LEDGER.md and briefs/" }, only: { type: "string", description: "Comma-separated host ids to attempt; the rest record not-driven with a reason", enum: ["chatgpt", "claude", "codex", "menubar", "telegram"] } } },
},
specs: {
args: ["skill?"], effect: "write-reversible", class: "additive-write", execution: "call", idempotent: true, openWorld: true,
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
inputSchema: { properties: { skill: { type: "string", description: "One snappy-* hand whose pinned vendor spec is re-read; omit for every hand that pins one" } } },
},
openapi: {
args: ["skill"], effect: "read", class: "read", execution: "call", openWorld: false,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: false },
inputSchema: { properties: { skill: { type: "string", description: "Snappy skill whose HAND_CONTRACT is emitted as an OpenAPI 3.1 document" } } },
},
"fix-loader": {
args: ["skill"], effect: "write-reversible", class: "additive-write", execution: "call", idempotent: true, openWorld: false,
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
inputSchema: { properties: { skill: { type: "string", description: "Snappy skill whose AGENTS.md contract block is regenerated" } } },
},
},
} as const satisfies HandContract & { managed: boolean };
async function main(): Promise<void> {
const [, , command, ...args] = process.argv;
const summary = args.includes("--summary");
const limitAt = args.indexOf("--limit");
const limit = limitAt >= 0 ? Number(args[limitAt + 1]) : 100;
try {
if (!command || command === "help") {
console.log("Usage:");
console.log(" npx tsx api.ts lint <skill>");
console.log(" npx tsx api.ts lint --all [--summary] [--limit N]");
console.log(" npx tsx api.ts example <skill> <verb>");
console.log(" npx tsx api.ts probe [<skill>] [--via mini|public] [--out FILE]");
console.log(" npx tsx api.ts render [--out FILE] [--host-dir DIR] [--shot-dir DIR] [--kind k1,k2]");
console.log(" npx tsx api.ts loop [--via mini|public] [--out DIR] [--skip-probe] [--skip-render]");
console.log(" npx tsx api.ts hosts [--out DIR] [--only chatgpt,claude,codex,menubar,telegram] [--url URL] [--token-file FILE]");
console.log(" npx tsx api.ts specs [<skill>]");
console.log(" npx tsx api.ts openapi <skill>");
console.log(" npx tsx api.ts fix-loader <skill>");
return;
}
if (command === "contract") { console.log(JSON.stringify(HAND_CONTRACT, null, 2)); return; }
if (command === "lint") {
if (args.includes("--all")) printAll(await lintAll(), summary, Number.isFinite(limit) ? limit : 100);
else {
const skill = args.find((arg, index) => !arg.startsWith("--") && args[index - 1] !== "--limit");
if (!skill) throw new RefusedError("missing_argument", "lint needs <skill> or --all; contract slice lint.args = [skill?]");
printOne(await lintSkill(skill), summary);
}
return;
}
if (command === "example") {
if (!args[0] || !args[1]) throw new RefusedError("missing_argument", "example needs <skill> <verb>; contract slice example.args = [skill, verb]");
console.log(await firstExample(args[0], args[1]));
const hazard = await exampleHazard(args[0], args[1]);
if (hazard) console.error(hazard);
return;
}
if (command === "hosts") {
const { runHostsCommand } = await import("./loop.ts");
await runHostsCommand(args, { skillsRoot: SKILLS_ROOT });
return;
}
if (command === "probe" || command === "render" || command === "loop") {
const { runLoopCommand } = await import("./loop.ts");
await runLoopCommand(command, args, { skillsRoot: SKILLS_ROOT, lintAll, printAll });
return;
}
if (command === "specs") {
const skill = args.find((arg) => !arg.startsWith("--"));
const answer = await readSpecs(skill);
console.log(`read ${answer.read.length} vendor spec(s): ${answer.read.join(", ") || "none"}`);
for (const row of answer.skipped) console.log(` skipped ${row.skill}: ${row.why}`);
console.log(`wrote ${answer.written}`);
return;
}
if (command === "openapi") {
if (!args[0]) throw new RefusedError("missing_argument", "openapi needs <skill>; contract slice openapi.args = [skill]");
console.log(JSON.stringify(await handOpenApi(args[0]), null, 2));
return;
}
if (command === "fix-loader") {
if (!args[0]) throw new RefusedError("missing_argument", "fix-loader needs <skill>; contract slice fix-loader.args = [skill]");
const fixed = await fixLoader(args[0]);
console.log(`${fixed.changed ? "updated" : "unchanged"}: ${fixed.path} (${fixed.verbs} verbs)`); return;
}
throw new RefusedError("unknown_verb", `unknown verb ${JSON.stringify(command)}; contract slice verbs = [lint, example, probe, render, loop, hosts, specs, openapi, fix-loader]`);
} catch (error) {
// THE ONE PLACE THIS HAND SAYS NO. A refusal prints the closed table's
// shape on stdout and exits 1; anything else is an internal error and
// stays one, because a crash dressed as a governed refusal is the lie
// the table exists to prevent.
if (isRefusedError(error)) {
printRefusal(error.refusal);
console.error(error.message);
return;
}
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
}
if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) void main();
6 published face family(ies) no read can reach · 6 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.A family is only as true as the sentence that reaches it. The runner takes TWO roads from an answer to a family, in this order: the hand's own declaration — HAND_CONTRACT.verbs.<verb>.face: "<kind>" — and, for hands that declare nothing, the derivation familyForHand (the hand's name minus the prefix) with VERB_SHAPE folding the verb onto a slot. These families pass neither. They draw beautifully and nobody's words arrive at them. THE FIX IS ALMOST NEVER A FACE. It is one of three, and the cell below says which: (1) a hand already makes this thing and does not say so — it names the kind on the verb that draws it, and prints the rows that kind declares as an ADDITIVE fold, renaming nothing; (2) the hand's read prints text or the wrong shape, so the declaration would be a status over a missing artifact — the read gains a JSON answer first; (3) no hand makes this thing at all, which is an honest red that stays in the denominator until some hand does.
"blocks": no hand named snappy-blocks exists and no hand's contract declares a "blocks" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "blocks" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red
"code": no hand named snappy-code exists and no hand's contract declares a "code" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "code" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red
"instagram": no hand named snappy-instagram exists and no hand's contract declares a "instagram" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "instagram" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red
"sheets": no hand named snappy-sheets exists and no hand's contract declares a "sheets" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "sheets" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red
"stripe": no hand named snappy-stripe exists and no hand's contract declares a "stripe" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "stripe" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red
"x": no hand named snappy-x exists and no hand's contract declares a "x" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "x" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-family-has-no-read **6 published face family(ies) no read can reach** · 6 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism A family is only as true as the sentence that reaches it. The runner takes TWO roads from an answer to a family, in this order: the hand's own declaration — `HAND_CONTRACT.verbs.<verb>.face: "<kind>"` — and, for hands that declare nothing, the derivation `familyForHand` (the hand's name minus the prefix) with VERB_SHAPE folding the verb onto a slot. These families pass neither. They draw beautifully and nobody's words arrive at them. THE FIX IS ALMOST NEVER A FACE. It is one of three, and the cell below says which: (1) a hand already makes this thing and does not say so — it names the kind on the verb that draws it, and prints the rows that kind declares as an ADDITIVE fold, renaming nothing; (2) the hand's read prints text or the wrong shape, so the declaration would be a status over a missing artifact — the read gains a JSON answer first; (3) no hand makes this thing at all, which is an honest red that stays in the denominator until some hand does. ## The measured cells ``` "blocks": no hand named snappy-blocks exists and no hand's contract declares a "blocks" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "blocks" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red "code": no hand named snappy-code exists and no hand's contract declares a "code" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "code" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red "instagram": no hand named snappy-instagram exists and no hand's contract declares a "instagram" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "instagram" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red "sheets": no hand named snappy-sheets exists and no hand's contract declares a "sheets" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "sheets" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red "stripe": no hand named snappy-stripe exists and no hand's contract declares a "stripe" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "stripe" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red "x": no hand named snappy-x exists and no hand's contract declares a "x" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "x" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red ``` ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
1 ask(s) need a second call before a face exists · 1 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.The person's own words do not name the contract's verb, so the FIRST call a host writes refuses and the face only appears on the retry. Every host pays that hop, and a host that does not retry never draws at all ⟨rules 15, 32, and B11: a first call that refuses is a defect of the projection⟩.
skool-feed: 2 hop(s) — "feed" is the person's word and the contract declares "posts" — the first call refused () and the face only exists on the retry
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-first-call-refuses **1 ask(s) need a second call before a face exists** · 1 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism The person's own words do not name the contract's verb, so the FIRST call a host writes refuses and the face only appears on the retry. Every host pays that hop, and a host that does not retry never draws at all ⟨rules 15, 32, and B11: a first call that refuses is a defect of the projection⟩. ## The measured cells ``` skool-feed: 2 hop(s) — "feed" is the person's word and the contract declares "posts" — the first call refused () and the face only exists on the retry ``` ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
18 platform hand(s) name no face family · 18 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.The runner derives a face family from the hand's own name (snappy-runner/src/face.ts#familyForHand) against snappy-faces' FACE_FAMILIES. These hands land on a family that does not exist, so their answers are prose in EVERY host — no host driver can fix them and no screenshot will ever exist for them. The fix is a face family in the faces library, one per platform, in that platform's own look.
snappy-api-sniffer → "api-sniffer": snappy-faces declares no "api-sniffer" family, so every host draws snappy-api-sniffer's answers as prose — a face to build, not a host to fix
snappy-blog → "blog": snappy-faces declares no "blog" family, so every host draws snappy-blog's answers as prose — a face to build, not a host to fix
snappy-channel-contract → "channel-contract": snappy-faces declares no "channel-contract" family, so every host draws snappy-channel-contract's answers as prose — a face to build, not a host to fix
snappy-client-orbiter → "client-orbiter": snappy-faces declares no "client-orbiter" family, so every host draws snappy-client-orbiter's answers as prose — a face to build, not a host to fix
snappy-client-ray → "client-ray": snappy-faces declares no "client-ray" family, so every host draws snappy-client-ray's answers as prose — a face to build, not a host to fix
snappy-client-scott → "client-scott": snappy-faces declares no "client-scott" family, so every host draws snappy-client-scott's answers as prose — a face to build, not a host to fix
snappy-client-total → "client-total": snappy-faces declares no "client-total" family, so every host draws snappy-client-total's answers as prose — a face to build, not a host to fix
snappy-clients → "clients": snappy-faces declares no "clients" family, so every host draws snappy-clients's answers as prose — a face to build, not a host to fix
snappy-corpus → "corpus": snappy-faces declares no "corpus" family, so every host draws snappy-corpus's answers as prose — a face to build, not a host to fix
snappy-docs → "docs": snappy-faces declares no "docs" family, so every host draws snappy-docs's answers as prose — a face to build, not a host to fix
snappy-email → "email": snappy-faces declares no "email" family, so every host draws snappy-email's answers as prose — a face to build, not a host to fix
snappy-gateway → "gateway": snappy-faces declares no "gateway" family, so every host draws snappy-gateway's answers as prose — a face to build, not a host to fix
snappy-hooks → "hooks": snappy-faces declares no "hooks" family, so every host draws snappy-hooks's answers as prose — a face to build, not a host to fix
snappy-mastermind-model → "mastermind-model": snappy-faces declares no "mastermind-model" family, so every host draws snappy-mastermind-model's answers as prose — a face to build, not a host to fix
snappy-outbound → "outbound": snappy-faces declares no "outbound" family, so every host draws snappy-outbound's answers as prose — a face to build, not a host to fix
snappy-remotion → "remotion": snappy-faces declares no "remotion" family, so every host draws snappy-remotion's answers as prose — a face to build, not a host to fix
snappy-settings → "settings": snappy-faces declares no "settings" family, so every host draws snappy-settings's answers as prose — a face to build, not a host to fix
snappy-testimonials → "testimonials": snappy-faces declares no "testimonials" family, so every host draws snappy-testimonials's answers as prose — a face to build, not a host to fix
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-no-face-family **18 platform hand(s) name no face family** · 18 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism The runner derives a face family from the hand's own name (snappy-runner/src/face.ts#familyForHand) against snappy-faces' FACE_FAMILIES. These hands land on a family that does not exist, so their answers are prose in EVERY host — no host driver can fix them and no screenshot will ever exist for them. The fix is a face family in the faces library, one per platform, in that platform's own look. ## The measured cells ``` snappy-api-sniffer → "api-sniffer": snappy-faces declares no "api-sniffer" family, so every host draws snappy-api-sniffer's answers as prose — a face to build, not a host to fix snappy-blog → "blog": snappy-faces declares no "blog" family, so every host draws snappy-blog's answers as prose — a face to build, not a host to fix snappy-channel-contract → "channel-contract": snappy-faces declares no "channel-contract" family, so every host draws snappy-channel-contract's answers as prose — a face to build, not a host to fix snappy-client-orbiter → "client-orbiter": snappy-faces declares no "client-orbiter" family, so every host draws snappy-client-orbiter's answers as prose — a face to build, not a host to fix snappy-client-ray → "client-ray": snappy-faces declares no "client-ray" family, so every host draws snappy-client-ray's answers as prose — a face to build, not a host to fix snappy-client-scott → "client-scott": snappy-faces declares no "client-scott" family, so every host draws snappy-client-scott's answers as prose — a face to build, not a host to fix snappy-client-total → "client-total": snappy-faces declares no "client-total" family, so every host draws snappy-client-total's answers as prose — a face to build, not a host to fix snappy-clients → "clients": snappy-faces declares no "clients" family, so every host draws snappy-clients's answers as prose — a face to build, not a host to fix snappy-corpus → "corpus": snappy-faces declares no "corpus" family, so every host draws snappy-corpus's answers as prose — a face to build, not a host to fix snappy-docs → "docs": snappy-faces declares no "docs" family, so every host draws snappy-docs's answers as prose — a face to build, not a host to fix snappy-email → "email": snappy-faces declares no "email" family, so every host draws snappy-email's answers as prose — a face to build, not a host to fix snappy-gateway → "gateway": snappy-faces declares no "gateway" family, so every host draws snappy-gateway's answers as prose — a face to build, not a host to fix snappy-hooks → "hooks": snappy-faces declares no "hooks" family, so every host draws snappy-hooks's answers as prose — a face to build, not a host to fix snappy-mastermind-model → "mastermind-model": snappy-faces declares no "mastermind-model" family, so every host draws snappy-mastermind-model's answers as prose — a face to build, not a host to fix snappy-outbound → "outbound": snappy-faces declares no "outbound" family, so every host draws snappy-outbound's answers as prose — a face to build, not a host to fix snappy-remotion → "remotion": snappy-faces declares no "remotion" family, so every host draws snappy-remotion's answers as prose — a face to build, not a host to fix snappy-settings → "settings": snappy-faces declares no "settings" family, so every host draws snappy-settings's answers as prose — a face to build, not a host to fix snappy-testimonials → "testimonials": snappy-faces declares no "testimonials" family, so every host draws snappy-testimonials's answers as prose — a face to build, not a host to fix ``` ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
ChatGPT web app could not be driven at all · 4 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Every ask to ChatGPT web app is unreachable: no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one.
chatgpt/gmail-inbox: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
chatgpt/skool-feed: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
chatgpt/linkedin-profile: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
chatgpt/linkedin-decide: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
bashagent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt
Until that has been run this cluster cannot be closed by a lane, and reporting
it closed is the status/artifact defect ⟨CLAUDE.md §10⟩.
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-not-driven-chatgpt **ChatGPT web app could not be driven at all** · 4 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Every ask to ChatGPT web app is unreachable: no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one. ## The measured cells ``` chatgpt/gmail-inbox: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. chatgpt/skool-feed: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. chatgpt/linkedin-profile: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. chatgpt/linkedin-decide: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. ``` ## The one line the owner runs first ```bash agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt ``` Until that has been run this cluster cannot be closed by a lane, and reporting it closed is the status/artifact defect ⟨CLAUDE.md §10⟩. ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
Claude.ai web app could not be driven at all · 4 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Every ask to Claude.ai web app is unreachable: no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one.
claude/gmail-inbox: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
claude/skool-feed: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
claude/linkedin-profile: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
claude/linkedin-decide: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.
bashagent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude
Until that has been run this cluster cannot be closed by a lane, and reporting
it closed is the status/artifact defect ⟨CLAUDE.md §10⟩.
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-not-driven-claude **Claude.ai web app could not be driven at all** · 4 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Every ask to Claude.ai web app is unreachable: no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one. ## The measured cells ``` claude/gmail-inbox: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. claude/skool-feed: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. claude/linkedin-profile: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. claude/linkedin-decide: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩. ``` ## The one line the owner runs first ```bash agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude ``` Until that has been run this cluster cannot be closed by a lane, and reporting it closed is the status/artifact defect ⟨CLAUDE.md §10⟩. ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
Codex CLI could not be driven at all · 4 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Every ask to Codex CLI is unreachable: codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.). Until that is true the host is a blank column, and a blank column is not a passing one.
codex/gmail-inbox: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)
codex/skool-feed: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)
codex/linkedin-profile: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)
codex/linkedin-decide: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)
bashopen https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>
Until that has been run this cluster cannot be closed by a lane, and reporting
it closed is the status/artifact defect ⟨CLAUDE.md §10⟩.
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-not-driven-codex **Codex CLI could not be driven at all** · 4 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Every ask to Codex CLI is unreachable: codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.). Until that is true the host is a blank column, and a blank column is not a passing one. ## The measured cells ``` codex/gmail-inbox: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) codex/skool-feed: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host) codex/linkedin-profile: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host) codex/linkedin-decide: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host) ``` ## The one line the owner runs first ```bash open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file> ``` Until that has been run this cluster cannot be closed by a lane, and reporting it closed is the status/artifact defect ⟨CLAUDE.md §10⟩. ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
Snappy menu bar could not be driven at all · 4 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Every ask to Snappy menu bar is unreachable: the bar's own search offered no skill row for "my gmail inbox" within 60 s, so there was nothing to press. Until that is true the host is a blank column, and a blank column is not a passing one.
menubar/gmail-inbox: not-driven — the bar's own search offered no skill row for "my gmail inbox" within 60 s, so there was nothing to press
menubar/skool-feed: not-driven — the bar's own search offered no skill row for "my skool feed" within 60 s, so there was nothing to press
menubar/linkedin-profile: not-driven — the bar's own search offered no skill row for "my linkedin profile" within 60 s, so there was nothing to press
menubar/linkedin-decide: not-driven — no face mounted in the bar's stage for "show linkedin-decision" (no iframe appeared within 90 s); #snappy-faces-root held -1 element(s); the page logged: the runner refused
bashcd ~/Projects/snappy-runner/apps/snappy-bar && ./scripts/make-app.sh && open dist/Snappy.app # then: npx tsx api.ts hosts --only menubar --token-file <file>
Until that has been run this cluster cannot be closed by a lane, and reporting
it closed is the status/artifact defect ⟨CLAUDE.md §10⟩.
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-not-driven-menubar **Snappy menu bar could not be driven at all** · 4 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Every ask to Snappy menu bar is unreachable: the bar's own search offered no skill row for "my gmail inbox" within 60 s, so there was nothing to press. Until that is true the host is a blank column, and a blank column is not a passing one. ## The measured cells ``` menubar/gmail-inbox: not-driven — the bar's own search offered no skill row for "my gmail inbox" within 60 s, so there was nothing to press menubar/skool-feed: not-driven — the bar's own search offered no skill row for "my skool feed" within 60 s, so there was nothing to press menubar/linkedin-profile: not-driven — the bar's own search offered no skill row for "my linkedin profile" within 60 s, so there was nothing to press menubar/linkedin-decide: not-driven — no face mounted in the bar's stage for "show linkedin-decision" (no iframe appeared within 90 s); #snappy-faces-root held -1 element(s); the page logged: the runner refused ``` ## The one line the owner runs first ```bash cd ~/Projects/snappy-runner/apps/snappy-bar && ./scripts/make-app.sh && open dist/Snappy.app # then: npx tsx api.ts hosts --only menubar --token-file <file> ``` Until that has been run this cluster cannot be closed by a lane, and reporting it closed is the status/artifact defect ⟨CLAUDE.md §10⟩. ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
Telegram on the phone could not be driven at all · 3 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Every ask to Telegram on the phone is unreachable: the runner's search named no verb for gmail-inbox, so there is nothing for an image host to draw.. Until that is true the host is a blank column, and a blank column is not a passing one.
telegram/gmail-inbox: not-driven — the runner's search named no verb for gmail-inbox, so there is nothing for an image host to draw.
telegram/skool-feed: not-driven — the runner's search named no verb for skool-feed, so there is nothing for an image host to draw.
telegram/linkedin-profile: not-driven — the runner's search named no verb for linkedin-profile, so there is nothing for an image host to draw.
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-not-driven-telegram **Telegram on the phone could not be driven at all** · 3 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Every ask to Telegram on the phone is unreachable: the runner's search named no verb for gmail-inbox, so there is nothing for an image host to draw.. Until that is true the host is a blank column, and a blank column is not a passing one. ## The measured cells ``` telegram/gmail-inbox: not-driven — the runner's search named no verb for gmail-inbox, so there is nothing for an image host to draw. telegram/skool-feed: not-driven — the runner's search named no verb for skool-feed, so there is nothing for an image host to draw. telegram/linkedin-profile: not-driven — the runner's search named no verb for linkedin-profile, so there is nothing for an image host to draw. ``` ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
1 of the four asks never reach a named face on the runner itself · 1 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Before any host is blamed, the same ask was put to the runner's own MCP door. These came back with no face named, so no host on any row could have drawn them — the defect is under the hosts, not in them.
linkedin-decide: snappy-linkedin post → not driven, `post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it.
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-road-names-no-face **1 of the four asks never reach a named face on the runner itself** · 1 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Before any host is blamed, the same ask was put to the runner's own MCP door. These came back with no face named, so no host on any row could have drawn them — the defect is under the hosts, not in them. ## The measured cells ``` linkedin-decide: snappy-linkedin post → not driven, `post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it. ``` ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
56 hand(s) have no verb the face road can even shape · 56 cell(s)
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
Later, never Publish.git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.The runner asks two questions in order — does this verb's word fold onto one of the manifest's shapes, and does the hand's name land on a family. These hands fail the FIRST one, so they never reach the family question and answer prose in every host however many families exist. Most are legitimately faceless (snappy-ffmpeg concat has no picture of itself). The ones that are not — snappy-krisp fetch-meetings is the worked example — are fixed by one word in the runner's own VERB_SHAPE fold, never by a new table.
snappy-ads: metrics, targets
snappy-agent-host: start, prompt, status, cancel, sessions, models
snappy-ai-models: chat, embed, image
snappy-analytics: scorecard
snappy-artifact-loop: channels, choose, envelope, kit, parse
snappy-ax: at-point, find, focused, macos-use, permissions, press
snappy-box: call, routes, sql
snappy-browse: clear-requests, click, close, extract, metrics, navigate
snappy-cleanshot: area, ax, crop, fullscreen, grab, grab-app
snappy-content: atoms, draft-new, metrics, status, tick, tick-all
snappy-course: module, modules
snappy-data-hygiene: classify, prompt, review
snappy-database: describe, query, tables
snappy-deploy: status, vercel
snappy-desktop: run, screenshot
snappy-dom-cartographer: eval, log, patch, snap, start, verify
snappy-faces: draw, lang, faces, grammar
snappy-ffmpeg: burn-subs, compress, concat, extract-audio, loudnorm, overlay
snappy-gemini: describe, generate, image
snappy-hands: census
snappy-image: canva, course-folder, generate, logo, logos, metrics
snappy-inbound: classify, slack-joins
snappy-inbox-sweep: gmail-personal, gmail-work, inbox-zero, send-skool, send-slack, slack
snappy-infra: ssh, vercel, xano
snappy-krisp: fetch-meetings, fetch-document, fetch-action-items, metrics
snappy-libretto: record, lessons, lesson, replay, promote
snappy-maintenance: all, check, ssl
snappy-mine: files, manifest, meetings-by, metrics, pending, persist
snappy-nightshift: bar, blocked, board, log, preflight, reap
snappy-offer: icp, offer, pricing
snappy-openrouter: chat, fallback
snappy-ops: ab, actions, catchup, dashboard, diff-machine, dispatch
snappy-os-operator: approvals, approve, deny, deploy-truth, grant-create, grant-update
snappy-pipeline: avatars, batch-overview, completeness, crash-patterns, data-landing, duplicates
snappy-playbook: week, weeks
snappy-positioning: banned, check, cite, flow, tone
snappy-post: linkedin, slack, telegram
snappy-publish: deploys, status
snappy-report-publish: gen-batch, publish, style, update-index, verify
snappy-review-pages: kit, provenance
snappy-sales: clients, leads, log
snappy-scheduling: available, propose
snappy-sensors: dump, section, stale
snappy-session-close: handoff, reconcile
snappy-skill: scaffold
snappy-sync: share
snappy-thumbnails: audit, brief, generate, set, variants
snappy-tool-design: lint, example, probe, render, loop, hosts
snappy-update: commits, notify
snappy-video: caption, clip, transcribe
snappy-voice-control: devices, permissions, pick, record, stream-url, transcribe
snappy-walkthrough: annotate, capture, gates, lesson, run, status
snappy-website: deploys, health
snappy-xano-dashboard: logs, open, test
snappy-xano-mcp: health, test
npx tsx skills/snappy-tool-design/api.ts hosts before and after, with this cluster's cell count falling./tmp/hosts-battery.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — hosts-verb-folds-onto-no-shape **56 hand(s) have no verb the face road can even shape** · 56 cell(s) ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the driver or the face family named below and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — a press is `Later`, never `Publish`. - NEVER export or commit the owner's browser state. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism The runner asks two questions in order — does this verb's word fold onto one of the manifest's shapes, and does the hand's name land on a family. These hands fail the FIRST one, so they never reach the family question and answer prose in every host however many families exist. Most are legitimately faceless (`snappy-ffmpeg concat` has no picture of itself). The ones that are not — `snappy-krisp fetch-meetings` is the worked example — are fixed by one word in the runner's own VERB_SHAPE fold, never by a new table. ## The measured cells ``` snappy-ads: metrics, targets snappy-agent-host: start, prompt, status, cancel, sessions, models snappy-ai-models: chat, embed, image snappy-analytics: scorecard snappy-artifact-loop: channels, choose, envelope, kit, parse snappy-ax: at-point, find, focused, macos-use, permissions, press snappy-box: call, routes, sql snappy-browse: clear-requests, click, close, extract, metrics, navigate snappy-cleanshot: area, ax, crop, fullscreen, grab, grab-app snappy-content: atoms, draft-new, metrics, status, tick, tick-all snappy-course: module, modules snappy-data-hygiene: classify, prompt, review snappy-database: describe, query, tables snappy-deploy: status, vercel snappy-desktop: run, screenshot snappy-dom-cartographer: eval, log, patch, snap, start, verify snappy-faces: draw, lang, faces, grammar snappy-ffmpeg: burn-subs, compress, concat, extract-audio, loudnorm, overlay snappy-gemini: describe, generate, image snappy-hands: census snappy-image: canva, course-folder, generate, logo, logos, metrics snappy-inbound: classify, slack-joins snappy-inbox-sweep: gmail-personal, gmail-work, inbox-zero, send-skool, send-slack, slack snappy-infra: ssh, vercel, xano snappy-krisp: fetch-meetings, fetch-document, fetch-action-items, metrics snappy-libretto: record, lessons, lesson, replay, promote snappy-maintenance: all, check, ssl snappy-mine: files, manifest, meetings-by, metrics, pending, persist snappy-nightshift: bar, blocked, board, log, preflight, reap snappy-offer: icp, offer, pricing snappy-openrouter: chat, fallback snappy-ops: ab, actions, catchup, dashboard, diff-machine, dispatch snappy-os-operator: approvals, approve, deny, deploy-truth, grant-create, grant-update snappy-pipeline: avatars, batch-overview, completeness, crash-patterns, data-landing, duplicates snappy-playbook: week, weeks snappy-positioning: banned, check, cite, flow, tone snappy-post: linkedin, slack, telegram snappy-publish: deploys, status snappy-report-publish: gen-batch, publish, style, update-index, verify snappy-review-pages: kit, provenance snappy-sales: clients, leads, log snappy-scheduling: available, propose snappy-sensors: dump, section, stale snappy-session-close: handoff, reconcile snappy-skill: scaffold snappy-sync: share snappy-thumbnails: audit, brief, generate, set, variants snappy-tool-design: lint, example, probe, render, loop, hosts snappy-update: commits, notify snappy-video: caption, clip, transcribe snappy-voice-control: devices, permissions, pick, record, stream-url, transcribe snappy-walkthrough: annotate, capture, gates, lesson, run, status snappy-website: deploys, health snappy-xano-dashboard: logs, open, test snappy-xano-mcp: health, test ``` ## The anti-fix — what would look like progress and is not - Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote. - Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself. - Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts hosts` before and after, with this cluster's cell count falling. 2. The screenshot path for every cell that turned green, under `/tmp/hosts-battery`. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. The whole matrix pasted, not the one row — no other cell may fall.
Rule 10 · evidence mode static · 74 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.One term names each concept
This is ONE root cause across 74 skill(s). Fix the mechanism, not the
74 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 74.
lint --all#snappy-ads: mixed synonym sets: endpoint/url/route/path
snappy-agent-host: mixed synonym sets: endpoint/url/route/path, field/box/element
snappy-ai-models: mixed synonym sets: endpoint/url/route/path
snappy-analytics: mixed synonym sets: endpoint/url/route/path
snappy-api-sniffer: mixed synonym sets: endpoint/url/route/path
snappy-artifact-loop: mixed synonym sets: endpoint/url/route/path, field/box/element
snappy-ax: mixed synonym sets: endpoint/url/route/path
snappy-blog: mixed synonym sets: endpoint/url/route/path
snappy-box: mixed synonym sets: endpoint/url/route/path
snappy-browse: mixed synonym sets: endpoint/url/route/path, field/box/element
snappy-chain: mixed synonym sets: endpoint/url/route/path
snappy-cleanshot: mixed synonym sets: endpoint/url/route/path, field/box/element
… and 62 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:1 — snappy-ads/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:1 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:1 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-api-sniffer/api.ts:1 — snappy-api-sniffer/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:1 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:1 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:1 — snappy-blog/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:1 — snappy-box/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:1 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-chain/api.ts:1 — snappy-chain/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1 — snappy-cleanshotnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 10's FAIL count falling from 74.lint <skill> line for two skills in the cluster, showing rule 10 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r10-one-term-names-each-concept **Rule 10** · evidence mode `static` · **74 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism One term names each concept This is ONE root cause across 74 skill(s). Fix the mechanism, not the 74 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 74. ## The measured evidence, verbatim from `lint --all` ``` snappy-ads: mixed synonym sets: endpoint/url/route/path snappy-agent-host: mixed synonym sets: endpoint/url/route/path, field/box/element snappy-ai-models: mixed synonym sets: endpoint/url/route/path snappy-analytics: mixed synonym sets: endpoint/url/route/path snappy-api-sniffer: mixed synonym sets: endpoint/url/route/path snappy-artifact-loop: mixed synonym sets: endpoint/url/route/path, field/box/element snappy-ax: mixed synonym sets: endpoint/url/route/path snappy-blog: mixed synonym sets: endpoint/url/route/path snappy-box: mixed synonym sets: endpoint/url/route/path snappy-browse: mixed synonym sets: endpoint/url/route/path, field/box/element snappy-chain: mixed synonym sets: endpoint/url/route/path snappy-cleanshot: mixed synonym sets: endpoint/url/route/path, field/box/element … and 62 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:1` — snappy-ads - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:1` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:1` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-api-sniffer/api.ts:1` — snappy-api-sniffer - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:1` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:1` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:1` — snappy-blog - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:1` — snappy-box - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:1` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-chain/api.ts:1` — snappy-chain - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1` — snappy-cleanshot ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 10's FAIL count falling from 74. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 10 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 11 · evidence mode static · 30 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Each task presents one default, not a menu
This is ONE root cause across 30 skill(s). Fix the mechanism, not the
30 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 30.
lint --all#snappy-agent-host: 2 line(s) present three-way alternative menus
snappy-ax: 1 line(s) present three-way alternative menus
snappy-blog: 3 line(s) present three-way alternative menus
snappy-cleanshot: 1 line(s) present three-way alternative menus
snappy-client-scott: 1 line(s) present three-way alternative menus
snappy-data-hygiene: 1 line(s) present three-way alternative menus
snappy-desktop: 1 line(s) present three-way alternative menus
snappy-docs: 2 line(s) present three-way alternative menus
snappy-email: 1 line(s) present three-way alternative menus
snappy-faces: 2 line(s) present three-way alternative menus
snappy-gemini: 2 line(s) present three-way alternative menus
snappy-libretto: 2 line(s) present three-way alternative menus
… and 18 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150 — snappy-blog/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538 — snappy-cleanshot/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58 — snappy-client-scott/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-data-hygiene/api.ts:335 — snappy-data-hygiene/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-desktop/api.ts:78 — snappy-desktop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-docs/api.ts:286 — snappy-docs/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-email/api.ts:524 — snappy-email/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50 — snappy-faces/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-gemini/api.ts:191 — snappy-gemini/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-libretto/api.ts:619 — snappy-librettonpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 11's FAIL count falling from 30.lint <skill> line for two skills in the cluster, showing rule 11 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r11-each-task-presents-one-default-not-a-menu **Rule 11** · evidence mode `static` · **30 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Each task presents one default, not a menu This is ONE root cause across 30 skill(s). Fix the mechanism, not the 30 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 30. ## The measured evidence, verbatim from `lint --all` ``` snappy-agent-host: 2 line(s) present three-way alternative menus snappy-ax: 1 line(s) present three-way alternative menus snappy-blog: 3 line(s) present three-way alternative menus snappy-cleanshot: 1 line(s) present three-way alternative menus snappy-client-scott: 1 line(s) present three-way alternative menus snappy-data-hygiene: 1 line(s) present three-way alternative menus snappy-desktop: 1 line(s) present three-way alternative menus snappy-docs: 2 line(s) present three-way alternative menus snappy-email: 1 line(s) present three-way alternative menus snappy-faces: 2 line(s) present three-way alternative menus snappy-gemini: 2 line(s) present three-way alternative menus snappy-libretto: 2 line(s) present three-way alternative menus … and 18 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150` — snappy-blog - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538` — snappy-cleanshot - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58` — snappy-client-scott - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-data-hygiene/api.ts:335` — snappy-data-hygiene - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-desktop/api.ts:78` — snappy-desktop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-docs/api.ts:286` — snappy-docs - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-email/api.ts:524` — snappy-email - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50` — snappy-faces - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-gemini/api.ts:191` — snappy-gemini - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-libretto/api.ts:619` — snappy-libretto ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 11's FAIL count falling from 30. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 11 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 12 · evidence mode static · 5 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Description contains no unauthorised imperative
This is ONE root cause across 5 skill(s). Fix the mechanism, not the
5 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 5.
lint --all#snappy-hands: unauthorised description imperatives: read
snappy-pipeline: unauthorised description imperatives: read
snappy-slack: unauthorised description imperatives: read
snappy-statechange: unauthorised description imperatives: read
snappy-sync: unauthorised description imperatives: upload
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-hands/api.ts:16 — snappy-hands/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-pipeline/api.ts:90 — snappy-pipeline/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-slack/api.ts:10 — snappy-slack/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-statechange/api.ts:179 — snappy-statechange/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-sync/api.ts:1 — snappy-syncnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 12's FAIL count falling from 5.lint <skill> line for two skills in the cluster, showing rule 12 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r12-description-contains-no-unauthorised-imperative **Rule 12** · evidence mode `static` · **5 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Description contains no unauthorised imperative This is ONE root cause across 5 skill(s). Fix the mechanism, not the 5 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 5. ## The measured evidence, verbatim from `lint --all` ``` snappy-hands: unauthorised description imperatives: read snappy-pipeline: unauthorised description imperatives: read snappy-slack: unauthorised description imperatives: read snappy-statechange: unauthorised description imperatives: read snappy-sync: unauthorised description imperatives: upload ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-hands/api.ts:16` — snappy-hands - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-pipeline/api.ts:90` — snappy-pipeline - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-slack/api.ts:10` — snappy-slack - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-statechange/api.ts:179` — snappy-statechange - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-sync/api.ts:1` — snappy-sync ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 12's FAIL count falling from 5. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 12 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 13 · evidence mode static · 1 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Arguments carry no descriptions in inputSchema, so the model guesses their meaning from the name alone.
This is ONE root cause across 1 skill(s). Fix the mechanism, not the
1 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 1.
lint --all#snappy-faces: draw.kind missing description; lang.kind missing description
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50 — snappy-facesnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 13's FAIL count falling from 1.lint <skill> line for two skills in the cluster, showing rule 13 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r13-argument-without-a-description **Rule 13** · evidence mode `static` · **1 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Arguments carry no descriptions in inputSchema, so the model guesses their meaning from the name alone. This is ONE root cause across 1 skill(s). Fix the mechanism, not the 1 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 1. ## The measured evidence, verbatim from `lint --all` ``` snappy-faces: draw.kind missing description; lang.kind missing description ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50` — snappy-faces ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 13's FAIL count falling from 1. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 13 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 15 · evidence mode probe · 5 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Near-miss keys refuse with the correct contract key
This is ONE root cause across 5 skill(s). Fix the mechanism, not the
5 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 5.
lint --all#snappy-imessage: 0 refusal(s) omit the correcting key; 1 uncovered; 1 case(s) left unfuzzed because the verb has an outside effect
snappy-maintenance: 1 refusal(s) omit the correcting key; 0 uncovered; 0 case(s) left unfuzzed because the verb has an outside effect
snappy-pipeline: 1 refusal(s) omit the correcting key; 0 uncovered; 0 case(s) left unfuzzed because the verb has an outside effect
snappy-testimonials: 0 refusal(s) omit the correcting key; 1 uncovered; 0 case(s) left unfuzzed because the verb has an outside effect
snappy-whatsapp: 0 refusal(s) omit the correcting key; 1 uncovered; 4 case(s) left unfuzzed because the verb has an outside effect
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-imessage/api.ts:58 — snappy-imessage/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-maintenance/api.ts:100 — snappy-maintenance/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-pipeline/api.ts:100 — snappy-pipeline/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-testimonials/api.ts:134 — snappy-testimonials/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-whatsapp/api.ts:298 — snappy-whatsappnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 15's FAIL count falling from 5.lint <skill> line for two skills in the cluster, showing rule 15 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r15-near-miss-keys-refuse-with-the-correct-contract-key **Rule 15** · evidence mode `probe` · **5 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Near-miss keys refuse with the correct contract key This is ONE root cause across 5 skill(s). Fix the mechanism, not the 5 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 5. ## The measured evidence, verbatim from `lint --all` ``` snappy-imessage: 0 refusal(s) omit the correcting key; 1 uncovered; 1 case(s) left unfuzzed because the verb has an outside effect snappy-maintenance: 1 refusal(s) omit the correcting key; 0 uncovered; 0 case(s) left unfuzzed because the verb has an outside effect snappy-pipeline: 1 refusal(s) omit the correcting key; 0 uncovered; 0 case(s) left unfuzzed because the verb has an outside effect snappy-testimonials: 0 refusal(s) omit the correcting key; 1 uncovered; 0 case(s) left unfuzzed because the verb has an outside effect snappy-whatsapp: 0 refusal(s) omit the correcting key; 1 uncovered; 4 case(s) left unfuzzed because the verb has an outside effect ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-imessage/api.ts:58` — snappy-imessage - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-maintenance/api.ts:100` — snappy-maintenance - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-pipeline/api.ts:100` — snappy-pipeline - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-testimonials/api.ts:134` — snappy-testimonials - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-whatsapp/api.ts:298` — snappy-whatsapp ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 15's FAIL count falling from 5. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 15 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 16 · evidence mode static · 1 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Runner-known values are not required arguments
This is ONE root cause across 1 skill(s). Fix the mechanism, not the
1 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 1.
lint --all#snappy-agent-host: runner-known required args: start.cwd
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1028 — snappy-agent-hostnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 16's FAIL count falling from 1.lint <skill> line for two skills in the cluster, showing rule 16 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r16-runner-known-values-are-not-required-arguments **Rule 16** · evidence mode `static` · **1 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Runner-known values are not required arguments This is ONE root cause across 1 skill(s). Fix the mechanism, not the 1 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 1. ## The measured evidence, verbatim from `lint --all` ``` snappy-agent-host: runner-known required args: start.cwd ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1028` — snappy-agent-host ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 16's FAIL count falling from 1. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 16 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 18 · evidence mode static · 1 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Verbs declare no effect class, so nothing downstream can derive annotations or decide what must stage.
This is ONE root cause across 1 skill(s). Fix the mechanism, not the
1 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 1.
lint --all#snappy-faces: missing class: draw, lang, faces, grammar, sheet
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:2 — snappy-facesnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 18's FAIL count falling from 1.lint <skill> line for two skills in the cluster, showing rule 18 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r18-verb-without-an-effect-class **Rule 18** · evidence mode `static` · **1 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Verbs declare no effect class, so nothing downstream can derive annotations or decide what must stage. This is ONE root cause across 1 skill(s). Fix the mechanism, not the 1 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 1. ## The measured evidence, verbatim from `lint --all` ``` snappy-faces: missing class: draw, lang, faces, grammar, sheet ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:2` — snappy-faces ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 18's FAIL count falling from 1. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 18 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 19 · evidence mode static · 1 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Published MCP annotations were guessed rather than derived from the declared effect class.
This is ONE root cause across 1 skill(s). Fix the mechanism, not the
1 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 1.
lint --all#snappy-faces: annotation defaults mismatch: draw, lang, faces, grammar, sheet
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:2 — snappy-facesnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 19's FAIL count falling from 1.lint <skill> line for two skills in the cluster, showing rule 19 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r19-annotations-not-derived-from-class **Rule 19** · evidence mode `static` · **1 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Published MCP annotations were guessed rather than derived from the declared effect class. This is ONE root cause across 1 skill(s). Fix the mechanism, not the 1 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 1. ## The measured evidence, verbatim from `lint --all` ``` snappy-faces: annotation defaults mismatch: draw, lang, faces, grammar, sheet ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:2` — snappy-faces ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 19's FAIL count falling from 1. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 19 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 20 · evidence mode static · 2 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Read and write are separate verbs
This is ONE root cause across 2 skill(s). Fix the mechanism, not the
2 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 2.
lint --all#snappy-libretto: mode-changing args on: replay
snappy-nightshift: mode-changing args on: reap
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-libretto/api.ts:4 — snappy-libretto/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-nightshift/api.ts:379 — snappy-nightshiftnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 20's FAIL count falling from 2.lint <skill> line for two skills in the cluster, showing rule 20 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r20-read-and-write-are-separate-verbs **Rule 20** · evidence mode `static` · **2 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Read and write are separate verbs This is ONE root cause across 2 skill(s). Fix the mechanism, not the 2 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 2. ## The measured evidence, verbatim from `lint --all` ``` snappy-libretto: mode-changing args on: replay snappy-nightshift: mode-changing args on: reap ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-libretto/api.ts:4` — snappy-libretto - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-nightshift/api.ts:379` — snappy-nightshift ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 20's FAIL count falling from 2. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 20 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 23 · evidence mode probe · 10 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Responses obey hard and soft caps
This is ONE root cause across 10 skill(s). Fix the mechanism, not the
10 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 10.
lint --all#snappy-client-orbiter: 1 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-client-scott: 1 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-client-total: 1 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-clients: 3 hard-cap breach(es); 2 unannounced default-read soft-cap breach(es)
snappy-faces: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-image: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-inbox-sweep: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-ops: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)
snappy-sales: 1 hard-cap breach(es); 2 unannounced default-read soft-cap breach(es)
snappy-skool: 0 hard-cap breach(es); 8 unannounced default-read soft-cap breach(es)
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-orbiter/api.ts:145 — snappy-client-orbiter/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58 — snappy-client-scott/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-total/api.ts:196 — snappy-client-total/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-clients/api.ts:82 — snappy-clients/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50 — snappy-faces/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-image/api.ts:458 — snappy-image/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:673 — snappy-inbox-sweep/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1650 — snappy-ops/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-sales/api.ts:109 — snappy-sales/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-skool/api.ts:51 — snappy-skoolnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 23's FAIL count falling from 10.lint <skill> line for two skills in the cluster, showing rule 23 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r23-responses-obey-hard-and-soft-caps **Rule 23** · evidence mode `probe` · **10 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Responses obey hard and soft caps This is ONE root cause across 10 skill(s). Fix the mechanism, not the 10 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 10. ## The measured evidence, verbatim from `lint --all` ``` snappy-client-orbiter: 1 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-client-scott: 1 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-client-total: 1 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-clients: 3 hard-cap breach(es); 2 unannounced default-read soft-cap breach(es) snappy-faces: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-image: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-inbox-sweep: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-ops: 0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es) snappy-sales: 1 hard-cap breach(es); 2 unannounced default-read soft-cap breach(es) snappy-skool: 0 hard-cap breach(es); 8 unannounced default-read soft-cap breach(es) ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-orbiter/api.ts:145` — snappy-client-orbiter - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58` — snappy-client-scott - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-total/api.ts:196` — snappy-client-total - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-clients/api.ts:82` — snappy-clients - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50` — snappy-faces - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-image/api.ts:458` — snappy-image - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:673` — snappy-inbox-sweep - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1650` — snappy-ops - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-sales/api.ts:109` — snappy-sales - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-skool/api.ts:51` — snappy-skool ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 23's FAIL count falling from 10. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 23 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 24 · evidence mode probe · 3 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.The answer does not shrink when limit shrinks — the argument is accepted and ignored, so an AI that asks for one row pays for all of them.
This is ONE root cause across 3 skill(s). Fix the mechanism, not the
3 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 3.
lint --all#snappy-telegram: unbounded growth: read:non-monotone, messages:non-monotone, updates:non-monotone
snappy-testimonials: unbounded growth: list:non-monotone
snappy-thumbnails: unbounded growth: audit:non-monotone
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-telegram/api.ts:10 — snappy-telegram/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-testimonials/api.ts:6 — snappy-testimonials/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:268 — snappy-thumbnailsnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 24's FAIL count falling from 3.lint <skill> line for two skills in the cluster, showing rule 24 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r24-limit-does-not-bound-the-answer **Rule 24** · evidence mode `probe` · **3 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism The answer does not shrink when `limit` shrinks — the argument is accepted and ignored, so an AI that asks for one row pays for all of them. This is ONE root cause across 3 skill(s). Fix the mechanism, not the 3 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 3. ## The measured evidence, verbatim from `lint --all` ``` snappy-telegram: unbounded growth: read:non-monotone, messages:non-monotone, updates:non-monotone snappy-testimonials: unbounded growth: list:non-monotone snappy-thumbnails: unbounded growth: audit:non-monotone ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-telegram/api.ts:10` — snappy-telegram - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-testimonials/api.ts:6` — snappy-testimonials - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:268` — snappy-thumbnails ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 24's FAIL count falling from 3. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 24 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 31 · evidence mode probe · 8 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.A refusal arrives without the full outcome/code/message/fix envelope, so the caller is told it failed but not what to do next.
This is ONE root cause across 8 skill(s). Fix the mechanism, not the
8 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 8.
lint --all#snappy-artifact-loop: refusals schema-checked for outcome/code/message/fix
snappy-imessage: refusals schema-checked for outcome/code/message/fix
snappy-inbox-sweep: refusals schema-checked for outcome/code/message/fix
snappy-mine: refusals schema-checked for outcome/code/message/fix
snappy-nightshift: refusals schema-checked for outcome/code/message/fix
snappy-ops: refusals schema-checked for outcome/code/message/fix
snappy-thumbnails: refusals schema-checked for outcome/code/message/fix
snappy-tool-design: refusals schema-checked for outcome/code/message/fix
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-imessage/api.ts:58 — snappy-imessage/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:673 — snappy-inbox-sweep/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-mine/api.ts:347 — snappy-mine/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-nightshift/api.ts:351 — snappy-nightshift/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1650 — snappy-ops/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:302 — snappy-thumbnails/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-tool-design/api.ts:164 — snappy-tool-designnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 31's FAIL count falling from 8.lint <skill> line for two skills in the cluster, showing rule 31 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r31-refusal-envelope-incomplete **Rule 31** · evidence mode `probe` · **8 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism A refusal arrives without the full outcome/code/message/fix envelope, so the caller is told it failed but not what to do next. This is ONE root cause across 8 skill(s). Fix the mechanism, not the 8 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 8. ## The measured evidence, verbatim from `lint --all` ``` snappy-artifact-loop: refusals schema-checked for outcome/code/message/fix snappy-imessage: refusals schema-checked for outcome/code/message/fix snappy-inbox-sweep: refusals schema-checked for outcome/code/message/fix snappy-mine: refusals schema-checked for outcome/code/message/fix snappy-nightshift: refusals schema-checked for outcome/code/message/fix snappy-ops: refusals schema-checked for outcome/code/message/fix snappy-thumbnails: refusals schema-checked for outcome/code/message/fix snappy-tool-design: refusals schema-checked for outcome/code/message/fix ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-imessage/api.ts:58` — snappy-imessage - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:673` — snappy-inbox-sweep - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-mine/api.ts:347` — snappy-mine - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-nightshift/api.ts:351` — snappy-nightshift - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1650` — snappy-ops - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:302` — snappy-thumbnails - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-tool-design/api.ts:164` — snappy-tool-design ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 31's FAIL count falling from 8. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 31 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 32 · evidence mode probe · 15 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Refusals name a literal from the valid domain
This is ONE root cause across 15 skill(s). Fix the mechanism, not the
15 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 15.
lint --all#snappy-artifact-loop: refusals checked against contract literals
snappy-database: refusals checked against contract literals
snappy-imessage: refusals checked against contract literals
snappy-inbox-sweep: refusals checked against contract literals
snappy-maintenance: refusals checked against contract literals
snappy-mine: refusals checked against contract literals
snappy-nightshift: refusals checked against contract literals
snappy-ops: refusals checked against contract literals
snappy-os-operator: refusals checked against contract literals
snappy-pipeline: refusals checked against contract literals
snappy-thumbnails: refusals checked against contract literals
… and 3 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-database/api.ts:81 — snappy-database/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-imessage/api.ts:58 — snappy-imessage/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:673 — snappy-inbox-sweep/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-maintenance/api.ts:100 — snappy-maintenance/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-mine/api.ts:347 — snappy-mine/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-nightshift/api.ts:351 — snappy-nightshift/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1650 — snappy-ops/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-os-operator/api.ts:248 — snappy-os-operator/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-pipeline/api.ts:100 — snappy-pipeline/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:302 — snappy-thumbnailsnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 32's FAIL count falling from 15.lint <skill> line for two skills in the cluster, showing rule 32 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r32-refusals-name-a-literal-from-the-valid-domain **Rule 32** · evidence mode `probe` · **15 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Refusals name a literal from the valid domain This is ONE root cause across 15 skill(s). Fix the mechanism, not the 15 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 15. ## The measured evidence, verbatim from `lint --all` ``` snappy-artifact-loop: refusals checked against contract literals snappy-database: refusals checked against contract literals snappy-imessage: refusals checked against contract literals snappy-inbox-sweep: refusals checked against contract literals snappy-maintenance: refusals checked against contract literals snappy-mine: refusals checked against contract literals snappy-nightshift: refusals checked against contract literals snappy-ops: refusals checked against contract literals snappy-os-operator: refusals checked against contract literals snappy-pipeline: refusals checked against contract literals snappy-thumbnails: refusals checked against contract literals … and 3 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-database/api.ts:81` — snappy-database - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-imessage/api.ts:58` — snappy-imessage - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:673` — snappy-inbox-sweep - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-maintenance/api.ts:100` — snappy-maintenance - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-mine/api.ts:347` — snappy-mine - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-nightshift/api.ts:351` — snappy-nightshift - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1650` — snappy-ops - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-os-operator/api.ts:248` — snappy-os-operator - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-pipeline/api.ts:100` — snappy-pipeline - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:302` — snappy-thumbnails ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 32's FAIL count falling from 15. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 32 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 33 · evidence mode static · 1 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Refusal codes form one tested closed table
This is ONE root cause across 1 skill(s). Fix the mechanism, not the
1 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 1.
lint --all#snappy-faces: HAND_CONTRACT.refusals missing
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50 — snappy-facesnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 33's FAIL count falling from 1.lint <skill> line for two skills in the cluster, showing rule 33 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r33-refusal-codes-form-one-tested-closed-table **Rule 33** · evidence mode `static` · **1 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Refusal codes form one tested closed table This is ONE root cause across 1 skill(s). Fix the mechanism, not the 1 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 1. ## The measured evidence, verbatim from `lint --all` ``` snappy-faces: HAND_CONTRACT.refusals missing ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50` — snappy-faces ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 33's FAIL count falling from 1. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 33 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 35 · evidence mode static · 4 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Requirements are declared from executable credential reads
This is ONE root cause across 4 skill(s). Fix the mechanism, not the
4 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 4.
lint --all#snappy-client-orbiter: declared-not-required=[SLACK_BOT_TOKEN]; required-not-declared=[]
snappy-client-scott: declared-not-required=[SLACK_BOT_TOKEN]; required-not-declared=[]
snappy-client-total: declared-not-required=[SLACK_BOT_TOKEN]; required-not-declared=[]
snappy-outbound: declared-not-required=[GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_SERVICE_ACCOUNT_KEY, ROBERT_PHONE, SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN, TELEGRAM_ROBERT_CHAT_ID, WHATSAPP_PHONE_ID, WHATSAPP_TOKEN]; required-not-declared=[]
/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-client-orbiter/api.ts:145 — snappy-client-orbiter/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-client-scott/api.ts:58 — snappy-client-scott/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-client-total/api.ts:196 — snappy-client-total/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-outbound/api.ts:247 — snappy-outboundnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 35's FAIL count falling from 4.lint <skill> line for two skills in the cluster, showing rule 35 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r35-requirements-are-declared-from-executable-credential-reads **Rule 35** · evidence mode `static` · **4 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Requirements are declared from executable credential reads This is ONE root cause across 4 skill(s). Fix the mechanism, not the 4 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 4. ## The measured evidence, verbatim from `lint --all` ``` snappy-client-orbiter: declared-not-required=[SLACK_BOT_TOKEN]; required-not-declared=[] snappy-client-scott: declared-not-required=[SLACK_BOT_TOKEN]; required-not-declared=[] snappy-client-total: declared-not-required=[SLACK_BOT_TOKEN]; required-not-declared=[] snappy-outbound: declared-not-required=[GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_SERVICE_ACCOUNT_KEY, ROBERT_PHONE, SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN, TELEGRAM_ROBERT_CHAT_ID, WHATSAPP_PHONE_ID, WHATSAPP_TOKEN]; required-not-declared=[] ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-client-orbiter/api.ts:145` — snappy-client-orbiter - `/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-client-scott/api.ts:58` — snappy-client-scott - `/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-client-total/api.ts:196` — snappy-client-total - `/Users/robertboulos/Projects/snappy-kernel-loop-2/skills/snappy-outbound/api.ts:247` — snappy-outbound ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 35's FAIL count falling from 4. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 35 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 39 · evidence mode static · 4 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Verb sets consolidate intent rather than mirror endpoints
This is ONE root cause across 4 skill(s). Fix the mechanism, not the
4 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 4.
lint --all#snappy-client-ray: 4 verbs, 4 literal endpoint(s); one-to-one heuristic=true
snappy-deploy: 2 verbs, 3 literal endpoint(s); one-to-one heuristic=true
snappy-openrouter: 2 verbs, 3 literal endpoint(s); one-to-one heuristic=true
snappy-report-publish: 5 verbs, 4 literal endpoint(s); one-to-one heuristic=true
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-ray/api.ts:322 — snappy-client-ray/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-deploy/api.ts:169 — snappy-deploy/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-openrouter/api.ts:100 — snappy-openrouter/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-report-publish/api.ts:235 — snappy-report-publishnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 39's FAIL count falling from 4.lint <skill> line for two skills in the cluster, showing rule 39 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r39-verb-sets-consolidate-intent-rather-than-mirror-endpoints **Rule 39** · evidence mode `static` · **4 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Verb sets consolidate intent rather than mirror endpoints This is ONE root cause across 4 skill(s). Fix the mechanism, not the 4 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 4. ## The measured evidence, verbatim from `lint --all` ``` snappy-client-ray: 4 verbs, 4 literal endpoint(s); one-to-one heuristic=true snappy-deploy: 2 verbs, 3 literal endpoint(s); one-to-one heuristic=true snappy-openrouter: 2 verbs, 3 literal endpoint(s); one-to-one heuristic=true snappy-report-publish: 5 verbs, 4 literal endpoint(s); one-to-one heuristic=true ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-ray/api.ts:322` — snappy-client-ray - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-deploy/api.ts:169` — snappy-deploy - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-openrouter/api.ts:100` — snappy-openrouter - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-report-publish/api.ts:235` — snappy-report-publish ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 39's FAIL count falling from 4. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 39 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 4 · evidence mode static · 9 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Near-neighbour descriptions cross-reference each other
This is ONE root cause across 9 skill(s). Fix the mechanism, not the
9 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 9.
lint --all#snappy-artifact-loop: near neighbours not named: snappy-whatsapp
snappy-content: near neighbours not named: snappy-slack
snappy-faces: near neighbours not named: snappy-infra, snappy-linkedin, snappy-telegram, snappy-watchtower, snappy-xano-mcp
snappy-hands: near neighbours not named: snappy-imessage
snappy-openrouter: near neighbours not named: snappy-whatsapp
snappy-os-operator: near neighbours not named: snappy-imessage
snappy-resident: near neighbours not named: snappy-whatsapp
snappy-slack: near neighbours not named: snappy-desktop, snappy-docs, snappy-xano-mcp
snappy-xano-mcp: near neighbours not named: snappy-slack
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:1 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-content/api.ts:1 — snappy-content/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:1 — snappy-faces/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-hands/api.ts:1 — snappy-hands/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-openrouter/api.ts:1 — snappy-openrouter/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-os-operator/api.ts:1 — snappy-os-operator/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-resident/api.ts:1 — snappy-resident/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-slack/api.ts:1 — snappy-slack/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-xano-mcp/api.ts:1 — snappy-xano-mcpnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 4's FAIL count falling from 9.lint <skill> line for two skills in the cluster, showing rule 4 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r4-near-neighbour-descriptions-cross-reference-each-other **Rule 4** · evidence mode `static` · **9 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Near-neighbour descriptions cross-reference each other This is ONE root cause across 9 skill(s). Fix the mechanism, not the 9 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 9. ## The measured evidence, verbatim from `lint --all` ``` snappy-artifact-loop: near neighbours not named: snappy-whatsapp snappy-content: near neighbours not named: snappy-slack snappy-faces: near neighbours not named: snappy-infra, snappy-linkedin, snappy-telegram, snappy-watchtower, snappy-xano-mcp snappy-hands: near neighbours not named: snappy-imessage snappy-openrouter: near neighbours not named: snappy-whatsapp snappy-os-operator: near neighbours not named: snappy-imessage snappy-resident: near neighbours not named: snappy-whatsapp snappy-slack: near neighbours not named: snappy-desktop, snappy-docs, snappy-xano-mcp snappy-xano-mcp: near neighbours not named: snappy-slack ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:1` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-content/api.ts:1` — snappy-content - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:1` — snappy-faces - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-hands/api.ts:1` — snappy-hands - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-openrouter/api.ts:1` — snappy-openrouter - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-os-operator/api.ts:1` — snappy-os-operator - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-resident/api.ts:1` — snappy-resident - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-slack/api.ts:1` — snappy-slack - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-xano-mcp/api.ts:1` — snappy-xano-mcp ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 4's FAIL count falling from 9. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 4 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 40 · evidence mode static · 65 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Metadata, skill body, and resources follow three-level budgets
This is ONE root cause across 65 skill(s). Fix the mechanism, not the
65 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 65.
lint --all#snappy-ads: metadata ~181 tokens; body ~4996 tokens
snappy-agent-host: metadata ~161 tokens; body ~13637 tokens
snappy-ai-models: metadata ~260 tokens; body ~4564 tokens
snappy-analytics: metadata ~150 tokens; body ~5826 tokens
snappy-artifact-loop: metadata ~234 tokens; body ~10936 tokens
snappy-ax: metadata ~271 tokens; body ~7806 tokens
snappy-blog: metadata ~190 tokens; body ~4695 tokens
snappy-box: metadata ~197 tokens; body ~3919 tokens
snappy-browse: metadata ~186 tokens; body ~5136 tokens
snappy-calendar: metadata ~195 tokens; body ~4230 tokens
snappy-cleanshot: metadata ~195 tokens; body ~5758 tokens
… and 53 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42 — snappy-ads/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150 — snappy-blog/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124 — snappy-box/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122 — snappy-calendar/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538 — snappy-cleanshotnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 40's FAIL count falling from 65.lint <skill> line for two skills in the cluster, showing rule 40 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r40-metadata-skill-body-and-resources-follow-three-level-budgets **Rule 40** · evidence mode `static` · **65 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Metadata, skill body, and resources follow three-level budgets This is ONE root cause across 65 skill(s). Fix the mechanism, not the 65 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 65. ## The measured evidence, verbatim from `lint --all` ``` snappy-ads: metadata ~181 tokens; body ~4996 tokens snappy-agent-host: metadata ~161 tokens; body ~13637 tokens snappy-ai-models: metadata ~260 tokens; body ~4564 tokens snappy-analytics: metadata ~150 tokens; body ~5826 tokens snappy-artifact-loop: metadata ~234 tokens; body ~10936 tokens snappy-ax: metadata ~271 tokens; body ~7806 tokens snappy-blog: metadata ~190 tokens; body ~4695 tokens snappy-box: metadata ~197 tokens; body ~3919 tokens snappy-browse: metadata ~186 tokens; body ~5136 tokens snappy-calendar: metadata ~195 tokens; body ~4230 tokens snappy-cleanshot: metadata ~195 tokens; body ~5758 tokens … and 53 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42` — snappy-ads - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150` — snappy-blog - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124` — snappy-box - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122` — snappy-calendar - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538` — snappy-cleanshot ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 40's FAIL count falling from 65. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 40 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 43 · evidence mode probe · 7 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.A read is one call, not a queued job
This is ONE root cause across 7 skill(s). Fix the mechanism, not the
7 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 7.
lint --all#snappy-analytics: scorecard:p50>3s-or-missing
snappy-inbox-sweep: gmail-work:p50>3s-or-missing
snappy-krisp: fetch-meetings:p50>3s-or-missing, fetch-action-items:p50>3s-or-missing
snappy-ops: snapshot:p50>3s-or-missing
snappy-sensors: dump:p50>3s-or-missing, stale:p50>3s-or-missing
snappy-telegram: messages:p50>3s-or-missing, decisions:p50>3s-or-missing
snappy-youtube: auth:p50>3s-or-missing
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:1 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:1 — snappy-inbox-sweep/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-krisp/api.ts:1 — snappy-krisp/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1 — snappy-ops/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-sensors/api.ts:1 — snappy-sensors/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-telegram/api.ts:1 — snappy-telegram/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-youtube/api.ts:1 — snappy-youtubenpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 43's FAIL count falling from 7.lint <skill> line for two skills in the cluster, showing rule 43 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r43-a-read-is-one-call-not-a-queued-job **Rule 43** · evidence mode `probe` · **7 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism A read is one call, not a queued job This is ONE root cause across 7 skill(s). Fix the mechanism, not the 7 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 7. ## The measured evidence, verbatim from `lint --all` ``` snappy-analytics: scorecard:p50>3s-or-missing snappy-inbox-sweep: gmail-work:p50>3s-or-missing snappy-krisp: fetch-meetings:p50>3s-or-missing, fetch-action-items:p50>3s-or-missing snappy-ops: snapshot:p50>3s-or-missing snappy-sensors: dump:p50>3s-or-missing, stale:p50>3s-or-missing snappy-telegram: messages:p50>3s-or-missing, decisions:p50>3s-or-missing snappy-youtube: auth:p50>3s-or-missing ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:1` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbox-sweep/api.ts:1` — snappy-inbox-sweep - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-krisp/api.ts:1` — snappy-krisp - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ops/api.ts:1` — snappy-ops - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-sensors/api.ts:1` — snappy-sensors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-telegram/api.ts:1` — snappy-telegram - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-youtube/api.ts:1` — snappy-youtube ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 43's FAIL count falling from 7. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 43 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 45 · evidence mode static · 1 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Long work is a separately named job verb
This is ONE root cause across 1 skill(s). Fix the mechanism, not the
1 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 1.
lint --all#snappy-faces: 3 read(s) not execution=call; queue implementation=false
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50 — snappy-facesnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 45's FAIL count falling from 1.lint <skill> line for two skills in the cluster, showing rule 45 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r45-long-work-is-a-separately-named-job-verb **Rule 45** · evidence mode `static` · **1 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Long work is a separately named job verb This is ONE root cause across 1 skill(s). Fix the mechanism, not the 1 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 1. ## The measured evidence, verbatim from `lint --all` ``` snappy-faces: 3 read(s) not execution=call; queue implementation=false ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50` — snappy-faces ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 45's FAIL count falling from 1. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 45 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 5 · evidence mode static · 38 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Description is valid, third-person, and says what plus when
This is ONE root cause across 38 skill(s). Fix the mechanism, not the
38 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 38.
lint --all#snappy-ai-models: 1037 chars
snappy-analytics: first/second person; no when/use marker
snappy-artifact-loop: first/second person
snappy-ax: 1082 chars; XML/HTML tag
snappy-calendar: first/second person
snappy-cleanshot: no when/use marker
snappy-content: first/second person
snappy-corpus: first/second person
snappy-dashboard: first/second person; no when/use marker
snappy-deploy: 1158 chars
snappy-dom-cartographer: 1078 chars
… and 26 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122 — snappy-calendar/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538 — snappy-cleanshot/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-content/api.ts:437 — snappy-content/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-corpus/api.ts:103 — snappy-corpus/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-dashboard/api.ts:22 — snappy-dashboard/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-deploy/api.ts:169 — snappy-deploy/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-dom-cartographer/api.ts:149 — snappy-dom-cartographernpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 5's FAIL count falling from 38.lint <skill> line for two skills in the cluster, showing rule 5 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r5-description-is-valid-third-person-and-says-what-plus-when **Rule 5** · evidence mode `static` · **38 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Description is valid, third-person, and says what plus when This is ONE root cause across 38 skill(s). Fix the mechanism, not the 38 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 38. ## The measured evidence, verbatim from `lint --all` ``` snappy-ai-models: 1037 chars snappy-analytics: first/second person; no when/use marker snappy-artifact-loop: first/second person snappy-ax: 1082 chars; XML/HTML tag snappy-calendar: first/second person snappy-cleanshot: no when/use marker snappy-content: first/second person snappy-corpus: first/second person snappy-dashboard: first/second person; no when/use marker snappy-deploy: 1158 chars snappy-dom-cartographer: 1078 chars … and 26 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122` — snappy-calendar - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538` — snappy-cleanshot - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-content/api.ts:437` — snappy-content - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-corpus/api.ts:103` — snappy-corpus - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-dashboard/api.ts:22` — snappy-dashboard - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-deploy/api.ts:169` — snappy-deploy - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-dom-cartographer/api.ts:149` — snappy-dom-cartographer ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 5's FAIL count falling from 38. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 5 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 54 · evidence mode static · 53 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.A skill declares itself ready with fewer than three eval scenarios — a status truer than the artifact behind it.
This is ONE root cause across 53 skill(s). Fix the mechanism, not the
53 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 53.
lint --all#snappy-ads: 0 eval scenario(s); ready=true
snappy-ai-models: 0 eval scenario(s); ready=true
snappy-analytics: 0 eval scenario(s); ready=true
snappy-blog: 0 eval scenario(s); ready=true
snappy-box: 0 eval scenario(s); ready=true
snappy-browse: 0 eval scenario(s); ready=true
snappy-calendar: 0 eval scenario(s); ready=true
snappy-client-orbiter: 0 eval scenario(s); ready=true
snappy-client-scott: 0 eval scenario(s); ready=true
snappy-client-template: 0 eval scenario(s); ready=true
snappy-client-total: 0 eval scenario(s); ready=true
… and 41 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42 — snappy-ads/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150 — snappy-blog/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124 — snappy-box/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122 — snappy-calendar/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-orbiter/api.ts:145 — snappy-client-orbiter/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58 — snappy-client-scott/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-template/api.ts:52 — snappy-client-template/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-total/api.ts:196 — snappy-client-totalnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 54's FAIL count falling from 53.lint <skill> line for two skills in the cluster, showing rule 54 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r54-ready-without-evals **Rule 54** · evidence mode `static` · **53 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism A skill declares itself ready with fewer than three eval scenarios — a status truer than the artifact behind it. This is ONE root cause across 53 skill(s). Fix the mechanism, not the 53 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 53. ## The measured evidence, verbatim from `lint --all` ``` snappy-ads: 0 eval scenario(s); ready=true snappy-ai-models: 0 eval scenario(s); ready=true snappy-analytics: 0 eval scenario(s); ready=true snappy-blog: 0 eval scenario(s); ready=true snappy-box: 0 eval scenario(s); ready=true snappy-browse: 0 eval scenario(s); ready=true snappy-calendar: 0 eval scenario(s); ready=true snappy-client-orbiter: 0 eval scenario(s); ready=true snappy-client-scott: 0 eval scenario(s); ready=true snappy-client-template: 0 eval scenario(s); ready=true snappy-client-total: 0 eval scenario(s); ready=true … and 41 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42` — snappy-ads - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150` — snappy-blog - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124` — snappy-box - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122` — snappy-calendar - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-orbiter/api.ts:145` — snappy-client-orbiter - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58` — snappy-client-scott - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-template/api.ts:52` — snappy-client-template - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-total/api.ts:196` — snappy-client-total ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 54's FAIL count falling from 53. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 54 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 55 · evidence mode static · 95 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Eval scenarios are multi-call with ground truth
This is ONE root cause across 95 skill(s). Fix the mechanism, not the
95 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 95.
lint --all#snappy-ads: eval manifest missing
snappy-agent-host: eval manifest missing
snappy-ai-models: eval manifest missing
snappy-analytics: eval manifest missing
snappy-api-sniffer: eval manifest missing
snappy-artifact-loop: eval manifest missing
snappy-ax: eval manifest missing
snappy-blog: eval manifest missing
snappy-box: eval manifest missing
snappy-browse: eval manifest missing
snappy-calendar: eval manifest missing
snappy-chain: eval manifest missing
… and 83 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42 — snappy-ads/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-api-sniffer/api.ts:241 — snappy-api-sniffer/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150 — snappy-blog/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124 — snappy-box/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122 — snappy-calendar/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-chain/api.ts:195 — snappy-chainnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 55's FAIL count falling from 95.lint <skill> line for two skills in the cluster, showing rule 55 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r55-eval-scenarios-are-multi-call-with-ground-truth **Rule 55** · evidence mode `static` · **95 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Eval scenarios are multi-call with ground truth This is ONE root cause across 95 skill(s). Fix the mechanism, not the 95 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 95. ## The measured evidence, verbatim from `lint --all` ``` snappy-ads: eval manifest missing snappy-agent-host: eval manifest missing snappy-ai-models: eval manifest missing snappy-analytics: eval manifest missing snappy-api-sniffer: eval manifest missing snappy-artifact-loop: eval manifest missing snappy-ax: eval manifest missing snappy-blog: eval manifest missing snappy-box: eval manifest missing snappy-browse: eval manifest missing snappy-calendar: eval manifest missing snappy-chain: eval manifest missing … and 83 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42` — snappy-ads - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-api-sniffer/api.ts:241` — snappy-api-sniffer - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150` — snappy-blog - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124` — snappy-box - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122` — snappy-calendar - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-chain/api.ts:195` — snappy-chain ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 55's FAIL count falling from 95. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 55 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 57 · evidence mode static · 95 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Eval manifest contains a disjoint holdout
This is ONE root cause across 95 skill(s). Fix the mechanism, not the
95 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 95.
lint --all#snappy-ads: 0 disjoint holdout scenario(s)
snappy-agent-host: 0 disjoint holdout scenario(s)
snappy-ai-models: 0 disjoint holdout scenario(s)
snappy-analytics: 0 disjoint holdout scenario(s)
snappy-api-sniffer: 0 disjoint holdout scenario(s)
snappy-artifact-loop: 0 disjoint holdout scenario(s)
snappy-ax: 0 disjoint holdout scenario(s)
snappy-blog: 0 disjoint holdout scenario(s)
snappy-box: 0 disjoint holdout scenario(s)
snappy-browse: 0 disjoint holdout scenario(s)
snappy-calendar: 0 disjoint holdout scenario(s)
snappy-chain: 0 disjoint holdout scenario(s)
… and 83 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42 — snappy-ads/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-api-sniffer/api.ts:241 — snappy-api-sniffer/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150 — snappy-blog/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124 — snappy-box/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122 — snappy-calendar/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-chain/api.ts:195 — snappy-chainnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 57's FAIL count falling from 95.lint <skill> line for two skills in the cluster, showing rule 57 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r57-eval-manifest-contains-a-disjoint-holdout **Rule 57** · evidence mode `static` · **95 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Eval manifest contains a disjoint holdout This is ONE root cause across 95 skill(s). Fix the mechanism, not the 95 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 95. ## The measured evidence, verbatim from `lint --all` ``` snappy-ads: 0 disjoint holdout scenario(s) snappy-agent-host: 0 disjoint holdout scenario(s) snappy-ai-models: 0 disjoint holdout scenario(s) snappy-analytics: 0 disjoint holdout scenario(s) snappy-api-sniffer: 0 disjoint holdout scenario(s) snappy-artifact-loop: 0 disjoint holdout scenario(s) snappy-ax: 0 disjoint holdout scenario(s) snappy-blog: 0 disjoint holdout scenario(s) snappy-box: 0 disjoint holdout scenario(s) snappy-browse: 0 disjoint holdout scenario(s) snappy-calendar: 0 disjoint holdout scenario(s) snappy-chain: 0 disjoint holdout scenario(s) … and 83 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ads/api.ts:42` — snappy-ads - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-api-sniffer/api.ts:241` — snappy-api-sniffer - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-blog/api.ts:150` — snappy-blog - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124` — snappy-box - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-calendar/api.ts:122` — snappy-calendar - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-chain/api.ts:195` — snappy-chain ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 57's FAIL count falling from 95. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 57 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 60 · evidence mode static · 4 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.The generated loader block has drifted from HAND_CONTRACT, so every reader gets a first call the contract no longer honours.
This is ONE root cause across 4 skill(s). Fix the mechanism, not the
4 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 4.
lint --all#snappy-github: generated block is stale against HAND_CONTRACT; run fix-loader
snappy-notion: generated block is stale against HAND_CONTRACT; run fix-loader
snappy-report-publish: generated block is stale against HAND_CONTRACT; run fix-loader
snappy-walkthrough: generated block is stale against HAND_CONTRACT; run fix-loader
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-github/api.ts:246 — snappy-github/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-notion/api.ts:40 — snappy-notion/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-report-publish/api.ts:235 — snappy-report-publish/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-walkthrough/api.ts:713 — snappy-walkthroughnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 60's FAIL count falling from 4.lint <skill> line for two skills in the cluster, showing rule 60 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r60-loader-block-stale **Rule 60** · evidence mode `static` · **4 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism The generated loader block has drifted from HAND_CONTRACT, so every reader gets a first call the contract no longer honours. This is ONE root cause across 4 skill(s). Fix the mechanism, not the 4 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 4. ## The measured evidence, verbatim from `lint --all` ``` snappy-github: generated block is stale against HAND_CONTRACT; run fix-loader snappy-notion: generated block is stale against HAND_CONTRACT; run fix-loader snappy-report-publish: generated block is stale against HAND_CONTRACT; run fix-loader snappy-walkthrough: generated block is stale against HAND_CONTRACT; run fix-loader ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-github/api.ts:246` — snappy-github - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-notion/api.ts:40` — snappy-notion - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-report-publish/api.ts:235` — snappy-report-publish - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-walkthrough/api.ts:713` — snappy-walkthrough ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 60's FAIL count falling from 4. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 60 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 7 · evidence mode static · 14 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Promised and declared verbs have no set difference
This is ONE root cause across 14 skill(s). Fix the mechanism, not the
14 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 14.
lint --all#snappy-browse: promised-only=[templates, dataset, upload, autofill, export, designs, folders, folder-items, create-folder, move, scan, import, import-url, canva, resize, comment, get-comment]; loader-missing=[]
snappy-client-template: promised-only=[help]; loader-missing=[]
snappy-dashboard: promised-only=[help]; loader-missing=[]
snappy-desktop: promised-only=[wa-chats, wa-read, wa-send]; loader-missing=[]
snappy-ffmpeg: promised-only=[help]; loader-missing=[]
snappy-image: promised-only=[autofill, export]; loader-missing=[]
snappy-krisp: promised-only=[pending-for, since, commitments, tool-mentions]; loader-missing=[]
snappy-skool: promised-only=[contract]; loader-missing=[]
snappy-statechange: promised-only=[contract]; loader-missing=[]
snappy-swarm: promised-only=[help]; loader-missing=[]
snappy-thumbnails: promised-only=[contract]; loader-missing=[]
snappy-walkthrough: promised-only=[]; loader-missing=[steps]
… and 2 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-template/api.ts:52 — snappy-client-template/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-dashboard/api.ts:22 — snappy-dashboard/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-desktop/api.ts:78 — snappy-desktop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ffmpeg/api.ts:402 — snappy-ffmpeg/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-image/api.ts:458 — snappy-image/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-krisp/api.ts:482 — snappy-krisp/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-skool/api.ts:51 — snappy-skool/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-statechange/api.ts:282 — snappy-statechange/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-swarm/api.ts:29 — snappy-swarm/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:302 — snappy-thumbnails/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-walkthrough/api.ts:713 — snappy-walkthroughnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 7's FAIL count falling from 14.lint <skill> line for two skills in the cluster, showing rule 7 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r7-promised-and-declared-verbs-have-no-set-difference **Rule 7** · evidence mode `static` · **14 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Promised and declared verbs have no set difference This is ONE root cause across 14 skill(s). Fix the mechanism, not the 14 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 14. ## The measured evidence, verbatim from `lint --all` ``` snappy-browse: promised-only=[templates, dataset, upload, autofill, export, designs, folders, folder-items, create-folder, move, scan, import, import-url, canva, resize, comment, get-comment]; loader-missing=[] snappy-client-template: promised-only=[help]; loader-missing=[] snappy-dashboard: promised-only=[help]; loader-missing=[] snappy-desktop: promised-only=[wa-chats, wa-read, wa-send]; loader-missing=[] snappy-ffmpeg: promised-only=[help]; loader-missing=[] snappy-image: promised-only=[autofill, export]; loader-missing=[] snappy-krisp: promised-only=[pending-for, since, commitments, tool-mentions]; loader-missing=[] snappy-skool: promised-only=[contract]; loader-missing=[] snappy-statechange: promised-only=[contract]; loader-missing=[] snappy-swarm: promised-only=[help]; loader-missing=[] snappy-thumbnails: promised-only=[contract]; loader-missing=[] snappy-walkthrough: promised-only=[]; loader-missing=[steps] … and 2 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-template/api.ts:52` — snappy-client-template - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-dashboard/api.ts:22` — snappy-dashboard - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-desktop/api.ts:78` — snappy-desktop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ffmpeg/api.ts:402` — snappy-ffmpeg - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-image/api.ts:458` — snappy-image - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-krisp/api.ts:482` — snappy-krisp - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-skool/api.ts:51` — snappy-skool - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-statechange/api.ts:282` — snappy-statechange - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-swarm/api.ts:29` — snappy-swarm - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-thumbnails/api.ts:302` — snappy-thumbnails - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-walkthrough/api.ts:713` — snappy-walkthrough ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 7's FAIL count falling from 14. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 7 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 8 · evidence mode static · 41 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Skill body and reference graph stay within disclosure budgets
This is ONE root cause across 41 skill(s). Fix the mechanism, not the
41 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 41.
lint --all#snappy-agent-host: 335 lines; ~13637 tokens
snappy-ai-models: 338 lines; ~4564 tokens; anthropic/README.md >100 lines without ToC; replicate/README.md >100 lines without ToC; anthropic/README.md >100 lines without ToC; replicate/README.md >100 lines without ToC
snappy-analytics: 352 lines; ~5826 tokens
snappy-artifact-loop: 849 lines; ~10936 tokens
snappy-ax: 258 lines; ~7806 tokens
snappy-box: 331 lines; ~3919 tokens; deep reference ../snappy-infra/auth-reference.md; endpoints.md >100 lines without ToC; endpoints.md >100 lines without ToC; endpoints.md >100 lines without ToC; writing-routes.md >100 lines without ToC; tables.md >100 lines without ToC; writing-routes.md >100 lines without ToC; endpoints.md >100 lines without ToC; writing-routes.md >100 lines without ToC; tables.md >100 lines without ToC
snappy-browse: 416 lines; ~5136 tokens; platforms.md >100 lines without ToC; deep reference ../snappy-infra/auth-reference.md; platforms.md >100 lines without ToC; troubleshooting.md >100 lines without ToC; deep reference ../snappy-infra/auth-reference.md
snappy-cleanshot: 425 lines; ~5758 tokens
snappy-client-scott: 472 lines; ~6021 tokens
snappy-content: 366 lines; ~4812 tokens; pipeline-architecture.md >100 lines without ToC; quality-rules.md >100 lines without ToC; pipeline-architecture.md >100 lines without ToC; expert-council-method.md >100 lines without ToC; carousel-method.md >100 lines without ToC
snappy-course: 611 lines; ~7284 tokens
… and 29 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58 — snappy-analytics/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124 — snappy-box/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271 — snappy-browse/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538 — snappy-cleanshot/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58 — snappy-client-scott/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-content/api.ts:437 — snappy-content/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-course/api.ts:104 — snappy-coursenpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 8's FAIL count falling from 41.lint <skill> line for two skills in the cluster, showing rule 8 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r8-skill-body-and-reference-graph-stay-within-disclosure-budget **Rule 8** · evidence mode `static` · **41 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Skill body and reference graph stay within disclosure budgets This is ONE root cause across 41 skill(s). Fix the mechanism, not the 41 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 41. ## The measured evidence, verbatim from `lint --all` ``` snappy-agent-host: 335 lines; ~13637 tokens snappy-ai-models: 338 lines; ~4564 tokens; anthropic/README.md >100 lines without ToC; replicate/README.md >100 lines without ToC; anthropic/README.md >100 lines without ToC; replicate/README.md >100 lines without ToC snappy-analytics: 352 lines; ~5826 tokens snappy-artifact-loop: 849 lines; ~10936 tokens snappy-ax: 258 lines; ~7806 tokens snappy-box: 331 lines; ~3919 tokens; deep reference ../snappy-infra/auth-reference.md; endpoints.md >100 lines without ToC; endpoints.md >100 lines without ToC; endpoints.md >100 lines without ToC; writing-routes.md >100 lines without ToC; tables.md >100 lines without ToC; writing-routes.md >100 lines without ToC; endpoints.md >100 lines without ToC; writing-routes.md >100 lines without ToC; tables.md >100 lines without ToC snappy-browse: 416 lines; ~5136 tokens; platforms.md >100 lines without ToC; deep reference ../snappy-infra/auth-reference.md; platforms.md >100 lines without ToC; troubleshooting.md >100 lines without ToC; deep reference ../snappy-infra/auth-reference.md snappy-cleanshot: 425 lines; ~5758 tokens snappy-client-scott: 472 lines; ~6021 tokens snappy-content: 366 lines; ~4812 tokens; pipeline-architecture.md >100 lines without ToC; quality-rules.md >100 lines without ToC; pipeline-architecture.md >100 lines without ToC; expert-council-method.md >100 lines without ToC; carousel-method.md >100 lines without ToC snappy-course: 611 lines; ~7284 tokens … and 29 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-analytics/api.ts:58` — snappy-analytics - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-box/api.ts:124` — snappy-box - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-browse/api.ts:271` — snappy-browse - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-cleanshot/api.ts:1538` — snappy-cleanshot - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-scott/api.ts:58` — snappy-client-scott - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-content/api.ts:437` — snappy-content - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-course/api.ts:104` — snappy-course ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 8's FAIL count falling from 41. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 8 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
Rule 9 · evidence mode static · 22 skill(s) share this one mechanism
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
git commit --only <explicit paths>; never git add -A; never git stash.tsc --watch.Time-sensitive prose is isolated under Old patterns
This is ONE root cause across 22 skill(s). Fix the mechanism, not the
22 symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered 22.
lint --all#snappy-agent-host: 2 time-sensitive instruction line(s) outside Old patterns
snappy-ai-models: 3 time-sensitive instruction line(s) outside Old patterns
snappy-artifact-loop: 4 time-sensitive instruction line(s) outside Old patterns
snappy-ax: 1 time-sensitive instruction line(s) outside Old patterns
snappy-client-ray: 1 time-sensitive instruction line(s) outside Old patterns
snappy-course: 3 time-sensitive instruction line(s) outside Old patterns
snappy-docs: 5 time-sensitive instruction line(s) outside Old patterns
snappy-faces: 2 time-sensitive instruction line(s) outside Old patterns
snappy-gmail: 1 time-sensitive instruction line(s) outside Old patterns
snappy-inbound: 1 time-sensitive instruction line(s) outside Old patterns
snappy-linkedin: 2 time-sensitive instruction line(s) outside Old patterns
snappy-notion: 7 time-sensitive instruction line(s) outside Old patterns
… and 10 more
/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177 — snappy-agent-host/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100 — snappy-ai-models/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291 — snappy-artifact-loop/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217 — snappy-ax/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-ray/api.ts:322 — snappy-client-ray/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-course/api.ts:104 — snappy-course/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-docs/api.ts:286 — snappy-docs/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50 — snappy-faces/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-gmail/api.ts:51 — snappy-gmail/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbound/api.ts:83 — snappy-inbound/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-linkedin/api.ts:36 — snappy-linkedin/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-notion/api.ts:40 — snappy-notionnpx tsx skills/snappy-tool-design/api.ts lint --all --summary before and after, with rule 9's FAIL count falling from 22.lint <skill> line for two skills in the cluster, showing rule 9 PASS.SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh still passing.# Lane brief — r9-time-sensitive-prose-is-isolated-under-old-patterns **Rule 9** · evidence mode `static` · **22 skill(s) share this one mechanism** ## Role and prohibitions You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the skills named under "Anchors" and nothing else. - NEVER install on any Mac, deploy, or push. - NEVER send, post, spend or delete through any hand — reads only. - NEVER edit a skill outside the list of files named below. - `git commit --only <explicit paths>`; never `git add -A`; never `git stash`. - nice -n 19 every compile; no `tsc --watch`. ## The mechanism Time-sensitive prose is isolated under Old patterns This is ONE root cause across 22 skill(s). Fix the mechanism, not the 22 symptoms: a change that repairs one skill and leaves the shape in place has killed one defect where the cluster offered 22. ## The measured evidence, verbatim from `lint --all` ``` snappy-agent-host: 2 time-sensitive instruction line(s) outside Old patterns snappy-ai-models: 3 time-sensitive instruction line(s) outside Old patterns snappy-artifact-loop: 4 time-sensitive instruction line(s) outside Old patterns snappy-ax: 1 time-sensitive instruction line(s) outside Old patterns snappy-client-ray: 1 time-sensitive instruction line(s) outside Old patterns snappy-course: 3 time-sensitive instruction line(s) outside Old patterns snappy-docs: 5 time-sensitive instruction line(s) outside Old patterns snappy-faces: 2 time-sensitive instruction line(s) outside Old patterns snappy-gmail: 1 time-sensitive instruction line(s) outside Old patterns snappy-inbound: 1 time-sensitive instruction line(s) outside Old patterns snappy-linkedin: 2 time-sensitive instruction line(s) outside Old patterns snappy-notion: 7 time-sensitive instruction line(s) outside Old patterns … and 10 more ``` ## Anchors - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-agent-host/api.ts:1177` — snappy-agent-host - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ai-models/api.ts:100` — snappy-ai-models - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-artifact-loop/api.ts:291` — snappy-artifact-loop - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-ax/api.ts:217` — snappy-ax - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-client-ray/api.ts:322` — snappy-client-ray - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-course/api.ts:104` — snappy-course - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-docs/api.ts:286` — snappy-docs - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-faces/api.ts:50` — snappy-faces - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-gmail/api.ts:51` — snappy-gmail - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-inbound/api.ts:83` — snappy-inbound - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-linkedin/api.ts:36` — snappy-linkedin - `/Users/robertboulos/Projects/snappy-kernel-loop-3/skills/snappy-notion/api.ts:40` — snappy-notion ## The anti-fix — what would look like progress and is not - Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves. - Repairing the prose in SKILL.md while the contract that generates it stays wrong. - Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green. - Fixing the alphabetically first skill and reporting the cluster closed. ## The proof required 1. `npx tsx skills/snappy-tool-design/api.ts lint --all --summary` before and after, with rule 9's FAIL count falling from 22. 2. The full `lint <skill>` line for two skills in the cluster, showing rule 9 PASS. 3. `SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh` still passing. 4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
/**
* THE ONE KIND THAT THREW, REPRODUCED WITHOUT A BROWSER
* ⟨lane render-harness, 2026-09-09⟩.
*
* Three clean render runs against the mini drew 96 of 97 face kinds and
* recorded `notion-append` as `THREW SyntaxError: Expected ',' or ']' after
* array element in JSON at position 138` at BOTH 360 and 720. The fixture
* parsed; `snappy-faces lang notion-append --json` parsed; the faces skill's
* own paint probe painted it with 32 elements. The face was never broken —
* the RIG'S READER was, and only for a payload carrying a quote.
*
* The first case below is the exact bytes `agent-browser eval` printed for a
* `window.__loopState` whose button text quotes a Notion page title, captured
* from a live `agent-browser eval` on this Mac. It fails against the mangler
* `render.ts` used and passes against `decodeBrowserEval`.
*/
import { strict as assert } from "node:assert";
import { test } from "node:test";
import { decodeBrowserEval } from "./browser-eval.ts";
/** What `agent-browser eval 'JSON.stringify(window.__loopState)'` printed for
* notion-append: two layers of JSON, and a quote inside the button text. */
const NOTION_APPEND_EVAL =
'"{\\"state\\":\\"drew\\",\\"detail\\":{\\"width\\":720,\\"firstPaintMs\\":41,\\"painted\\":32,' +
'\\"height\\":540,\\"buttons\\":[\\"Append writes the paragraph to the end of ' +
'\\\\\\"Harbourline import — decisions\\\\\\" now\\",\\"Later keeps it staged\\"]}}"\n';
test("the quote inside notion-append's door price survives the decode", () => {
const state = decodeBrowserEval<{ state: string; detail: { buttons: string[] } }>(NOTION_APPEND_EVAL);
assert.ok(state, "notion-append's loop state must decode; it recorded THREW for three runs");
assert.equal(state.state, "drew");
assert.deepEqual(state.detail.buttons, [
'Append writes the paragraph to the end of "Harbourline import — decisions" now',
"Later keeps it staged",
]);
});
test("the mangler this replaced is the defect, named so it cannot come back", () => {
// strip the outer quotes, then un-escape `\"` — right until a payload holds
// a quote of its own, when `\\\"` becomes `\\"` and closes the string early.
const mangled = NOTION_APPEND_EVAL.trim().replace(/^"|"$/g, "").replace(/\\"/g, '"');
assert.throws(() => JSON.parse(mangled), /Expected ',' or ']' after array element/);
});
test("a payload with no quote in it decodes the same either way", () => {
const plain = JSON.stringify(JSON.stringify({ state: "drew", detail: { buttons: ["Send", "Later"] } }));
const decoded = decodeBrowserEval<{ detail: { buttons: string[] } }>(plain);
assert.deepEqual(decoded?.detail.buttons, ["Send", "Later"]);
});
test("a backslash in the text survives too — the mangler's other blind spot", () => {
const raw = JSON.stringify(JSON.stringify({ note: 'C:\\Users\\mara — "the ledger"' }));
assert.deepEqual(decodeBrowserEval<{ note: string }>(raw), { note: 'C:\\Users\\mara — "the ledger"' });
});
test("a bare object return needs no second layer", () => {
assert.deepEqual(decodeBrowserEval<{ mounted: boolean }>('{"mounted":true}'), { mounted: true });
});
test("noise before the object is an error unless the caller allows it", () => {
const noisy = 'reading page...\n{"mounted":true}\n';
assert.equal(decodeBrowserEval(noisy), null);
assert.deepEqual(decodeBrowserEval(noisy, { tolerateNoise: true }), { mounted: true });
});
test("nothing to read reads as nothing, never as a value", () => {
assert.equal(decodeBrowserEval(""), null);
assert.equal(decodeBrowserEval("undefined"), null);
assert.equal(decodeBrowserEval("null"), null);
assert.equal(decodeBrowserEval('"just a string"'), null);
});
/**
* THE ONE KIND THAT THREW, REPRODUCED WITHOUT A BROWSER
* ⟨lane render-harness, 2026-09-09⟩.
*
* Three clean render runs against the mini drew 96 of 97 face kinds and
* recorded `notion-append` as `THREW SyntaxError: Expected ',' or ']' after
* array element in JSON at position 138` at BOTH 360 and 720. The fixture
* parsed; `snappy-faces lang notion-append --json` parsed; the faces skill's
* own paint probe painted it with 32 elements. The face was never broken —
* the RIG'S READER was, and only for a payload carrying a quote.
*
* The first case below is the exact bytes `agent-browser eval` printed for a
* `window.__loopState` whose button text quotes a Notion page title, captured
* from a live `agent-browser eval` on this Mac. It fails against the mangler
* `render.ts` used and passes against `decodeBrowserEval`.
*/
import { strict as assert } from "node:assert";
import { test } from "node:test";
import { decodeBrowserEval } from "./browser-eval.ts";
/** What `agent-browser eval 'JSON.stringify(window.__loopState)'` printed for
* notion-append: two layers of JSON, and a quote inside the button text. */
const NOTION_APPEND_EVAL =
'"{\\"state\\":\\"drew\\",\\"detail\\":{\\"width\\":720,\\"firstPaintMs\\":41,\\"painted\\":32,' +
'\\"height\\":540,\\"buttons\\":[\\"Append writes the paragraph to the end of ' +
'\\\\\\"Harbourline import — decisions\\\\\\" now\\",\\"Later keeps it staged\\"]}}"\n';
test("the quote inside notion-append's door price survives the decode", () => {
const state = decodeBrowserEval<{ state: string; detail: { buttons: string[] } }>(NOTION_APPEND_EVAL);
assert.ok(state, "notion-append's loop state must decode; it recorded THREW for three runs");
assert.equal(state.state, "drew");
assert.deepEqual(state.detail.buttons, [
'Append writes the paragraph to the end of "Harbourline import — decisions" now',
"Later keeps it staged",
]);
});
test("the mangler this replaced is the defect, named so it cannot come back", () => {
// strip the outer quotes, then un-escape `\"` — right until a payload holds
// a quote of its own, when `\\\"` becomes `\\"` and closes the string early.
const mangled = NOTION_APPEND_EVAL.trim().replace(/^"|"$/g, "").replace(/\\"/g, '"');
assert.throws(() => JSON.parse(mangled), /Expected ',' or ']' after array element/);
});
test("a payload with no quote in it decodes the same either way", () => {
const plain = JSON.stringify(JSON.stringify({ state: "drew", detail: { buttons: ["Send", "Later"] } }));
const decoded = decodeBrowserEval<{ detail: { buttons: string[] } }>(plain);
assert.deepEqual(decoded?.detail.buttons, ["Send", "Later"]);
});
test("a backslash in the text survives too — the mangler's other blind spot", () => {
const raw = JSON.stringify(JSON.stringify({ note: 'C:\\Users\\mara — "the ledger"' }));
assert.deepEqual(decodeBrowserEval<{ note: string }>(raw), { note: 'C:\\Users\\mara — "the ledger"' });
});
test("a bare object return needs no second layer", () => {
assert.deepEqual(decodeBrowserEval<{ mounted: boolean }>('{"mounted":true}'), { mounted: true });
});
test("noise before the object is an error unless the caller allows it", () => {
const noisy = 'reading page...\n{"mounted":true}\n';
assert.equal(decodeBrowserEval(noisy), null);
assert.deepEqual(decodeBrowserEval(noisy, { tolerateNoise: true }), { mounted: true });
});
test("nothing to read reads as nothing, never as a value", () => {
assert.equal(decodeBrowserEval(""), null);
assert.equal(decodeBrowserEval("undefined"), null);
assert.equal(decodeBrowserEval("null"), null);
assert.equal(decodeBrowserEval('"just a string"'), null);
});
/**
* THE ONE DECODE OF AN `agent-browser eval` ANSWER ⟨lane render-harness, 2026-09-09⟩.
*
* `agent-browser eval <expr>` prints the RETURN VALUE json-encoded. So a rig
* that evaluates `JSON.stringify(window.__x)` gets back a JSON *string
* literal* whose content is itself JSON — two layers, and the outer one is
* real JSON, not decoration. Measured on this Mac:
*
* window.__t = { a: ['say "hi" now'] }
* $ agent-browser eval 'JSON.stringify(window.__t)'
* "{\"a\":[\"say \\\"hi\\\" now\"]}"
*
* THE DEFECT THAT BUILT THIS FILE. `render.ts` peeled those two layers by
* hand — strip the outer quotes, then `.replace(/\\"/g, '"')`. That mangler
* is right only while no string in the payload contains a quote of its own.
* `snappy-notion`'s `notion-append` door says its price is `writes the
* paragraph to the end of "Harbourline import — decisions" now`, the face
* draws that sentence inside the button, the rig reads button text, and the
* inner `\"` arrives as `\\\"`. The mangler turns those four characters into
* `\\"` — an escaped backslash followed by a live quote — which CLOSES the
* string early and leaves `Harbourline` sitting where a `,` or `]` belongs:
*
* SyntaxError: Expected ',' or ']' after array element in JSON at position 138
*
* One kind out of 97 recorded THREW at both widths for three clean runs, and
* the face was drawing perfectly the whole time. A reader that cannot read
* the artifact reports a false red — the status/artifact rule pointed the
* other way, and the second time this exact encoding bit this skill in a day
* (`hosts-bar.ts readSight`, same morning). Hence ONE road, here, that both
* callers use: `JSON.parse` is the only thing allowed to undo `JSON.stringify`.
*/
/** How many layers of `JSON.stringify` a rig may have to undo. `eval` adds
* one; a payload that is itself a stringified object adds a second. */
const MAX_LAYERS = 3;
/**
* Decode what `agent-browser eval` printed into the value the page returned.
*
* Peels JSON layers until a non-string falls out, so it reads both
* `JSON.stringify(window.__x)` (two layers) and a bare object return (one).
* `tolerateNoise` slices from the first `{` for the rigs whose command prints
* a status line before the value; without it, leading noise is an error, not
* a silent guess.
*/
export function decodeBrowserEval<T = unknown>(out: string, options: { tolerateNoise?: boolean } = {}): T | null {
let text: unknown = out.trim();
for (let layer = 0; layer < MAX_LAYERS; layer += 1) {
if (typeof text !== "string") break;
const trimmed = text.trim();
if (trimmed === "" || trimmed === "undefined") return null;
const from = trimmed.startsWith("\"") || trimmed.startsWith("{") || trimmed.startsWith("[")
? trimmed
: options.tolerateNoise
? sliceObject(trimmed)
: null;
if (from === null) return null;
try { text = JSON.parse(from); } catch { return null; }
}
return typeof text === "string" ? null : (text as T);
}
/** The one concession to a noisy command: the object between the first `{`
* and the last `}`. Returns null when there is no object to find. */
function sliceObject(text: string): string | null {
const at = text.indexOf("{");
if (at < 0) return null;
const end = text.lastIndexOf("}");
return end <= at ? null : text.slice(at, end + 1);
}
/**
* THE ONE DECODE OF AN `agent-browser eval` ANSWER ⟨lane render-harness, 2026-09-09⟩.
*
* `agent-browser eval <expr>` prints the RETURN VALUE json-encoded. So a rig
* that evaluates `JSON.stringify(window.__x)` gets back a JSON *string
* literal* whose content is itself JSON — two layers, and the outer one is
* real JSON, not decoration. Measured on this Mac:
*
* window.__t = { a: ['say "hi" now'] }
* $ agent-browser eval 'JSON.stringify(window.__t)'
* "{\"a\":[\"say \\\"hi\\\" now\"]}"
*
* THE DEFECT THAT BUILT THIS FILE. `render.ts` peeled those two layers by
* hand — strip the outer quotes, then `.replace(/\\"/g, '"')`. That mangler
* is right only while no string in the payload contains a quote of its own.
* `snappy-notion`'s `notion-append` door says its price is `writes the
* paragraph to the end of "Harbourline import — decisions" now`, the face
* draws that sentence inside the button, the rig reads button text, and the
* inner `\"` arrives as `\\\"`. The mangler turns those four characters into
* `\\"` — an escaped backslash followed by a live quote — which CLOSES the
* string early and leaves `Harbourline` sitting where a `,` or `]` belongs:
*
* SyntaxError: Expected ',' or ']' after array element in JSON at position 138
*
* One kind out of 97 recorded THREW at both widths for three clean runs, and
* the face was drawing perfectly the whole time. A reader that cannot read
* the artifact reports a false red — the status/artifact rule pointed the
* other way, and the second time this exact encoding bit this skill in a day
* (`hosts-bar.ts readSight`, same morning). Hence ONE road, here, that both
* callers use: `JSON.parse` is the only thing allowed to undo `JSON.stringify`.
*/
/** How many layers of `JSON.stringify` a rig may have to undo. `eval` adds
* one; a payload that is itself a stringified object adds a second. */
const MAX_LAYERS = 3;
/**
* Decode what `agent-browser eval` printed into the value the page returned.
*
* Peels JSON layers until a non-string falls out, so it reads both
* `JSON.stringify(window.__x)` (two layers) and a bare object return (one).
* `tolerateNoise` slices from the first `{` for the rigs whose command prints
* a status line before the value; without it, leading noise is an error, not
* a silent guess.
*/
export function decodeBrowserEval<T = unknown>(out: string, options: { tolerateNoise?: boolean } = {}): T | null {
let text: unknown = out.trim();
for (let layer = 0; layer < MAX_LAYERS; layer += 1) {
if (typeof text !== "string") break;
const trimmed = text.trim();
if (trimmed === "" || trimmed === "undefined") return null;
const from = trimmed.startsWith("\"") || trimmed.startsWith("{") || trimmed.startsWith("[")
? trimmed
: options.tolerateNoise
? sliceObject(trimmed)
: null;
if (from === null) return null;
try { text = JSON.parse(from); } catch { return null; }
}
return typeof text === "string" ? null : (text as T);
}
/** The one concession to a noisy command: the object between the first `{`
* and the last `}`. Returns null when there is no object to find. */
function sliceObject(text: string): string | null {
const at = text.indexOf("{");
if (at < 0) return null;
const end = text.lastIndexOf("}");
return end <= at ? null : text.slice(at, end + 1);
}
/**
* THE EVIDENCE FILES, AND THE ONE READER OF THEM.
*
* `lint` grades 26 of its 60 rules from measured evidence rather than source.
* Until 2026-09-08 nothing produced that evidence, so those rules answered
* DEFER forever and the loop never closed.
*
* ONE corpus, not ninety-eight. The first shape considered was a
* `tool-design-probe.json` inside every skill's own directory — the shape the
* lint originally read. That is ninety-eight artifacts written by one run,
* each a copy of the same server facts (the catalog is ONE catalog; the
* fresh-client handshake is ONE handshake), and ninety-eight chances for a
* stale one to grade a rule green. DUPLICATE ROADS ARE BANNED, so the probe
* writes ONE file here, keyed by skill, and this module is its only reader.
* `probeFor()` folds the server-wide block into each skill's slice, so the
* rule code above still sees the flat per-skill corpus it always read.
*/
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
/** One recorded read call through the MCP. Field names are the rule code's. */
export interface ProbeRead {
verb: string;
limit?: number;
chars?: number;
tokens?: number;
duration_ms?: number;
request_count?: number;
truncated?: boolean;
summary?: string;
cursor?: string;
content?: unknown[];
structuredContent?: unknown;
outputSchemaValid?: boolean;
as_of_age_minutes?: number;
stale_label?: boolean;
evidence_envelope?: boolean;
face?: {
textFallback?: boolean;
resourceUri?: string;
mimeType?: string;
renditions?: string[];
state?: string;
kind?: string;
};
/** Recorded for the loop's clustering, never graded directly. */
outcome?: string;
round_trip_ms?: number;
bytes?: number;
}
export interface ProbeRefusal {
outcome?: string;
code?: string;
message?: string;
fix?: string;
verb?: string;
provided?: string;
/** Where in HAND_CONTRACT the correcting fact lives, when the hand printed
* the closed table's whole shape. Recorded, not graded: it is the evidence
* that the refusal came from `refusal-codes.ts` and not from a hand-written
* `{error: …}` that happens to spell the same four keys. */
contract_slice?: string;
}
/** The per-skill slice as the rule code reads it, server facts already folded in. */
export interface ProbeCorpus {
near_miss?: Array<{ verb: string; provided: string; expected: string; refusal: string }>;
/** Cases the probe REFUSED to fuzz because the verb sends, posts or deletes.
* Recorded so rule 15 can tell "uncovered" from "unsafe to cover". */
near_miss_skipped?: Array<{ verb: string; provided: string; expected: string; reason: string }>;
/** Verbs the server DECLARED a read and the probe still would not call,
* because the verb's own name says it reaches a person or spends. The row
* exists so the absence of a read sample is stated in the CORPUS and not
* only in a log nobody keeps: a missing measurement that says nothing reads
* as a measurement that found nothing ⟨CLAUDE.md §10⟩. */
safety_skipped?: Array<{ verb: string; effect: string; reason: string }>;
unmet_requirement?: Array<{ elapsed_ms: number; child_processes: number; verb?: string }>;
reads?: ProbeRead[];
refusals?: ProbeRefusal[];
published_tools?: number;
tools_list_tokens?: number;
search_ranks?: number[];
metrics?: string[];
fresh_client?: boolean;
handshake_after_deploy?: boolean;
}
/** The facts that belong to the SERVER, recorded once and folded into every skill. */
export interface ServerProbe {
runner_url?: string;
via?: "mini" | "public";
revision_first?: string;
revision_last?: string;
client_created_at?: string;
published_tools?: number;
tools_list_bytes?: number;
tools_list_tokens?: number;
tools_sorted?: boolean;
tool_names?: string[];
ttl_ms_present?: boolean;
cache_scope_present?: boolean;
prompts_count?: number;
prompts_bytes?: number;
prompts_ms?: number;
resources_count?: number;
resources_bytes?: number;
resources_ms?: number;
faces_view_bytes?: number;
faces_view_ms?: number;
faces_view_mime?: string;
faces_view_listed?: boolean;
fresh_client?: boolean;
handshake_after_deploy?: boolean;
metrics?: string[];
run?: Record<string, number>;
}
export interface ProbeFile {
schema: string;
generated_at: string;
server: ServerProbe;
/** The public-URL primitives block, recorded by `probe --via public`. */
public?: ServerProbe & { sampled_hands?: string[]; note?: string };
skills: Record<string, ProbeCorpus>;
}
export interface RenderKind {
kind: string;
family: string;
member: string;
component: string;
/** The hand whose `faces/` folder holds this kind's fixture, or null while
* the faces library still keeps the family. Read off the draw's fixture
* path by `render.ts drawOwner`, so the corpus carries its own evidence for
* `skill_families` instead of pointing at a list kept somewhere else. */
skill?: string | null;
widths: Record<string, { first_paint_ms: number | null; drew: string; height: number | null; nested_scroll: boolean | null; painted: number }>;
buttons: string[];
press?: { word: string; reached_execute: boolean; outcome?: string; server_ms?: number } | null;
shot?: string | null;
threw?: string | null;
}
export interface RenderFile {
schema: string;
generated_at: string;
host: { ext_apps: string; widths: number[]; resource_uri: string };
kinds: RenderKind[];
/** Four booleans per face FAMILY — the unit rule 53 actually names. */
families: Record<string, Record<string, boolean>>;
/** Which skill owns which families. DERIVED, never guessed: the publisher of
* the bundle owns every family in it, and each hand owns the family whose
* faces live in its own folder (`kinds[].skill`). */
skill_families: Record<string, string[]>;
}
export const PROBE_FILE = "tool-design-probe.json";
export const RENDER_FILE = "tool-design-render.json";
export const PROBE_SCHEMA = "snappy-tool-design/probe@1";
export const RENDER_SCHEMA = "snappy-tool-design/render@1";
/** The five metric names rule 56 requires a probe run to have recorded. */
export const REQUIRED_METRICS = ["accuracy", "runtime", "tool_call_count", "tokens", "errors"];
function readJson<T>(path: string): T | null {
if (!existsSync(path)) return null;
try {
return JSON.parse(readFileSync(path, "utf8")) as T;
} catch {
return null;
}
}
export function readProbeFile(dir: string): ProbeFile | null {
const file = readJson<ProbeFile>(join(dir, PROBE_FILE));
return file?.schema === PROBE_SCHEMA ? file : null;
}
export function readRenderFile(dir: string): RenderFile | null {
const file = readJson<RenderFile>(join(dir, RENDER_FILE));
return file?.schema === RENDER_SCHEMA ? file : null;
}
/**
* The per-skill slice, with the server-wide facts folded in.
*
* A skill the probe never reached returns null and keeps its DEFER — absence
* of evidence stays absence, never a PASS. That is the same law the render
* side follows.
*/
export function probeFor(file: ProbeFile | null, skill: string): ProbeCorpus | null {
if (!file) return null;
const slice = file.skills[skill];
if (!slice) return null;
const server = file.server ?? {};
return {
...slice,
published_tools: slice.published_tools ?? server.published_tools,
tools_list_tokens: slice.tools_list_tokens ?? server.tools_list_tokens,
metrics: slice.metrics ?? server.metrics,
fresh_client: slice.fresh_client ?? server.fresh_client,
handshake_after_deploy: slice.handshake_after_deploy ?? server.handshake_after_deploy,
};
}
/**
* The render booleans for one skill.
*
* A skill that owns face families is graded on the AND of those families —
* one bad family is a bad skill. A skill the run gives no family gets an
* EMPTY family list, which is not a pass: rules 50–53 measured nothing about
* it, and `renderOutcome` turns that into DEFER ⟨2026-09-09; it used to be a
* PASS reading "vacuous", and four rules sat on the zero floor vouching for 94
* skills nobody had looked at⟩.
*
* TWO KINDS OF EVIDENCE, ONE ANSWER. `skill_families` says where a family's
* faces LIVE (`skills/<hand>/faces/`). `drewKinds` says which face kinds this
* hand's probe reads actually CAME BACK with — a hand that answers with
* `imageset-grid` is accountable for how imageset draws even though the faces
* library still keeps that family. Neither is a list anyone maintains: the
* first is a path on disk, the second is a recorded read.
*/
export function renderFor(
file: RenderFile | null,
skill: string,
drewKinds: readonly string[] = [],
): { values: Record<string, boolean>; families: string[] } | null {
if (!file) return null;
const families = [...new Set([
...(file.skill_families[skill] ?? []),
...drewKinds.flatMap((kind) => file.kinds.filter((row) => row.kind === kind).map((row) => row.family)),
])];
if (!families.length) return { values: {}, families: [] };
const keys = new Set(families.flatMap((family) => Object.keys(file.families[family] ?? {})));
const values: Record<string, boolean> = {};
for (const key of keys) values[key] = families.every((family) => file.families[family]?.[key] === true);
return { values, families };
}
/** The face kinds this hand's recorded reads actually answered with. */
export function faceKindsDrawn(slice: ProbeCorpus | null): string[] {
return [...new Set((slice?.reads ?? []).flatMap((read) => read.face?.kind ? [read.face.kind] : []))];
}
/**
* THE EVIDENCE FILES, AND THE ONE READER OF THEM.
*
* `lint` grades 26 of its 60 rules from measured evidence rather than source.
* Until 2026-09-08 nothing produced that evidence, so those rules answered
* DEFER forever and the loop never closed.
*
* ONE corpus, not ninety-eight. The first shape considered was a
* `tool-design-probe.json` inside every skill's own directory — the shape the
* lint originally read. That is ninety-eight artifacts written by one run,
* each a copy of the same server facts (the catalog is ONE catalog; the
* fresh-client handshake is ONE handshake), and ninety-eight chances for a
* stale one to grade a rule green. DUPLICATE ROADS ARE BANNED, so the probe
* writes ONE file here, keyed by skill, and this module is its only reader.
* `probeFor()` folds the server-wide block into each skill's slice, so the
* rule code above still sees the flat per-skill corpus it always read.
*/
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
/** One recorded read call through the MCP. Field names are the rule code's. */
export interface ProbeRead {
verb: string;
limit?: number;
chars?: number;
tokens?: number;
duration_ms?: number;
request_count?: number;
truncated?: boolean;
summary?: string;
cursor?: string;
content?: unknown[];
structuredContent?: unknown;
outputSchemaValid?: boolean;
as_of_age_minutes?: number;
stale_label?: boolean;
evidence_envelope?: boolean;
face?: {
textFallback?: boolean;
resourceUri?: string;
mimeType?: string;
renditions?: string[];
state?: string;
kind?: string;
};
/** Recorded for the loop's clustering, never graded directly. */
outcome?: string;
round_trip_ms?: number;
bytes?: number;
}
export interface ProbeRefusal {
outcome?: string;
code?: string;
message?: string;
fix?: string;
verb?: string;
provided?: string;
/** Where in HAND_CONTRACT the correcting fact lives, when the hand printed
* the closed table's whole shape. Recorded, not graded: it is the evidence
* that the refusal came from `refusal-codes.ts` and not from a hand-written
* `{error: …}` that happens to spell the same four keys. */
contract_slice?: string;
}
/** The per-skill slice as the rule code reads it, server facts already folded in. */
export interface ProbeCorpus {
near_miss?: Array<{ verb: string; provided: string; expected: string; refusal: string }>;
/** Cases the probe REFUSED to fuzz because the verb sends, posts or deletes.
* Recorded so rule 15 can tell "uncovered" from "unsafe to cover". */
near_miss_skipped?: Array<{ verb: string; provided: string; expected: string; reason: string }>;
/** Verbs the server DECLARED a read and the probe still would not call,
* because the verb's own name says it reaches a person or spends. The row
* exists so the absence of a read sample is stated in the CORPUS and not
* only in a log nobody keeps: a missing measurement that says nothing reads
* as a measurement that found nothing ⟨CLAUDE.md §10⟩. */
safety_skipped?: Array<{ verb: string; effect: string; reason: string }>;
unmet_requirement?: Array<{ elapsed_ms: number; child_processes: number; verb?: string }>;
reads?: ProbeRead[];
refusals?: ProbeRefusal[];
published_tools?: number;
tools_list_tokens?: number;
search_ranks?: number[];
metrics?: string[];
fresh_client?: boolean;
handshake_after_deploy?: boolean;
}
/** The facts that belong to the SERVER, recorded once and folded into every skill. */
export interface ServerProbe {
runner_url?: string;
via?: "mini" | "public";
revision_first?: string;
revision_last?: string;
client_created_at?: string;
published_tools?: number;
tools_list_bytes?: number;
tools_list_tokens?: number;
tools_sorted?: boolean;
tool_names?: string[];
ttl_ms_present?: boolean;
cache_scope_present?: boolean;
prompts_count?: number;
prompts_bytes?: number;
prompts_ms?: number;
resources_count?: number;
resources_bytes?: number;
resources_ms?: number;
faces_view_bytes?: number;
faces_view_ms?: number;
faces_view_mime?: string;
faces_view_listed?: boolean;
fresh_client?: boolean;
handshake_after_deploy?: boolean;
metrics?: string[];
run?: Record<string, number>;
}
export interface ProbeFile {
schema: string;
generated_at: string;
server: ServerProbe;
/** The public-URL primitives block, recorded by `probe --via public`. */
public?: ServerProbe & { sampled_hands?: string[]; note?: string };
skills: Record<string, ProbeCorpus>;
}
export interface RenderKind {
kind: string;
family: string;
member: string;
component: string;
/** The hand whose `faces/` folder holds this kind's fixture, or null while
* the faces library still keeps the family. Read off the draw's fixture
* path by `render.ts drawOwner`, so the corpus carries its own evidence for
* `skill_families` instead of pointing at a list kept somewhere else. */
skill?: string | null;
widths: Record<string, { first_paint_ms: number | null; drew: string; height: number | null; nested_scroll: boolean | null; painted: number }>;
buttons: string[];
press?: { word: string; reached_execute: boolean; outcome?: string; server_ms?: number } | null;
shot?: string | null;
threw?: string | null;
}
export interface RenderFile {
schema: string;
generated_at: string;
host: { ext_apps: string; widths: number[]; resource_uri: string };
kinds: RenderKind[];
/** Four booleans per face FAMILY — the unit rule 53 actually names. */
families: Record<string, Record<string, boolean>>;
/** Which skill owns which families. DERIVED, never guessed: the publisher of
* the bundle owns every family in it, and each hand owns the family whose
* faces live in its own folder (`kinds[].skill`). */
skill_families: Record<string, string[]>;
}
export const PROBE_FILE = "tool-design-probe.json";
export const RENDER_FILE = "tool-design-render.json";
export const PROBE_SCHEMA = "snappy-tool-design/probe@1";
export const RENDER_SCHEMA = "snappy-tool-design/render@1";
/** The five metric names rule 56 requires a probe run to have recorded. */
export const REQUIRED_METRICS = ["accuracy", "runtime", "tool_call_count", "tokens", "errors"];
function readJson<T>(path: string): T | null {
if (!existsSync(path)) return null;
try {
return JSON.parse(readFileSync(path, "utf8")) as T;
} catch {
return null;
}
}
export function readProbeFile(dir: string): ProbeFile | null {
const file = readJson<ProbeFile>(join(dir, PROBE_FILE));
return file?.schema === PROBE_SCHEMA ? file : null;
}
export function readRenderFile(dir: string): RenderFile | null {
const file = readJson<RenderFile>(join(dir, RENDER_FILE));
return file?.schema === RENDER_SCHEMA ? file : null;
}
/**
* The per-skill slice, with the server-wide facts folded in.
*
* A skill the probe never reached returns null and keeps its DEFER — absence
* of evidence stays absence, never a PASS. That is the same law the render
* side follows.
*/
export function probeFor(file: ProbeFile | null, skill: string): ProbeCorpus | null {
if (!file) return null;
const slice = file.skills[skill];
if (!slice) return null;
const server = file.server ?? {};
return {
...slice,
published_tools: slice.published_tools ?? server.published_tools,
tools_list_tokens: slice.tools_list_tokens ?? server.tools_list_tokens,
metrics: slice.metrics ?? server.metrics,
fresh_client: slice.fresh_client ?? server.fresh_client,
handshake_after_deploy: slice.handshake_after_deploy ?? server.handshake_after_deploy,
};
}
/**
* The render booleans for one skill.
*
* A skill that owns face families is graded on the AND of those families —
* one bad family is a bad skill. A skill the run gives no family gets an
* EMPTY family list, which is not a pass: rules 50–53 measured nothing about
* it, and `renderOutcome` turns that into DEFER ⟨2026-09-09; it used to be a
* PASS reading "vacuous", and four rules sat on the zero floor vouching for 94
* skills nobody had looked at⟩.
*
* TWO KINDS OF EVIDENCE, ONE ANSWER. `skill_families` says where a family's
* faces LIVE (`skills/<hand>/faces/`). `drewKinds` says which face kinds this
* hand's probe reads actually CAME BACK with — a hand that answers with
* `imageset-grid` is accountable for how imageset draws even though the faces
* library still keeps that family. Neither is a list anyone maintains: the
* first is a path on disk, the second is a recorded read.
*/
export function renderFor(
file: RenderFile | null,
skill: string,
drewKinds: readonly string[] = [],
): { values: Record<string, boolean>; families: string[] } | null {
if (!file) return null;
const families = [...new Set([
...(file.skill_families[skill] ?? []),
...drewKinds.flatMap((kind) => file.kinds.filter((row) => row.kind === kind).map((row) => row.family)),
])];
if (!families.length) return { values: {}, families: [] };
const keys = new Set(families.flatMap((family) => Object.keys(file.families[family] ?? {})));
const values: Record<string, boolean> = {};
for (const key of keys) values[key] = families.every((family) => file.families[family]?.[key] === true);
return { values, families };
}
/** The face kinds this hand's recorded reads actually answered with. */
export function faceKindsDrawn(slice: ProbeCorpus | null): string[] {
return [...new Set((slice?.reads ?? []).flatMap((read) => read.face?.kind ? [read.face.kind] : []))];
}
{
"scenarios": [
{
"id": "contract-drift-train",
"split": "train",
"calls": [
"lint snappy-tool-design",
"example snappy-tool-design lint"
],
"ground_truth": {
"rule": 6,
"description_matches_contract": true
}
},
{
"id": "loader-drift-train",
"split": "train",
"calls": [
"lint snappy-gmail",
"fix-loader snappy-gmail",
"lint snappy-gmail"
],
"ground_truth": {
"loader_contract_block": "current after fix-loader"
}
},
{
"id": "bounded-read-holdout",
"split": "holdout",
"calls": [
"lint --all --summary",
"lint snappy-gmail"
],
"ground_truth": {
"rule": 17,
"unbounded_collection_reads_are_reported": true
}
},
{
"id": "derived-first-call-holdout",
"split": "holdout",
"calls": [
"lint snappy-imessage",
"example snappy-imessage recent",
"lint snappy-gmail"
],
"ground_truth": {
"rule": 59,
"optional_positional_before_limit_is_reported": true,
"offender": "snappy-imessage recent(contact?, limit?)"
}
}
]
}
{
"scenarios": [
{
"id": "contract-drift-train",
"split": "train",
"calls": [
"lint snappy-tool-design",
"example snappy-tool-design lint"
],
"ground_truth": {
"rule": 6,
"description_matches_contract": true
}
},
{
"id": "loader-drift-train",
"split": "train",
"calls": [
"lint snappy-gmail",
"fix-loader snappy-gmail",
"lint snappy-gmail"
],
"ground_truth": {
"loader_contract_block": "current after fix-loader"
}
},
{
"id": "bounded-read-holdout",
"split": "holdout",
"calls": [
"lint --all --summary",
"lint snappy-gmail"
],
"ground_truth": {
"rule": 17,
"unbounded_collection_reads_are_reported": true
}
},
{
"id": "derived-first-call-holdout",
"split": "holdout",
"calls": [
"lint snappy-imessage",
"example snappy-imessage recent",
"lint snappy-gmail"
],
"ground_truth": {
"rule": 59,
"optional_positional_before_limit_is_reported": true,
"offender": "snappy-imessage recent(contact?, limit?)"
}
}
]
}
/**
* THE MENU BAR, DRIVEN WITHOUT A SYNTHETIC KEYSTROKE.
*
* The bar's popover is a WKWebView over a `snappy://bar` custom scheme, which
* is deliberately NOT an http origin — `WebHost.swift` says why: the MCP Apps
* host protocol is postMessage between the host page and the view's iframe, and
* only a real same-origin pair completes the transport. That choice is right
* for the product and it is exactly what made this host unmeasurable: there is
* no port to open, and the one thing left — typing into the popover — is
* synthetic keystrokes, which this desktop swallows behind a TCC dialog.
*
* SO THE PAGE IS RUN WHERE IT CAN BE DRIVEN, AND NOTHING ABOUT IT IS REWRITTEN.
* The rig serves the shipped `index.html`, `bar.css` and `bar-host.js` — the
* BUILT bundle out of the app the owner installs, byte for byte — from one
* http origin, and answers `/view.html` the same way `BarSchemeHandler` does:
* `resources/read ui://snappy/faces` from the runner, unedited. What Swift
* owns is replaced by the smallest possible stand-in and NOTHING ELSE:
*
* `window.webkit.messageHandlers.bar` — the one wire the page has to the
* native side. Every `kind` the page sends is answered here the way
* `Bar.swift`'s switchboard answers it, by reaching the SAME runner door.
*
* THE BEARER NEVER ENTERS THE PAGE. `Bar.swift` holds the token and the page
* asks it to call; here the RIG SERVER holds it and the page posts to `/mcp`
* on its own origin. The shim has no token, the DOM has no token, and a
* screenshot of this page could be published. The token is read from a
* 600-mode file by the server process itself and is never an argument.
*
* WHAT THIS CAN AND CANNOT PROVE. It proves the page's own road: the ask, the
* runner's ranking, the execute, the AppBridge handshake, the face mounting in
* an iframe with its doors. It does not prove the Swift switchboard or the
* popover's window, and the cells say so — a cell that claimed the .app itself
* would be a status with no artifact under it.
*/
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { PROMPTS, SHOT_ROOT, type HostId, type Observation } from "./hosts.ts";
const say = (line: string) => console.log(line);
/** The built bar the owner actually installs. Never the .mjs source: a rig
* that bundled its own copy would be measuring a build nobody ships. */
export const BAR_RESOURCE_DIRS = [
"/Applications/Snappy.app/Contents/Resources",
`${process.env.HOME}/Projects/snappy-runner-menu-bar-2/apps/snappy-bar/dist/Snappy.app/Contents/Resources`,
`${process.env.HOME}/Projects/snappy-runner/apps/snappy-bar/dist/Snappy.app/Contents/Resources`,
];
export function barResources(candidates: readonly string[] = BAR_RESOURCE_DIRS): string | null {
for (const dir of candidates) if (existsSync(join(dir, "bar-host.js")) && existsSync(join(dir, "index.html"))) return dir;
return null;
}
/** The stand-in for Swift, and the whole of it. Loaded BEFORE the page's own
* module, because `bar-host.js` posts to `window.webkit` from its first line. */
export function barShim(): string {
return `/* the rig's stand-in for Bar.swift's switchboard — no bearer, one road */
const rpc = async (method, params) => {
const res = await fetch("/mcp", { method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params }) });
return await res.json();
};
const call = async (tool, args) => {
const json = await rpc("tools/call", { name: tool, arguments: args });
if (json.error) return { ok: false, refusal: String(json.error.message || "the runner refused") };
return { ok: true, result: json.result };
};
const answer = async (kind, payload, args) => {
if (kind === "ready") {
const meta = await (await fetch("/meta")).json();
return { endpoint: meta.endpoint, health: meta.health, version: "rig", dev: false, tab: "made", home: "", homeIsDefault: true, publicMcp: meta.publicMcp };
}
if (kind === "kinds") return { kinds: (await (await fetch("/meta")).json()).draws || [] };
if (kind === "faces") {
const said = await call("execute", { name: "snappy-faces", verb: "faces", arguments: { json: true } });
const families = said.ok ? said.result?.structuredContent?.result?.hand_result : null;
return Array.isArray(families) ? { families } : { error: said.refusal || "snappy-faces faces did not answer" };
}
if (kind === "readResource") {
// THE CHUNK ROAD, STOOD IN FOR ⟨lane menu-bar-4, 2026-09-09⟩. The faces
// core asks its host for \`ui://snappy/faces/<slug>.js\`; \`Bar.swift\`
// answers it with \`RunnerClient.readResource\`, so the rig answers it with
// the same \`resources/read\`. Before this the page's own
// \`bridge.onreadresource\` returned \`{contents: []}\` and every face past
// the core sat on "Loading the <family> family…" — which is why this arm
// exists on BOTH roads or on neither.
const said = await rpc("resources/read", { uri: String(payload.uri) });
const text = said?.result?.contents?.[0]?.text;
return typeof text === "string" ? { text, mimeType: "text/javascript" } : { error: String(said?.error?.message || "the runner read no text for " + payload.uri) };
}
if (kind === "call") return await call(String(payload.name), args || {});
if (kind === "execute") return await call("execute", args || {});
if (kind === "sync") return { rows: [] };
if (kind === "collection") return { hands: [] };
if (kind === "connections") return { url: "", places: [], rows: [] };
if (kind === "log") { (window.__rigLog = window.__rigLog || []).push(payload.line); return null; }
return null;
};
window.webkit = { messageHandlers: { bar: { postMessage(message) {
Promise.resolve().then(() => answer(message.kind, message.payload || {}, message.arguments || {}))
.then((out) => window.__bar && window.__bar.settle(message.id, out))
.catch((error) => window.__bar && window.__bar.settle(message.id, { error: String(error) }));
} } } };
`;
}
/** The page, unedited except for one script tag before its own module. */
export function barIndexHtml(shipped: string): string {
const at = shipped.indexOf('<script type="module"');
if (at < 0) throw new Error("the shipped index.html has no module script — the rig would load the page without its host");
return `${shipped.slice(0, at)}<script src="/shim.js"></script>\n ${shipped.slice(at)}`;
}
/** The rig server: static bytes, `/view.html` from the runner's own resource,
* and `/mcp` proxied WITH the bearer this process holds and the page does not. */
export function barServerSource(): string {
return `import { createServer } from "node:http";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { decodeBrowserEval } from "./browser-eval.ts";
const ROOT = process.env.RIG_ROOT, URL_ = process.env.RIG_RUNNER, TOKEN = readFileSync(process.env.RIG_TOKEN_FILE, "utf8").trim();
const TYPES = { html: "text/html; charset=utf-8", js: "application/javascript; charset=utf-8", css: "text/css; charset=utf-8" };
let session = null, view = null, draws = [];
async function rpc(body) {
const headers = { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: "Bearer " + TOKEN };
if (session) headers["mcp-session-id"] = session;
const res = await fetch(URL_, { method: "POST", headers, body: JSON.stringify(body) });
session = res.headers.get("mcp-session-id") ?? session;
const text = await res.text();
const line = text.split("\\n").find((l) => l.startsWith("data: "));
return text.trim() === "" ? {} : JSON.parse(line ? line.slice(6) : text);
}
await rpc({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "hosts-bar-rig", version: "1" } } });
await rpc({ jsonrpc: "2.0", method: "notifications/initialized" });
async function faces() {
if (view !== null) return view;
const said = await rpc({ jsonrpc: "2.0", id: 2, method: "resources/read", params: { uri: "ui://snappy/faces" } });
const first = said?.result?.contents?.[0];
view = String(first?.text ?? "<!doctype html><body>ui://snappy/faces answered with no document");
draws = first?._meta?.["snappy-skills/draws"] ?? [];
return view;
}
createServer(async (req, res) => {
const path = req.url.split("?")[0];
if (req.method === "POST" && path === "/mcp") {
let body = ""; for await (const chunk of req) body += chunk;
const said = await rpc(JSON.parse(body));
res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(said)); return;
}
if (path === "/view.html") { const html = await faces(); res.writeHead(200, { "content-type": TYPES.html }); res.end(html); return; }
if (path === "/meta") { await faces(); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ endpoint: URL_, health: { ok: true }, publicMcp: "", draws })); return; }
const name = path === "/" ? "index.html" : path.slice(1);
try {
const bytes = readFileSync(join(ROOT, name));
res.writeHead(200, { "content-type": TYPES[name.split(".").pop()] ?? "application/octet-stream" }); res.end(bytes);
} catch { res.writeHead(404); res.end("no " + name); }
}).listen(Number(process.env.RIG_PORT), "127.0.0.1", () => console.log("rig " + process.env.RIG_PORT));
`;
}
export interface BarRig { origin: string; root: string; child: ChildProcess }
export function startBarRig(resources: string, runnerUrl: string, tokenFile: string, port: number): BarRig {
const root = join(SHOT_ROOT, "bar");
mkdirSync(root, { recursive: true });
for (const name of ["bar-host.js", "bar.css"]) copyFileSync(join(resources, name), join(root, name));
writeFileSync(join(root, "index.html"), barIndexHtml(readFileSync(join(resources, "index.html"), "utf8")));
writeFileSync(join(root, "shim.js"), barShim());
const server = join(SHOT_ROOT, "bar-rig.mjs");
writeFileSync(server, barServerSource());
const child = spawn(process.execPath, [server], {
env: { ...process.env, RIG_ROOT: root, RIG_RUNNER: runnerUrl, RIG_TOKEN_FILE: tokenFile, RIG_PORT: String(port) },
stdio: ["ignore", "pipe", "pipe"], detached: false,
});
return { origin: `http://127.0.0.1:${port}`, root, child };
}
function browser(args: string[], timeoutMs = 90_000): { ok: boolean; out: string } {
try {
return { ok: true, out: execFileSync("agent-browser", ["--session", "hosts-bar", ...args], { encoding: "utf8", timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }) };
} catch (error) {
const shown = error as { stdout?: string; stderr?: string; message?: string };
return { ok: false, out: `${shown.stdout ?? ""}${shown.stderr ?? ""}${shown.message ?? ""}` };
}
}
/** What the rig read off the page. SHAPES ONLY — a length, a tag, a family
* word, a door word. Never a subject line, never a sender, never a body. */
export interface BarSight { mounted: boolean; title: string; sub: string; faceNodes: number; faceText: number; miss: string; doors: string[]; loop: string[] }
export function readSight(out: string): BarSight | null {
// `agent-browser eval` prints the RETURN VALUE json-encoded, so a string
// return arrives quoted-and-escaped. Reading it by slicing at the first
// brace gave `{\"mounted\":true…}` — invalid JSON — and every cell it fed
// came back not-driven while the screenshot beside it showed the face drawn.
// A reader that cannot read the artifact reports a false red, which is the
// status/artifact defect pointed the other way ⟨measured 2026-09-09⟩.
//
// THE PEELING ITSELF LIVES IN browser-eval.ts. This function's own copy of
// it was the SECOND hand-rolled decode of one encoding in this skill; the
// third (`render.ts`) mangled a quote and recorded a drawing face as THREW
// for three runs. `tolerateNoise` is what is left that belongs to this rig:
// the bar's SIGHT expression is driven through a command whose output may
// carry a status line before the value ⟨lane render-harness, 2026-09-09⟩.
const sight = decodeBrowserEval<BarSight>(out, { tolerateNoise: true });
return sight !== null && typeof sight === "object" && "mounted" in sight ? sight : null;
}
/** The ask a person types for this cell, and how the bar answers it.
* `show <kind>` is the bar's own road for a face nobody has run yet
* (`kindTyped` → `showKind`), which is how the DECISION face is drawn without
* staging a post — the doors are real and this rig presses neither. */
const BAR_ASK: Record<string, { typed: string; example: boolean }> = {
"gmail-inbox": { typed: "my gmail inbox", example: false },
"skool-feed": { typed: "my skool feed", example: false },
"linkedin-profile": { typed: "my linkedin profile", example: false },
"linkedin-decide": { typed: "show linkedin-decision", example: true },
};
/**
* THE ARTIFACT, NOT THE FRAME ⟨CLAUDE.md §10⟩. An `<iframe>` with bytes in it
* proves nothing: `/view.html` is the faces bundle, ~617 KB of inline script
* before a single face has been told anything, so "the frame is not empty" is
* green by absence. What would have to EXIST if a face had drawn is ELEMENTS
* UNDER `#snappy-faces-root` — the view's own mount point — and an empty
* `#snappy-face-miss`, which is where the view names a kind it could not draw.
*/
const SIGHT = `(() => {
const frame = document.querySelector("#stage iframe");
const row = document.querySelector("#history .row");
let nodes = -1, text = 0, miss = "", doors = [];
try {
const doc = frame && frame.contentDocument;
const root = doc && doc.getElementById("snappy-faces-root");
nodes = root ? root.querySelectorAll("*").length : -1;
text = root ? root.innerText.length : 0;
const said = doc && doc.getElementById("snappy-face-miss");
miss = said ? String(said.textContent || "").slice(0, 160) : "";
const words = root ? root.innerText : "";
doors = ["Post", "Later", "Approve", "Send", "Publish"].filter((w) => new RegExp("\\b" + w + "\\b").test(words));
} catch (e) { nodes = -2; }
return JSON.stringify({
mounted: frame !== null && nodes > 0 && miss === "",
title: row ? (row.querySelector(".row-title") || {}).textContent || "" : "",
sub: row ? (row.querySelector(".row-sub") || {}).textContent || "" : "",
faceNodes: nodes, faceText: text, miss, doors, loop: (window.__rigLog || []).slice(-3),
});
})()`;
export function driveMenubar(rig: BarRig | null, why: string, kinds: Record<string, string | null>): Observation[] {
if (rig === null) {
say(` menubar: NOT DRIVEN — ${why}`);
return PROMPTS.map((prompt) => ({ host: "menubar" as HostId, prompt: prompt.id, driven: false, reason: why }));
}
const out: Observation[] = [];
for (const prompt of PROMPTS) {
const plan = BAR_ASK[prompt.id];
const started = Date.now();
browser(["open", `${rig.origin}/index.html`]);
browser(["wait", "1200"]);
const typed = browser(["fill", "#ask", plan.typed]);
if (!typed.ok) {
const reason = `the rig's browser could not reach the bar page at ${rig.origin}: ${typed.out.trim().split("\n").slice(-1)[0]?.slice(0, 160)}`;
say(` menubar/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "menubar", prompt: prompt.id, driven: false, reason });
continue;
}
browser(["press", "Enter"]);
if (!plan.example) {
// The runner's own ranking, pressed the way a person presses it: the
// first row the bar offers. Nothing here types a verb.
const waited = browser(["wait", ".matches .match"], 60_000);
if (!waited.ok) {
const reason = `the bar's own search offered no skill row for "${plan.typed}" within 60 s, so there was nothing to press`;
say(` menubar/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "menubar", prompt: prompt.id, driven: false, reason });
continue;
}
browser(["click", ".matches .match"]);
}
const mounted = browser(["wait", "#stage iframe"], 90_000);
browser(["wait", "2500"]);
const sight = readSight(browser(["eval", SIGHT]).out);
const ms = Date.now() - started;
const shot = join(SHOT_ROOT, `menubar-${prompt.id}.png`);
browser(["screenshot", shot]);
if (sight === null || sight.mounted !== true) {
const reason = `no face mounted in the bar's stage for "${plan.typed}"${mounted.ok ? "" : " (no iframe appeared within 90 s)"}`
+ (sight === null
? " — and the rig could not read the page at all, which is its own defect and not the bar's"
: `; #snappy-faces-root held ${sight.faceNodes} element(s)${sight.miss === "" ? "" : ` and the view named a miss: ${sight.miss}`}${sight.loop.length ? `; the page logged: ${sight.loop.join(" | ").slice(0, 200)}` : ""}`);
say(` menubar/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "menubar", prompt: prompt.id, driven: false, reason, shot });
continue;
}
const doors = sight.doors.length ? ` doors=[${sight.doors.join(", ")}]` : "";
say(` menubar/${prompt.id}: mounted "${sight.title}" ${sight.faceNodes} nodes${doors} ${ms} ms`);
out.push({
host: "menubar", prompt: prompt.id, cardElement: true, answered: true, kind: kinds[prompt.id] ?? null, ms, shot,
press: prompt.press === null ? null : {
word: prompt.press, reached_runner: false, within_ms: null,
how: "the doors are drawn and pressable in the face's own frame; this rig presses none — a press is the owner's, and pressing Later here would be answering for him.",
},
reason: `the bar's own page mounted a face in its stage: ${sight.faceNodes} element(s) under #snappy-faces-root inside the stage <iframe>, ${sight.faceText} character(s) of drawn text, history row "${sight.title}"${doors}`
+ (plan.example ? ", drawn as the faces skill's shipped example rather than a staged draft — the bar's `show <kind>` road, so nothing was staged to reach it" : "")
+ ". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves.",
});
}
return out;
}
/**
* THE MENU BAR, DRIVEN WITHOUT A SYNTHETIC KEYSTROKE.
*
* The bar's popover is a WKWebView over a `snappy://bar` custom scheme, which
* is deliberately NOT an http origin — `WebHost.swift` says why: the MCP Apps
* host protocol is postMessage between the host page and the view's iframe, and
* only a real same-origin pair completes the transport. That choice is right
* for the product and it is exactly what made this host unmeasurable: there is
* no port to open, and the one thing left — typing into the popover — is
* synthetic keystrokes, which this desktop swallows behind a TCC dialog.
*
* SO THE PAGE IS RUN WHERE IT CAN BE DRIVEN, AND NOTHING ABOUT IT IS REWRITTEN.
* The rig serves the shipped `index.html`, `bar.css` and `bar-host.js` — the
* BUILT bundle out of the app the owner installs, byte for byte — from one
* http origin, and answers `/view.html` the same way `BarSchemeHandler` does:
* `resources/read ui://snappy/faces` from the runner, unedited. What Swift
* owns is replaced by the smallest possible stand-in and NOTHING ELSE:
*
* `window.webkit.messageHandlers.bar` — the one wire the page has to the
* native side. Every `kind` the page sends is answered here the way
* `Bar.swift`'s switchboard answers it, by reaching the SAME runner door.
*
* THE BEARER NEVER ENTERS THE PAGE. `Bar.swift` holds the token and the page
* asks it to call; here the RIG SERVER holds it and the page posts to `/mcp`
* on its own origin. The shim has no token, the DOM has no token, and a
* screenshot of this page could be published. The token is read from a
* 600-mode file by the server process itself and is never an argument.
*
* WHAT THIS CAN AND CANNOT PROVE. It proves the page's own road: the ask, the
* runner's ranking, the execute, the AppBridge handshake, the face mounting in
* an iframe with its doors. It does not prove the Swift switchboard or the
* popover's window, and the cells say so — a cell that claimed the .app itself
* would be a status with no artifact under it.
*/
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { PROMPTS, SHOT_ROOT, type HostId, type Observation } from "./hosts.ts";
const say = (line: string) => console.log(line);
/** The built bar the owner actually installs. Never the .mjs source: a rig
* that bundled its own copy would be measuring a build nobody ships. */
export const BAR_RESOURCE_DIRS = [
"/Applications/Snappy.app/Contents/Resources",
`${process.env.HOME}/Projects/snappy-runner-menu-bar-2/apps/snappy-bar/dist/Snappy.app/Contents/Resources`,
`${process.env.HOME}/Projects/snappy-runner/apps/snappy-bar/dist/Snappy.app/Contents/Resources`,
];
export function barResources(candidates: readonly string[] = BAR_RESOURCE_DIRS): string | null {
for (const dir of candidates) if (existsSync(join(dir, "bar-host.js")) && existsSync(join(dir, "index.html"))) return dir;
return null;
}
/** The stand-in for Swift, and the whole of it. Loaded BEFORE the page's own
* module, because `bar-host.js` posts to `window.webkit` from its first line. */
export function barShim(): string {
return `/* the rig's stand-in for Bar.swift's switchboard — no bearer, one road */
const rpc = async (method, params) => {
const res = await fetch("/mcp", { method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params }) });
return await res.json();
};
const call = async (tool, args) => {
const json = await rpc("tools/call", { name: tool, arguments: args });
if (json.error) return { ok: false, refusal: String(json.error.message || "the runner refused") };
return { ok: true, result: json.result };
};
const answer = async (kind, payload, args) => {
if (kind === "ready") {
const meta = await (await fetch("/meta")).json();
return { endpoint: meta.endpoint, health: meta.health, version: "rig", dev: false, tab: "made", home: "", homeIsDefault: true, publicMcp: meta.publicMcp };
}
if (kind === "kinds") return { kinds: (await (await fetch("/meta")).json()).draws || [] };
if (kind === "faces") {
const said = await call("execute", { name: "snappy-faces", verb: "faces", arguments: { json: true } });
const families = said.ok ? said.result?.structuredContent?.result?.hand_result : null;
return Array.isArray(families) ? { families } : { error: said.refusal || "snappy-faces faces did not answer" };
}
if (kind === "readResource") {
// THE CHUNK ROAD, STOOD IN FOR ⟨lane menu-bar-4, 2026-09-09⟩. The faces
// core asks its host for \`ui://snappy/faces/<slug>.js\`; \`Bar.swift\`
// answers it with \`RunnerClient.readResource\`, so the rig answers it with
// the same \`resources/read\`. Before this the page's own
// \`bridge.onreadresource\` returned \`{contents: []}\` and every face past
// the core sat on "Loading the <family> family…" — which is why this arm
// exists on BOTH roads or on neither.
const said = await rpc("resources/read", { uri: String(payload.uri) });
const text = said?.result?.contents?.[0]?.text;
return typeof text === "string" ? { text, mimeType: "text/javascript" } : { error: String(said?.error?.message || "the runner read no text for " + payload.uri) };
}
if (kind === "call") return await call(String(payload.name), args || {});
if (kind === "execute") return await call("execute", args || {});
if (kind === "sync") return { rows: [] };
if (kind === "collection") return { hands: [] };
if (kind === "connections") return { url: "", places: [], rows: [] };
if (kind === "log") { (window.__rigLog = window.__rigLog || []).push(payload.line); return null; }
return null;
};
window.webkit = { messageHandlers: { bar: { postMessage(message) {
Promise.resolve().then(() => answer(message.kind, message.payload || {}, message.arguments || {}))
.then((out) => window.__bar && window.__bar.settle(message.id, out))
.catch((error) => window.__bar && window.__bar.settle(message.id, { error: String(error) }));
} } } };
`;
}
/** The page, unedited except for one script tag before its own module. */
export function barIndexHtml(shipped: string): string {
const at = shipped.indexOf('<script type="module"');
if (at < 0) throw new Error("the shipped index.html has no module script — the rig would load the page without its host");
return `${shipped.slice(0, at)}<script src="/shim.js"></script>\n ${shipped.slice(at)}`;
}
/** The rig server: static bytes, `/view.html` from the runner's own resource,
* and `/mcp` proxied WITH the bearer this process holds and the page does not. */
export function barServerSource(): string {
return `import { createServer } from "node:http";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { decodeBrowserEval } from "./browser-eval.ts";
const ROOT = process.env.RIG_ROOT, URL_ = process.env.RIG_RUNNER, TOKEN = readFileSync(process.env.RIG_TOKEN_FILE, "utf8").trim();
const TYPES = { html: "text/html; charset=utf-8", js: "application/javascript; charset=utf-8", css: "text/css; charset=utf-8" };
let session = null, view = null, draws = [];
async function rpc(body) {
const headers = { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: "Bearer " + TOKEN };
if (session) headers["mcp-session-id"] = session;
const res = await fetch(URL_, { method: "POST", headers, body: JSON.stringify(body) });
session = res.headers.get("mcp-session-id") ?? session;
const text = await res.text();
const line = text.split("\\n").find((l) => l.startsWith("data: "));
return text.trim() === "" ? {} : JSON.parse(line ? line.slice(6) : text);
}
await rpc({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "hosts-bar-rig", version: "1" } } });
await rpc({ jsonrpc: "2.0", method: "notifications/initialized" });
async function faces() {
if (view !== null) return view;
const said = await rpc({ jsonrpc: "2.0", id: 2, method: "resources/read", params: { uri: "ui://snappy/faces" } });
const first = said?.result?.contents?.[0];
view = String(first?.text ?? "<!doctype html><body>ui://snappy/faces answered with no document");
draws = first?._meta?.["snappy-skills/draws"] ?? [];
return view;
}
createServer(async (req, res) => {
const path = req.url.split("?")[0];
if (req.method === "POST" && path === "/mcp") {
let body = ""; for await (const chunk of req) body += chunk;
const said = await rpc(JSON.parse(body));
res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(said)); return;
}
if (path === "/view.html") { const html = await faces(); res.writeHead(200, { "content-type": TYPES.html }); res.end(html); return; }
if (path === "/meta") { await faces(); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ endpoint: URL_, health: { ok: true }, publicMcp: "", draws })); return; }
const name = path === "/" ? "index.html" : path.slice(1);
try {
const bytes = readFileSync(join(ROOT, name));
res.writeHead(200, { "content-type": TYPES[name.split(".").pop()] ?? "application/octet-stream" }); res.end(bytes);
} catch { res.writeHead(404); res.end("no " + name); }
}).listen(Number(process.env.RIG_PORT), "127.0.0.1", () => console.log("rig " + process.env.RIG_PORT));
`;
}
export interface BarRig { origin: string; root: string; child: ChildProcess }
export function startBarRig(resources: string, runnerUrl: string, tokenFile: string, port: number): BarRig {
const root = join(SHOT_ROOT, "bar");
mkdirSync(root, { recursive: true });
for (const name of ["bar-host.js", "bar.css"]) copyFileSync(join(resources, name), join(root, name));
writeFileSync(join(root, "index.html"), barIndexHtml(readFileSync(join(resources, "index.html"), "utf8")));
writeFileSync(join(root, "shim.js"), barShim());
const server = join(SHOT_ROOT, "bar-rig.mjs");
writeFileSync(server, barServerSource());
const child = spawn(process.execPath, [server], {
env: { ...process.env, RIG_ROOT: root, RIG_RUNNER: runnerUrl, RIG_TOKEN_FILE: tokenFile, RIG_PORT: String(port) },
stdio: ["ignore", "pipe", "pipe"], detached: false,
});
return { origin: `http://127.0.0.1:${port}`, root, child };
}
function browser(args: string[], timeoutMs = 90_000): { ok: boolean; out: string } {
try {
return { ok: true, out: execFileSync("agent-browser", ["--session", "hosts-bar", ...args], { encoding: "utf8", timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }) };
} catch (error) {
const shown = error as { stdout?: string; stderr?: string; message?: string };
return { ok: false, out: `${shown.stdout ?? ""}${shown.stderr ?? ""}${shown.message ?? ""}` };
}
}
/** What the rig read off the page. SHAPES ONLY — a length, a tag, a family
* word, a door word. Never a subject line, never a sender, never a body. */
export interface BarSight { mounted: boolean; title: string; sub: string; faceNodes: number; faceText: number; miss: string; doors: string[]; loop: string[] }
export function readSight(out: string): BarSight | null {
// `agent-browser eval` prints the RETURN VALUE json-encoded, so a string
// return arrives quoted-and-escaped. Reading it by slicing at the first
// brace gave `{\"mounted\":true…}` — invalid JSON — and every cell it fed
// came back not-driven while the screenshot beside it showed the face drawn.
// A reader that cannot read the artifact reports a false red, which is the
// status/artifact defect pointed the other way ⟨measured 2026-09-09⟩.
//
// THE PEELING ITSELF LIVES IN browser-eval.ts. This function's own copy of
// it was the SECOND hand-rolled decode of one encoding in this skill; the
// third (`render.ts`) mangled a quote and recorded a drawing face as THREW
// for three runs. `tolerateNoise` is what is left that belongs to this rig:
// the bar's SIGHT expression is driven through a command whose output may
// carry a status line before the value ⟨lane render-harness, 2026-09-09⟩.
const sight = decodeBrowserEval<BarSight>(out, { tolerateNoise: true });
return sight !== null && typeof sight === "object" && "mounted" in sight ? sight : null;
}
/** The ask a person types for this cell, and how the bar answers it.
* `show <kind>` is the bar's own road for a face nobody has run yet
* (`kindTyped` → `showKind`), which is how the DECISION face is drawn without
* staging a post — the doors are real and this rig presses neither. */
const BAR_ASK: Record<string, { typed: string; example: boolean }> = {
"gmail-inbox": { typed: "my gmail inbox", example: false },
"skool-feed": { typed: "my skool feed", example: false },
"linkedin-profile": { typed: "my linkedin profile", example: false },
"linkedin-decide": { typed: "show linkedin-decision", example: true },
};
/**
* THE ARTIFACT, NOT THE FRAME ⟨CLAUDE.md §10⟩. An `<iframe>` with bytes in it
* proves nothing: `/view.html` is the faces bundle, ~617 KB of inline script
* before a single face has been told anything, so "the frame is not empty" is
* green by absence. What would have to EXIST if a face had drawn is ELEMENTS
* UNDER `#snappy-faces-root` — the view's own mount point — and an empty
* `#snappy-face-miss`, which is where the view names a kind it could not draw.
*/
const SIGHT = `(() => {
const frame = document.querySelector("#stage iframe");
const row = document.querySelector("#history .row");
let nodes = -1, text = 0, miss = "", doors = [];
try {
const doc = frame && frame.contentDocument;
const root = doc && doc.getElementById("snappy-faces-root");
nodes = root ? root.querySelectorAll("*").length : -1;
text = root ? root.innerText.length : 0;
const said = doc && doc.getElementById("snappy-face-miss");
miss = said ? String(said.textContent || "").slice(0, 160) : "";
const words = root ? root.innerText : "";
doors = ["Post", "Later", "Approve", "Send", "Publish"].filter((w) => new RegExp("\\b" + w + "\\b").test(words));
} catch (e) { nodes = -2; }
return JSON.stringify({
mounted: frame !== null && nodes > 0 && miss === "",
title: row ? (row.querySelector(".row-title") || {}).textContent || "" : "",
sub: row ? (row.querySelector(".row-sub") || {}).textContent || "" : "",
faceNodes: nodes, faceText: text, miss, doors, loop: (window.__rigLog || []).slice(-3),
});
})()`;
export function driveMenubar(rig: BarRig | null, why: string, kinds: Record<string, string | null>): Observation[] {
if (rig === null) {
say(` menubar: NOT DRIVEN — ${why}`);
return PROMPTS.map((prompt) => ({ host: "menubar" as HostId, prompt: prompt.id, driven: false, reason: why }));
}
const out: Observation[] = [];
for (const prompt of PROMPTS) {
const plan = BAR_ASK[prompt.id];
const started = Date.now();
browser(["open", `${rig.origin}/index.html`]);
browser(["wait", "1200"]);
const typed = browser(["fill", "#ask", plan.typed]);
if (!typed.ok) {
const reason = `the rig's browser could not reach the bar page at ${rig.origin}: ${typed.out.trim().split("\n").slice(-1)[0]?.slice(0, 160)}`;
say(` menubar/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "menubar", prompt: prompt.id, driven: false, reason });
continue;
}
browser(["press", "Enter"]);
if (!plan.example) {
// The runner's own ranking, pressed the way a person presses it: the
// first row the bar offers. Nothing here types a verb.
const waited = browser(["wait", ".matches .match"], 60_000);
if (!waited.ok) {
const reason = `the bar's own search offered no skill row for "${plan.typed}" within 60 s, so there was nothing to press`;
say(` menubar/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "menubar", prompt: prompt.id, driven: false, reason });
continue;
}
browser(["click", ".matches .match"]);
}
const mounted = browser(["wait", "#stage iframe"], 90_000);
browser(["wait", "2500"]);
const sight = readSight(browser(["eval", SIGHT]).out);
const ms = Date.now() - started;
const shot = join(SHOT_ROOT, `menubar-${prompt.id}.png`);
browser(["screenshot", shot]);
if (sight === null || sight.mounted !== true) {
const reason = `no face mounted in the bar's stage for "${plan.typed}"${mounted.ok ? "" : " (no iframe appeared within 90 s)"}`
+ (sight === null
? " — and the rig could not read the page at all, which is its own defect and not the bar's"
: `; #snappy-faces-root held ${sight.faceNodes} element(s)${sight.miss === "" ? "" : ` and the view named a miss: ${sight.miss}`}${sight.loop.length ? `; the page logged: ${sight.loop.join(" | ").slice(0, 200)}` : ""}`);
say(` menubar/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "menubar", prompt: prompt.id, driven: false, reason, shot });
continue;
}
const doors = sight.doors.length ? ` doors=[${sight.doors.join(", ")}]` : "";
say(` menubar/${prompt.id}: mounted "${sight.title}" ${sight.faceNodes} nodes${doors} ${ms} ms`);
out.push({
host: "menubar", prompt: prompt.id, cardElement: true, answered: true, kind: kinds[prompt.id] ?? null, ms, shot,
press: prompt.press === null ? null : {
word: prompt.press, reached_runner: false, within_ms: null,
how: "the doors are drawn and pressable in the face's own frame; this rig presses none — a press is the owner's, and pressing Later here would be answering for him.",
},
reason: `the bar's own page mounted a face in its stage: ${sight.faceNodes} element(s) under #snappy-faces-root inside the stage <iframe>, ${sight.faceText} character(s) of drawn text, history row "${sight.title}"${doors}`
+ (plan.example ? ", drawn as the faces skill's shipped example rather than a staged draft — the bar's `show <kind>` road, so nothing was staged to reach it" : "")
+ ". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves.",
});
}
return out;
}
api.ts hosts — the same four asks, put to every host, counted. Written
2026-09-09 after one green cell (the Gmail card mounting in the ChatGPT app)
and the owner's answer: "this is barely scraping it — it has to truly be
seamless and work every six ways from here to Sunday and back, for LinkedIn,
for Skool, for Statechange, for everything under God's sky."
bashnpx tsx api.ts hosts --token-file <600-mode file holding the runner bearer>
npx tsx api.ts hosts --only chatgpt,claude --out /tmp/hosts-run
--url picks the runner door the road pre-check drives (default: the mini's).
--runner-face points at snappy-runner/src/face.ts, whose VERB_SHAPE fold
is READ rather than copied. --codex-model picks the model codex exec uses.
--bar-port moves the menu-bar rig off 3196. --already names cards a previous
run already put on a host, so a decision is not sent to the owner twice to
re-measure itself — the prior photo id is carried as the artifact and the cell
says which run made it. Its spelling is ONE comma-separated flag, with the run
that made them named beside it:
bash--already 'linkedin-decide=8386,gmail-inbox=8387,skool-feed=8388,linkedin-profile=8389' \
--already-when '2026-09-09 03:5x'
A prior send can never buy a green: linkedin-decide still needs a press, and
a standing card has no ref to match a press against, so that cell reads
"press pending" however long the card has been on his phone. That is the
honest red — it is not evidence he did not press.
A CELL THIS RUN DID NOT TIME IS A CELL THIS RUN DID NOT DRAW. A carried card
has no milliseconds, and written **mounted** ? ms it read on the owner's
glass exactly like one this run made (hosts-5, 2026-09-09). The message id
rides the cell as standing and the ledger prints `mounted · standing
#8387` instead.
| id | what a person types | press |
|---|---|---|
gmail-inbox |
Using Snappy Skills, show me my Gmail inbox, three messages | — |
skool-feed |
Using Snappy Skills, show me my Skool feed | — |
linkedin-profile |
Using Snappy Skills, show me my LinkedIn profile | — |
linkedin-decide |
Using Snappy Skills, draft a LinkedIn post about launch checklists and let me decide | Later |
Later is the ONLY word this battery presses. The press must prove the road
from the host back to the runner, and proving it with a send would post to a
person to measure a wire.
| id | surface | road |
|---|---|---|
chatgpt |
widget | agent-browser --state chatgpt → chatgpt.com |
claude |
widget | agent-browser --state claude → claude.ai |
codex |
none | codex exec --json, mcp_servers.snappy-skills |
menubar |
webview | the SHIPPED bar bundle over the rig's own origin (hosts-bar.ts). The REAL app is drivable — scripts/drive.swift for ⌥⌘S and the keys, snappy-cleanshot ax Snappy for the artifact, since a snappy://bar page has no DOM to eval — and was driven by hand on 2026-09-09 (HOSTS-LEDGER.md). It is not this column until it is a driver. |
telegram |
image | snappy-telegram show <kind> --from - (the runner's own answer, piped in) + decisions |
A terminal has NO card surface, so a codex cell can never be mounted and
saying so is the measurement, not a failure of the driver — what is worth
knowing there is whether the ask reached the MCP and whether the answer NAMED
a face.
recordCell is the ONE decider — a driver returns an observation and can
never invent a green.
observation without one THROWS rather than writing a blank cell.
On linkedin-decide green additionally requires press.reached_runner — a
card that draws and cannot be acted on is half the product, and half counted
as whole is the status/artifact defect.
roadCheck used to carry a literal verb list (tries: ["feed", "posts"]), so
skool-feed scored two hops against a list this repo maintained while the
runner's own search had been naming posts for that sentence for days. The
ask now goes to search word for word and the top match's hand and verb ARE
the first call, with only the argument words that verb's own contract names.
The ROUTING PREAMBLE comes off first. "Using Snappy Skills," is the address on
the envelope — it tells a chat host which MCP to reach and every host strips it
before it composes an intent. Measured 2026-09-09: with the preamble the Skool
ask ranks snappy-gateway get (a refusal); without it, snappy-skool posts in
one hop.
The popover is a snappy://bar custom scheme ON PURPOSE — MCP Apps needs a
same-origin host/view pair (WebHost.swift) — so there is no port to open, and
the only road left is synthetic keystrokes, which a TCC dialog swallows. The
rig serves the SHIPPED index.html, bar.css and bar-host.js out of the
built Snappy.app, byte for byte, answers /view.html exactly as
BarSchemeHandler does (resources/read ui://snappy/faces, unedited), and
stands in for the ONE thing Swift owns: window.webkit.messageHandlers.bar.
THE BEARER NEVER ENTERS THE PAGE. The rig server holds it — read from a
600-mode file by that process — and the page posts to /mcp on its own origin.
The shim has no token and a screenshot of the page could be published.
What it proves: the ask, the runner's ranking, the execute, the AppBridge
handshake, the face mounting in an iframe with its doors. What it does not:
the Swift switchboard or the popover window. The cells say so.
The artifact is ELEMENTS UNDER #snappy-faces-root with an empty
#snappy-face-miss — never "the iframe has bytes in it", because /view.html
is ~617 KB of inline script before a face has been told anything.
| block | what it answers |
|---|---|
road |
the same asks put to the RUNNER'S own door, so a red cell can be READ: did a face exist to draw at all, and how many calls did the person's own words cost? Deliberately NOT in the number. |
face_families.unreachable |
THE SAME QUESTION FROM THE FAMILY'S END, and not the same cell. One per PUBLISHED family, green only when a READ NAMES IT — a hand the runner's own familyForHand routes there holding a verb that folds onto a shape. Built 2026-09-09 because the hand axis is blind to a family no hand lands on: the library gained six that day (krisp, knowledge, transcripts, imageset, walkthrough, doc) and the number moved by two. Measured on kernel fde3385: krisp has its hand and no shapeable verb; imageset and doc have no hand of their name at all, though the manifest's notes say which hands they were drawn for. Each red says WHICH, because the fixes differ — one word in the runner's VERB_SHAPE, or a name that meets. |
face_families |
which platform hands land on a family snappy-faces does not declare — the red no host driver can fix. Derived as the runner derives (snappy-runner/src/face.ts): a verb whose word folds onto one of the manifest's shapes makes a hand DRAWABLE, and its family is its own name against FACE_FAMILIES. Both tables are read from their owners at call time. |
no_shape |
hands with no verb the face road can even SHAPE — they never reach the family question, so they answer prose in every host however many families exist. snappy-krisp fetch-meetings is the worked example. Reported, never counted: most rows are legitimately faceless, and the fix where there is one is a word in the runner's own VERB_SHAPE. |
clusters |
the reds grouped by the one thing that would have to change, each written out as a lane brief under briefs/. |
tool-design-hosts.json (schema snappy-tool-design/hosts@1),
HOSTS-LEDGER.md — which loop folds into LEDGER.md — and one brief per
cluster. Screenshots and driver logs live under /tmp/hosts-battery, OUTSIDE
the repo: the owner's glass is not a commit. The corpus carries SHAPES never
values, and hosts.test.ts fails on an address, a phone number, a token-shaped
string, or a screenshot path inside the repo.
linkedin-decide cell did exactly that: no read had run, so the driver sent
linkedin-decision's own example — Mara Quill's invented draft — and the row
explained itself in prose the matrix did not read. The owner's rule that
morning is the rule now: "nothing he sees is a fixture." A cell whose read
cannot run on this Mac is not-driven WITH the hand's refusal, which IS the
measurement; the denominator never moves for it.
element that would have to exist if the face had DRAWN, and count that.
families some hand already reaches, so six new families moved the number by
two and the run would have reported a bigger library as no change at all.
measured against and reading the difference as a product change. MEASURED
2026-09-09: against a local runner whose search names no verb, the matrix
read 1/20 while the SHIPPED bar drew the Gmail and Skool faces minutes
earlier on the same Mac. The runner's door belongs beside the number. PROVE
THE ROAD BEFORE COUNTING: GET <url-without-/mcp>/health with the same
bearer must answer revision and it must be the kernel head the number is
being attributed to. Measured hosts-5: 8d37ad10f9-2b589e8db210-f57a6ec39c,
98 hands, and the matrix went 1/20 → 8/20 on the bearer alone.
CLOSED 2026-09-09 (hosts-6): proveRunner is now the FIRST thing the run
does — before a single ask — and it THROWS rather than degrading, so a door
that cannot say which revision it serves writes no corpus at all. The proof
rides in the corpus as runner (mcp_door, health_door, revision,
hands, kernel_head, attributed) and the ledger prints it on the same
line as the number: counted against <revision> through <door>. When the
runner is not on this tree's head the line SAYS so, because a number
attributed to a head no runner is serving is the status/artifact defect in
its purest form. The head is stored abbreviated to twelve characters on
purpose: a full 40-character sha is indistinguishable from a bearer to the
corpus's own ^[A-Za-z0-9_-]{40,}$ guard, and exempting "shas we know are
fine" is how the next token gets through.
eval` prints its return value json-encoded, and slicing at the first brace
reported four false reds while the screenshots beside them showed the faces.
# The hosts battery
`api.ts hosts` — the same four asks, put to every host, counted. Written
2026-09-09 after one green cell (the Gmail card mounting in the ChatGPT app)
and the owner's answer: "this is barely scraping it — it has to truly be
seamless and work every six ways from here to Sunday and back, for LinkedIn,
for Skool, for Statechange, for everything under God's sky."
## Run it
```bash
npx tsx api.ts hosts --token-file <600-mode file holding the runner bearer>
npx tsx api.ts hosts --only chatgpt,claude --out /tmp/hosts-run
```
`--url` picks the runner door the road pre-check drives (default: the mini's).
`--runner-face` points at `snappy-runner/src/face.ts`, whose `VERB_SHAPE` fold
is READ rather than copied. `--codex-model` picks the model `codex exec` uses.
`--bar-port` moves the menu-bar rig off 3196. `--already` names cards a previous
run already put on a host, so a decision is not sent to the owner twice to
re-measure itself — the prior photo id is carried as the artifact and the cell
says which run made it. Its spelling is ONE comma-separated flag, with the run
that made them named beside it:
```bash
--already 'linkedin-decide=8386,gmail-inbox=8387,skool-feed=8388,linkedin-profile=8389' \
--already-when '2026-09-09 03:5x'
```
A prior send can never buy a green: `linkedin-decide` still needs a press, and
a standing card has no `ref` to match a press against, so that cell reads
"press pending" however long the card has been on his phone. That is the
honest red — it is not evidence he did not press.
A CELL THIS RUN DID NOT TIME IS A CELL THIS RUN DID NOT DRAW. A carried card
has no milliseconds, and written `**mounted** ? ms` it read on the owner's
glass exactly like one this run made (hosts-5, 2026-09-09). The message id
rides the cell as `standing` and the ledger prints `**mounted** · standing
#8387` instead.
## The prompts, the same for every host
| id | what a person types | press |
|---|---|---|
| `gmail-inbox` | Using Snappy Skills, show me my Gmail inbox, three messages | — |
| `skool-feed` | Using Snappy Skills, show me my Skool feed | — |
| `linkedin-profile` | Using Snappy Skills, show me my LinkedIn profile | — |
| `linkedin-decide` | Using Snappy Skills, draft a LinkedIn post about launch checklists and let me decide | `Later` |
`Later` is the ONLY word this battery presses. The press must prove the road
from the host back to the runner, and proving it with a send would post to a
person to measure a wire.
## The hosts
| id | surface | road |
|---|---|---|
| `chatgpt` | widget | `agent-browser --state chatgpt` → chatgpt.com |
| `claude` | widget | `agent-browser --state claude` → claude.ai |
| `codex` | none | `codex exec --json`, `mcp_servers.snappy-skills` |
| `menubar` | webview | the SHIPPED bar bundle over the rig's own origin (`hosts-bar.ts`). The REAL app is drivable — `scripts/drive.swift` for ⌥⌘S and the keys, `snappy-cleanshot ax Snappy` for the artifact, since a `snappy://bar` page has no DOM to eval — and was driven by hand on 2026-09-09 (HOSTS-LEDGER.md). It is not this column until it is a driver. |
| `telegram` | image | `snappy-telegram show <kind> --from -` (the runner's own answer, piped in) + `decisions` |
A terminal has NO card surface, so a codex cell can never be `mounted` and
saying so is the measurement, not a failure of the driver — what is worth
knowing there is whether the ask reached the MCP and whether the answer NAMED
a face.
## How a cell is coloured
`recordCell` is the ONE decider — a driver returns an observation and can
never invent a green.
- **mounted** — a card was found in that host's own DOM. The only green.
- **text** — the host answered without one. Red: the product's claim is the drawing.
- **not-driven** — the host was never reached. Red, with the reason; an
observation without one THROWS rather than writing a blank cell.
On `linkedin-decide` green additionally requires `press.reached_runner` — a
card that draws and cannot be acted on is half the product, and half counted
as whole is the status/artifact defect.
## The first call is the runner's own ranking
`roadCheck` used to carry a literal verb list (`tries: ["feed", "posts"]`), so
`skool-feed` scored two hops against a list this repo maintained while the
runner's own `search` had been naming `posts` for that sentence for days. The
ask now goes to `search` word for word and the top match's hand and verb ARE
the first call, with only the argument words that verb's own contract names.
The ROUTING PREAMBLE comes off first. "Using Snappy Skills," is the address on
the envelope — it tells a chat host which MCP to reach and every host strips it
before it composes an intent. Measured 2026-09-09: with the preamble the Skool
ask ranks `snappy-gateway get` (a refusal); without it, `snappy-skool posts` in
one hop.
## The menu bar, driven without a keystroke
The popover is a `snappy://bar` custom scheme ON PURPOSE — MCP Apps needs a
same-origin host/view pair (`WebHost.swift`) — so there is no port to open, and
the only road left is synthetic keystrokes, which a TCC dialog swallows. The
rig serves the SHIPPED `index.html`, `bar.css` and `bar-host.js` out of the
built Snappy.app, byte for byte, answers `/view.html` exactly as
`BarSchemeHandler` does (`resources/read ui://snappy/faces`, unedited), and
stands in for the ONE thing Swift owns: `window.webkit.messageHandlers.bar`.
THE BEARER NEVER ENTERS THE PAGE. The rig server holds it — read from a
600-mode file by that process — and the page posts to `/mcp` on its own origin.
The shim has no token and a screenshot of the page could be published.
What it proves: the ask, the runner's ranking, the execute, the AppBridge
handshake, the face mounting in an iframe with its doors. What it does not:
the Swift switchboard or the popover window. The cells say so.
The artifact is ELEMENTS UNDER `#snappy-faces-root` with an empty
`#snappy-face-miss` — never "the iframe has bytes in it", because `/view.html`
is ~617 KB of inline script before a face has been told anything.
## The blocks beside the matrix
| block | what it answers |
|---|---|
| `road` | the same asks put to the RUNNER'S own door, so a red cell can be READ: did a face exist to draw at all, and how many calls did the person's own words cost? Deliberately NOT in the number. |
| `face_families.unreachable` | THE SAME QUESTION FROM THE FAMILY'S END, and not the same cell. One per PUBLISHED family, green only when a READ NAMES IT — a hand the runner's own `familyForHand` routes there holding a verb that folds onto a shape. Built 2026-09-09 because the hand axis is blind to a family no hand lands on: the library gained six that day (krisp, knowledge, transcripts, imageset, walkthrough, doc) and the number moved by two. Measured on kernel fde3385: `krisp` has its hand and no shapeable verb; `imageset` and `doc` have no hand of their name at all, though the manifest's notes say which hands they were drawn for. Each red says WHICH, because the fixes differ — one word in the runner's `VERB_SHAPE`, or a name that meets. |
| `face_families` | which platform hands land on a family `snappy-faces` does not declare — the red no host driver can fix. Derived as the runner derives (`snappy-runner/src/face.ts`): a verb whose word folds onto one of the manifest's shapes makes a hand DRAWABLE, and its family is its own name against `FACE_FAMILIES`. Both tables are read from their owners at call time. |
| `no_shape` | hands with no verb the face road can even SHAPE — they never reach the family question, so they answer prose in every host however many families exist. `snappy-krisp fetch-meetings` is the worked example. Reported, never counted: most rows are legitimately faceless, and the fix where there is one is a word in the runner's own `VERB_SHAPE`. |
| `clusters` | the reds grouped by the one thing that would have to change, each written out as a lane brief under `briefs/`. |
## What it writes
`tool-design-hosts.json` (schema `snappy-tool-design/hosts@1`),
`HOSTS-LEDGER.md` — which `loop` folds into `LEDGER.md` — and one brief per
cluster. Screenshots and driver logs live under `/tmp/hosts-battery`, OUTSIDE
the repo: the owner's glass is not a commit. The corpus carries SHAPES never
values, and `hosts.test.ts` fails on an address, a phone number, a token-shaped
string, or a screenshot path inside the repo.
## The anti-fixes
- Driving the loop's OWN AppBridge host and reporting a host cell green.
- Dropping a cell that could not be driven. A shrinking denominator flatters.
- Recording a face as mounted because the tool result NAMED a kind.
- Sending anything — a card to a phone, a post to a feed — to score a cell.
- Drawing a telegram cell over the hand's SHIPPED FIXTURE. Until 2026-09-09 the
`linkedin-decide` cell did exactly that: no read had run, so the driver sent
`linkedin-decision`'s own example — Mara Quill's invented draft — and the row
explained itself in prose the matrix did not read. The owner's rule that
morning is the rule now: "nothing he sees is a fixture." A cell whose read
cannot run on this Mac is `not-driven` WITH the hand's refusal, which IS the
measurement; the denominator never moves for it.
- Calling a cell mounted because the host's frame is not empty. Name the
element that would have to exist if the face had DRAWN, and count that.
- Counting only the hands. A family axis derived from the hands can only see
families some hand already reaches, so six new families moved the number by
two and the run would have reported a bigger library as no change at all.
- Driving the battery at a runner other than the one the number was last
measured against and reading the difference as a product change. MEASURED
2026-09-09: against a local runner whose `search` names no verb, the matrix
read 1/20 while the SHIPPED bar drew the Gmail and Skool faces minutes
earlier on the same Mac. The runner's door belongs beside the number. PROVE
THE ROAD BEFORE COUNTING: `GET <url-without-/mcp>/health` with the same
bearer must answer `revision` and it must be the kernel head the number is
being attributed to. Measured hosts-5: `8d37ad10f9-2b589e8db210-f57a6ec39c`,
98 hands, and the matrix went 1/20 → 8/20 on the bearer alone.
CLOSED 2026-09-09 (hosts-6): `proveRunner` is now the FIRST thing the run
does — before a single ask — and it THROWS rather than degrading, so a door
that cannot say which revision it serves writes no corpus at all. The proof
rides in the corpus as `runner` (`mcp_door`, `health_door`, `revision`,
`hands`, `kernel_head`, `attributed`) and the ledger prints it on the same
line as the number: `counted against <revision> through <door>`. When the
runner is not on this tree's head the line SAYS so, because a number
attributed to a head no runner is serving is the status/artifact defect in
its purest form. The head is stored abbreviated to twelve characters on
purpose: a full 40-character sha is indistinguishable from a bearer to the
corpus's own `^[A-Za-z0-9_-]{40,}$` guard, and exempting "shas we know are
fine" is how the next token gets through.
- Reading the rig's own answer with a parser that cannot read it: `agent-browser
eval` prints its return value json-encoded, and slicing at the first brace
reported four false reds while the screenshots beside them showed the faces.
/**
* THE HOST DRIVERS — the impure half of the battery.
*
* `hosts.ts` decides what a cell IS; this file goes and finds out. They are
* split because the deciding is the part that must be tested without a
* browser, a phone or a network, and the finding out is the part that cannot
* be tested at all — only run, with its reasons written down.
*
* EVERY DRIVER RETURNS OBSERVATIONS, NEVER CELLS. One recorder colours a cell
* (`recordCell`), so a driver cannot invent a green by returning one. A driver
* that cannot reach its host returns `driven: false` WITH the reason and, when
* the fix belongs to the owner rather than to a lane, the one line he runs.
*
* THE OWNER'S BROWSER STATE IS READ, NEVER WRITTEN AND NEVER EXPORTED. The
* web hosts are driven with `agent-browser --state <name>`, which is a saved
* cookie jar he made once. This file lists the names; it never opens one,
* never copies one, and nothing it writes carries a cookie.
*/
import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { HOSTS, PROMPTS, SHOT_ROOT, type HostId, type Observation, type PressRecord } from "./hosts.ts";
const say = (line: string) => console.log(line);
function run(file: string, args: string[], timeoutMs: number): { ok: boolean; out: string } {
try {
return { ok: true, out: execFileSync(file, args, { encoding: "utf8", timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }) };
} catch (error) {
const shown = error as { stdout?: string; stderr?: string; message?: string };
return { ok: false, out: `${shown.stdout ?? ""}${shown.stderr ?? ""}${shown.message ?? ""}` };
}
}
/* ── THE WEB HOSTS ─────────────────────────────────────────────────────────*/
/**
* The saved cookie jars `agent-browser` holds, by name.
*
* `state list` prints `<session>-<state>.json` rows under its sessions
* directory. A host is drivable when a state whose name contains the host's
* own word exists — that is the ONLY thing checked, because opening one to be
* sure would be using the owner's login to test whether he has a login.
*/
export function savedStates(): string[] {
const listed = run("agent-browser", ["state", "list"], 30_000);
if (!listed.ok) return [];
return listed.out.split("\n").map((line) => line.trim().split(" ")[0]).filter((name) => name.endsWith(".json"));
}
export function hasStateFor(host: "chatgpt" | "claude", states: readonly string[]): boolean {
const word = host === "chatgpt" ? /chatgpt|openai/i : /claude|anthropic/i;
return states.some((name) => word.test(name));
}
/**
* A web host, driven through the owner's own saved session.
*
* NOT IMPLEMENTED PAST THE GATE, ON PURPOSE. Every ask to chatgpt.com or
* claude.ai needs the owner's logged-in cookie jar, and this machine holds
* none: 185 saved states, zero for either host (measured 2026-09-09). Writing
* the click path against a host nobody here can open would be code proven by
* nothing, and a driver that has never once run is the same lie as a green
* cell — so the gate returns the honest not-driven observation and the one
* line the owner runs, and the click path is written by the lane that can
* watch it work.
*/
export function driveWebHost(host: "chatgpt" | "claude", states: readonly string[]): Observation[] {
const spec = HOSTS.find((h) => h.id === host)!;
if (!hasStateFor(host, states)) {
const reason = `no saved agent-browser state for ${host} — ${states.length} state(s) exist on this Mac and none match /${host}/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.`;
say(` ${host}: NOT DRIVEN — ${reason}`);
return PROMPTS.map((prompt) => ({ host, prompt: prompt.id, driven: false, reason }));
}
const reason = `a saved ${host} state exists but this battery has no click path for it yet — the driver is written by the lane that can watch it run against the real host.`;
say(` ${host}: NOT DRIVEN — ${reason}`);
return PROMPTS.map((prompt) => ({ host, prompt: prompt.id, driven: false, reason }));
}
/* ── CODEX ─────────────────────────────────────────────────────────────────*/
export interface CodexEvent { type?: string; item?: { type?: string; name?: string; text?: string; message?: string; result?: unknown }; message?: string }
/**
* What a codex turn did with one ask.
*
* A terminal has NO CARD SURFACE — there is nothing in a codex turn that could
* hold an iframe — so a codex cell can never be `mounted` and saying so is the
* measurement, not a failure of the driver. What IS worth knowing is whether
* the ask reached the Skills MCP at all and whether the answer NAMED a face:
* a named face with no surface is a host ceiling, and a turn that never
* reached the MCP is a road defect. The two are different briefs.
*/
export function readCodexTurn(lines: readonly string[]): { answered: boolean; reachedMcp: boolean; kind: string | null; failure: string | null; warnings: string[] } {
let reachedMcp = false;
let answered = false;
let kind: string | null = null;
let failure: string | null = null;
// A WARNING IS NOT THE FAILURE, and reporting the first error item as the
// reason a host could not be driven names the wrong thing ⟨measured
// 2026-09-09: the skills-budget warning was reported over the usage limit
// that actually ended every turn⟩. `turn.failed` outranks every item.
const warnings: string[] = [];
for (const line of lines) {
let event: CodexEvent;
try { event = JSON.parse(line) as CodexEvent; } catch { continue; }
const item = event.item ?? {};
if (item.type === "mcp_tool_call" || /snappy[-_]skills/i.test(String(item.name ?? ""))) reachedMcp = true;
const text = `${item.text ?? ""}${JSON.stringify(item.result ?? "")}`;
if (!reachedMcp && /"tool"\s*:\s*"execute"|snappy-skills/.test(text)) reachedMcp = true;
const named = text.match(/"kind"\s*:\s*"([a-z0-9-]+)"/);
if (named && kind === null) kind = named[1];
if (event.type === "item.completed" && item.type === "agent_message") answered = true;
if (event.type === "turn.completed") answered = true;
if (event.type === "item.completed" && item.type === "error") {
warnings.push(String(item.message ?? "").slice(0, 300));
}
if (event.type === "turn.failed" || event.type === "error") {
const said = String((event as { error?: { message?: string } }).error?.message ?? event.message ?? "turn failed");
// codex wraps its API error as JSON inside the message; the sentence a
// person can act on is inside it, so it is unwrapped rather than shown raw.
const inner = said.match(/"message"\s*:\s*"((?:[^"\\]|\\.)*)"/)?.[1];
failure = (inner ?? said).replace(/\\"/g, '"').slice(0, 300);
}
}
if (failure === null && !answered && warnings.length) failure = warnings[0];
return { answered, reachedMcp, kind, failure, warnings };
}
/** A usage limit is the ACCOUNT'S window, not this ask's ⟨measured 2026-09-09:
* four prompts × ~3.5 min of retries to learn the same sentence four times,
* 14 minutes of every battery run⟩. So the first one that says so answers for
* the host, and the other three carry the SAME reason rather than a guess. */
export function isUsageWindow(failure: string | null): boolean {
return failure !== null && /usage limit|purchase more credits|try again at/i.test(failure);
}
export function driveCodex(model: string): Observation[] {
const out: Observation[] = [];
let windowClosed: string | null = null;
for (const prompt of PROMPTS) {
if (windowClosed !== null) {
say(` codex/${prompt.id}: NOT DRIVEN — the same window, not re-driven`);
out.push({ host: "codex", prompt: prompt.id, driven: false, reason: `${windowClosed} (the ask was not re-driven: the window belongs to the account and had already answered for this host)` });
continue;
}
const started = Date.now();
const turn = run("codex", ["exec", "--json", "-m", model, "--sandbox", "read-only", "--skip-git-repo-check", prompt.text], 420_000);
const read = readCodexTurn(turn.out.split("\n"));
const ms = Date.now() - started;
if (!read.answered && read.failure) {
const also = read.warnings.length ? ` (also, every turn: ${read.warnings[0]})` : "";
say(` codex/${prompt.id}: NOT DRIVEN — ${read.failure}`);
const reason = `codex exec failed before answering: ${read.failure}${also}`;
if (isUsageWindow(read.failure)) windowClosed = reason;
out.push({ host: "codex", prompt: prompt.id, driven: false, reason });
continue;
}
const reason = read.reachedMcp
? `codex reached the Skills MCP and answered in ${ms} ms; a terminal has no card surface, so the face was ${read.kind ? `named "${read.kind}" and` : "not named and"} never drawn`
: `codex answered without reaching the Skills MCP at all in ${ms} ms — the ask never became a tool call`;
say(` codex/${prompt.id}: text (mcp=${read.reachedMcp} kind=${read.kind ?? "-"}) ${ms} ms`);
out.push({ host: "codex", prompt: prompt.id, cardElement: false, answered: true, kind: read.kind, reason, ms });
}
return out;
}
/* ── THE MENU BAR ──────────────────────────────────────────────────────────
* `hosts-bar.ts`, and only there. This file used to hold a `driveMenubar` that
* probed three ports for an http origin the bar has never had — the popover is
* a `snappy://bar` custom scheme by design — so it could only ever return
* not-driven, and it did, for every run. The real driver runs the SHIPPED
* bundle over a rig origin and is one road, not a second one beside it.
*/
/* ── TELEGRAM ──────────────────────────────────────────────────────────────
*
* THE ONE SEND THIS BATTERY IS ARMED FOR ⟨owner, 2026-09-09⟩. The standing rail
* is "no sends EXCEPT the owner's own Telegram chat for proofs", and a card on
* his own phone IS the Telegram cell — there is no other way to measure this
* host, because its whole surface is a picture that arrives. So the driver
* sends, to `TELEGRAM_ROBERT_CHAT_ID` and nowhere else, ONE message per ask and
* never a second round.
*
* THE ARTIFACT IS THE PHOTO MESSAGE ID ⟨CLAUDE.md §10⟩. `show` prints
* `message_id` for the photo Telegram accepted; a cell is `mounted` only when
* that number came back, because it is the thing that would have to exist if
* the card were on his phone. The hand's own words about having sent it are a
* status, and a status is not the artifact.
*
* THE PRESS IS HIS AND STAYS HIS. The decision card goes out with its doors
* pressable and this lane presses NOTHING — not even `Later`, which on this
* host is a real button in a real chat and pressing it would be answering for
* him. So `decisions` is READ for a press that arrived on its own, and a card
* that drew with nobody having pressed is "shown, press pending": red, by the
* same rule that makes a drawn-but-unactionable face half the product.
*
* WHY THE DATA COMES FROM THE ROAD AND NOT FROM A FIXTURE. The face is drawn
* over whatever the runner's own `execute` printed for that ask, so the cell
* measures the SAME answer every other host would have drawn. An ask the
* runner never answered has no data, and its cell is not-driven with that as
* the reason rather than a pretty picture of a fixture.
*/
/** What the road left for one ask: the face the runner named and the file its
* own answer was written to, OUTSIDE the repo. */
export interface TelegramAsk {
prompt: string;
kind: string | null;
/** A path under SHOT_ROOT holding the hand's printed answer. */
dataFile: string | null;
/** The door words this face is sent with, when it is a decision. */
doors: string[] | null;
/** Why there is nothing to draw, when kind is null. */
gap: string | null;
}
export interface TelegramShown { message_id?: number; sent?: { width?: number; height?: number }; doors?: string[]; ref?: string; unanswerable?: boolean }
/** The last JSON value the hand printed — `--json` puts the face's own object
* on stdout after the human line, so the object is taken and the prose is not
* parsed for facts. */
export function lastJson(out: string): unknown {
const lines = out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("{") || l.startsWith("["));
for (let i = lines.length - 1; i >= 0; i -= 1) {
try { return JSON.parse(lines[i]!); } catch { /* the next one up */ }
}
// The hand pretty-prints; fall back to the first balanced object in the tail.
const brace = out.indexOf("{");
if (brace < 0) return null;
try { return JSON.parse(out.slice(brace)); } catch { return null; }
}
/** Did the owner press a door on this card? Read from the hand's own
* `decisions`, matched on the `ref` this send packed into its callback data. */
export function pressedRef(decisionsOut: string, ref: string): { word: string } | null {
const rows = lastJson(decisionsOut);
if (!Array.isArray(rows)) return null;
for (const row of rows) {
const r = row as { ref?: unknown; door?: unknown; word?: unknown; label?: unknown };
if (typeof r.ref === "string" && r.ref === ref) {
const word = [r.door, r.word, r.label].find((w): w is string => typeof w === "string");
return { word: word ?? "pressed" };
}
}
return null;
}
/**
* A CARD ALREADY ON HIS PHONE IS NOT SENT AGAIN ⟨rail: at most three messages⟩.
* The decision card went out on the 2026-09-09 06:0x run as photo message_id
* 8386 and is still in the chat with its doors unpressed. Re-sending it to
* re-measure it would put a second identical decision in front of him to score
* ourselves, which is the thing the read asks are careful not to do. So the
* prior photo id is carried as the artifact, the cell SAYS which run put it
* there, and `decisions` is read once for a press that arrived on its own.
* The cell stays red until it did — a prior send can never buy a green.
*/
/** THE ONE PRESS READER for this host, so the standing card and the freshly
* sent one cannot answer the question differently. */
export function readPress(handApiTs: string, word: string | null, ref: string): PressRecord | null {
if (word === null) return null;
const read = run("npx", ["tsx", handApiTs, "decisions", "--json"], 120_000);
const hit = ref === "" ? null : pressedRef(read.out, ref);
return {
word,
reached_runner: hit !== null,
within_ms: null,
how: `snappy-telegram decisions, matched on the ref the send packed (${ref === "" ? "no ref to match — a press could only be read if the send had named one" : "ref present"}). `
+ `Nothing here presses: the doors are on the owner's own phone and pressing one would be answering for him.`,
};
}
export function driveTelegram(handApiTs: string, asks: readonly TelegramAsk[], already: Record<string, { message_id: number; when: string }> = {}): Observation[] {
if (!existsSync(handApiTs)) {
const reason = `snappy-telegram's api.ts is not at ${handApiTs.replace(process.env.HOME ?? "~", "~")}, so this host has no hand to send through.`;
say(` telegram: NOT DRIVEN — ${reason}`);
return PROMPTS.map((prompt) => ({ host: "telegram" as HostId, prompt: prompt.id, driven: false, reason }));
}
const out: Observation[] = [];
for (const prompt of PROMPTS) {
const ask = asks.find((a) => a.prompt === prompt.id);
if (ask === undefined || ask.kind === null) {
const reason = ask?.gap ?? `the road named no face for ${prompt.id}, so there is nothing for this host to draw.`;
say(` telegram/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "telegram", prompt: prompt.id, driven: false, reason });
continue;
}
// ── A FIXTURE IS NOT A MEASUREMENT ⟨the owner, 2026-09-09 10:32⟩ ──────
// "whatever is rendered is not fake data; it exercises the same roads."
// This driver used to send the hand's shipped example when the road left
// no data file, and say so in the cell's reason — a picture of Mara Quill
// scored as a picture of his inbox, with a sentence underneath nobody
// reads. The hand now REFUSES to draw without `--from`, so the only
// honest cell is not-driven with the read that could not run, which is
// the measurement. The denominator does not move: the cell still exists,
// it is just red for the true reason.
if (ask.dataFile === null) {
const reason = `no read answered for ${prompt.id} on this Mac, so there is no real data to draw. `
+ `This host sends the answer of a real read (\`--from -\`) and nothing else; the hand refuses a card with no read behind it.`;
say(` telegram/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "telegram", prompt: prompt.id, driven: false, reason });
continue;
}
const standing = already[prompt.id];
if (standing !== undefined) {
const press = readPress(handApiTs, prompt.press, "");
say(` telegram/${prompt.id}: standing card message_id=${standing.message_id} (not resent)${press === null ? "" : press.reached_runner ? " PRESSED" : " press pending"}`);
out.push({
host: "telegram", prompt: prompt.id, cardElement: true, answered: true, kind: ask.kind, ms: null, press, standing: standing.message_id,
reason: `the card is already on the owner's own chat as photo message_id ${standing.message_id}, put there by the ${standing.when} run, and this lane did not send a second copy of a decision to measure itself`
+ (press === null ? "" : press.reached_runner ? ` — its ${press.word} door was pressed` : " — its doors are still unpressed; shown, press pending"),
});
continue;
}
const args = ["tsx", handApiTs, "show", ask.kind, "--json", "--from", ask.dataFile];
if (ask.doors !== null) args.push("--doors", JSON.stringify(ask.doors));
const started = Date.now();
const sent = run("npx", args, 300_000);
const ms = Date.now() - started;
const shown = (lastJson(sent.out) ?? {}) as TelegramShown;
// THE NUMBER, NOT THE SENTENCE. `message_id` is the photo Telegram
// accepted; without it nothing arrived, however the run exited.
if (typeof shown.message_id !== "number") {
const reason = `snappy-telegram show ${ask.kind} returned no message_id, so no card reached the chat: ${sent.out.trim().split("\n").slice(-2).join(" ").slice(0, 200)}`;
say(` telegram/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "telegram", prompt: prompt.id, driven: false, reason });
continue;
}
const press = readPress(handApiTs, prompt.press, typeof shown.ref === "string" ? shown.ref : "");
const drew = shown.sent?.width !== undefined ? ` ${shown.sent.width}x${shown.sent.height}` : "";
const doors = Array.isArray(shown.doors) && shown.doors.length ? ` doors=[${shown.doors.join(", ")}]` : "";
say(` telegram/${prompt.id}: mounted ${ask.kind}${drew}${doors} message_id=${shown.message_id} ${ms} ms${press === null ? "" : press.reached_runner ? " PRESSED" : " press pending"}`);
out.push({
host: "telegram", prompt: prompt.id, cardElement: true, answered: true, kind: ask.kind, ms, press,
reason: `the card arrived on the owner's own chat as photo message_id ${shown.message_id}${drew}${doors}, drawn over the runner's own answer to the ask`
+ (press === null ? "" : press.reached_runner ? ` and its ${press.word} door was pressed` : `, and its doors are unpressed — shown, press pending; the press is the owner's`),
});
}
return out;
}
/* ── THE PRESS ─────────────────────────────────────────────────────────────
* The axis nothing measured before: a card drew, a person pressed LATER, and
* did the RUNNER hear it? The artifact that would have to exist is a row in
* the mini's own calls.jsonl for that hand, after the press, inside the
* window. Its absence is the whole finding — a face that draws and cannot be
* acted on is half the product.
*/
export function pressReachedRunner(hand: string, sinceIso: string, windowMs: number): PressRecord | null {
const tail = run("ssh", ["-o", "ConnectTimeout=8", "mini", "tail -200 ~/.snappy-skills/calls.jsonl"], 30_000);
if (!tail.ok) return null;
const since = Date.parse(sinceIso);
const hit = tail.out.split("\n").some((line) => {
if (!line.includes(`"skill":"${hand}"`) || !line.includes('"tool":"execute"')) return false;
const at = line.match(/"at":"([^"]+)"/)?.[1];
if (at === undefined) return false;
const stamp = Date.parse(at);
return stamp >= since && stamp - since <= windowMs;
});
return { word: "Later", reached_runner: hit, within_ms: hit ? windowMs : null, how: `mini ~/.snappy-skills/calls.jsonl gained an execute for ${hand} within ${windowMs} ms of the press` };
}
export { SHOT_ROOT, existsSync };
/**
* THE HOST DRIVERS — the impure half of the battery.
*
* `hosts.ts` decides what a cell IS; this file goes and finds out. They are
* split because the deciding is the part that must be tested without a
* browser, a phone or a network, and the finding out is the part that cannot
* be tested at all — only run, with its reasons written down.
*
* EVERY DRIVER RETURNS OBSERVATIONS, NEVER CELLS. One recorder colours a cell
* (`recordCell`), so a driver cannot invent a green by returning one. A driver
* that cannot reach its host returns `driven: false` WITH the reason and, when
* the fix belongs to the owner rather than to a lane, the one line he runs.
*
* THE OWNER'S BROWSER STATE IS READ, NEVER WRITTEN AND NEVER EXPORTED. The
* web hosts are driven with `agent-browser --state <name>`, which is a saved
* cookie jar he made once. This file lists the names; it never opens one,
* never copies one, and nothing it writes carries a cookie.
*/
import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { HOSTS, PROMPTS, SHOT_ROOT, type HostId, type Observation, type PressRecord } from "./hosts.ts";
const say = (line: string) => console.log(line);
function run(file: string, args: string[], timeoutMs: number): { ok: boolean; out: string } {
try {
return { ok: true, out: execFileSync(file, args, { encoding: "utf8", timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] }) };
} catch (error) {
const shown = error as { stdout?: string; stderr?: string; message?: string };
return { ok: false, out: `${shown.stdout ?? ""}${shown.stderr ?? ""}${shown.message ?? ""}` };
}
}
/* ── THE WEB HOSTS ─────────────────────────────────────────────────────────*/
/**
* The saved cookie jars `agent-browser` holds, by name.
*
* `state list` prints `<session>-<state>.json` rows under its sessions
* directory. A host is drivable when a state whose name contains the host's
* own word exists — that is the ONLY thing checked, because opening one to be
* sure would be using the owner's login to test whether he has a login.
*/
export function savedStates(): string[] {
const listed = run("agent-browser", ["state", "list"], 30_000);
if (!listed.ok) return [];
return listed.out.split("\n").map((line) => line.trim().split(" ")[0]).filter((name) => name.endsWith(".json"));
}
export function hasStateFor(host: "chatgpt" | "claude", states: readonly string[]): boolean {
const word = host === "chatgpt" ? /chatgpt|openai/i : /claude|anthropic/i;
return states.some((name) => word.test(name));
}
/**
* A web host, driven through the owner's own saved session.
*
* NOT IMPLEMENTED PAST THE GATE, ON PURPOSE. Every ask to chatgpt.com or
* claude.ai needs the owner's logged-in cookie jar, and this machine holds
* none: 185 saved states, zero for either host (measured 2026-09-09). Writing
* the click path against a host nobody here can open would be code proven by
* nothing, and a driver that has never once run is the same lie as a green
* cell — so the gate returns the honest not-driven observation and the one
* line the owner runs, and the click path is written by the lane that can
* watch it work.
*/
export function driveWebHost(host: "chatgpt" | "claude", states: readonly string[]): Observation[] {
const spec = HOSTS.find((h) => h.id === host)!;
if (!hasStateFor(host, states)) {
const reason = `no saved agent-browser state for ${host} — ${states.length} state(s) exist on this Mac and none match /${host}/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.`;
say(` ${host}: NOT DRIVEN — ${reason}`);
return PROMPTS.map((prompt) => ({ host, prompt: prompt.id, driven: false, reason }));
}
const reason = `a saved ${host} state exists but this battery has no click path for it yet — the driver is written by the lane that can watch it run against the real host.`;
say(` ${host}: NOT DRIVEN — ${reason}`);
return PROMPTS.map((prompt) => ({ host, prompt: prompt.id, driven: false, reason }));
}
/* ── CODEX ─────────────────────────────────────────────────────────────────*/
export interface CodexEvent { type?: string; item?: { type?: string; name?: string; text?: string; message?: string; result?: unknown }; message?: string }
/**
* What a codex turn did with one ask.
*
* A terminal has NO CARD SURFACE — there is nothing in a codex turn that could
* hold an iframe — so a codex cell can never be `mounted` and saying so is the
* measurement, not a failure of the driver. What IS worth knowing is whether
* the ask reached the Skills MCP at all and whether the answer NAMED a face:
* a named face with no surface is a host ceiling, and a turn that never
* reached the MCP is a road defect. The two are different briefs.
*/
export function readCodexTurn(lines: readonly string[]): { answered: boolean; reachedMcp: boolean; kind: string | null; failure: string | null; warnings: string[] } {
let reachedMcp = false;
let answered = false;
let kind: string | null = null;
let failure: string | null = null;
// A WARNING IS NOT THE FAILURE, and reporting the first error item as the
// reason a host could not be driven names the wrong thing ⟨measured
// 2026-09-09: the skills-budget warning was reported over the usage limit
// that actually ended every turn⟩. `turn.failed` outranks every item.
const warnings: string[] = [];
for (const line of lines) {
let event: CodexEvent;
try { event = JSON.parse(line) as CodexEvent; } catch { continue; }
const item = event.item ?? {};
if (item.type === "mcp_tool_call" || /snappy[-_]skills/i.test(String(item.name ?? ""))) reachedMcp = true;
const text = `${item.text ?? ""}${JSON.stringify(item.result ?? "")}`;
if (!reachedMcp && /"tool"\s*:\s*"execute"|snappy-skills/.test(text)) reachedMcp = true;
const named = text.match(/"kind"\s*:\s*"([a-z0-9-]+)"/);
if (named && kind === null) kind = named[1];
if (event.type === "item.completed" && item.type === "agent_message") answered = true;
if (event.type === "turn.completed") answered = true;
if (event.type === "item.completed" && item.type === "error") {
warnings.push(String(item.message ?? "").slice(0, 300));
}
if (event.type === "turn.failed" || event.type === "error") {
const said = String((event as { error?: { message?: string } }).error?.message ?? event.message ?? "turn failed");
// codex wraps its API error as JSON inside the message; the sentence a
// person can act on is inside it, so it is unwrapped rather than shown raw.
const inner = said.match(/"message"\s*:\s*"((?:[^"\\]|\\.)*)"/)?.[1];
failure = (inner ?? said).replace(/\\"/g, '"').slice(0, 300);
}
}
if (failure === null && !answered && warnings.length) failure = warnings[0];
return { answered, reachedMcp, kind, failure, warnings };
}
/** A usage limit is the ACCOUNT'S window, not this ask's ⟨measured 2026-09-09:
* four prompts × ~3.5 min of retries to learn the same sentence four times,
* 14 minutes of every battery run⟩. So the first one that says so answers for
* the host, and the other three carry the SAME reason rather than a guess. */
export function isUsageWindow(failure: string | null): boolean {
return failure !== null && /usage limit|purchase more credits|try again at/i.test(failure);
}
export function driveCodex(model: string): Observation[] {
const out: Observation[] = [];
let windowClosed: string | null = null;
for (const prompt of PROMPTS) {
if (windowClosed !== null) {
say(` codex/${prompt.id}: NOT DRIVEN — the same window, not re-driven`);
out.push({ host: "codex", prompt: prompt.id, driven: false, reason: `${windowClosed} (the ask was not re-driven: the window belongs to the account and had already answered for this host)` });
continue;
}
const started = Date.now();
const turn = run("codex", ["exec", "--json", "-m", model, "--sandbox", "read-only", "--skip-git-repo-check", prompt.text], 420_000);
const read = readCodexTurn(turn.out.split("\n"));
const ms = Date.now() - started;
if (!read.answered && read.failure) {
const also = read.warnings.length ? ` (also, every turn: ${read.warnings[0]})` : "";
say(` codex/${prompt.id}: NOT DRIVEN — ${read.failure}`);
const reason = `codex exec failed before answering: ${read.failure}${also}`;
if (isUsageWindow(read.failure)) windowClosed = reason;
out.push({ host: "codex", prompt: prompt.id, driven: false, reason });
continue;
}
const reason = read.reachedMcp
? `codex reached the Skills MCP and answered in ${ms} ms; a terminal has no card surface, so the face was ${read.kind ? `named "${read.kind}" and` : "not named and"} never drawn`
: `codex answered without reaching the Skills MCP at all in ${ms} ms — the ask never became a tool call`;
say(` codex/${prompt.id}: text (mcp=${read.reachedMcp} kind=${read.kind ?? "-"}) ${ms} ms`);
out.push({ host: "codex", prompt: prompt.id, cardElement: false, answered: true, kind: read.kind, reason, ms });
}
return out;
}
/* ── THE MENU BAR ──────────────────────────────────────────────────────────
* `hosts-bar.ts`, and only there. This file used to hold a `driveMenubar` that
* probed three ports for an http origin the bar has never had — the popover is
* a `snappy://bar` custom scheme by design — so it could only ever return
* not-driven, and it did, for every run. The real driver runs the SHIPPED
* bundle over a rig origin and is one road, not a second one beside it.
*/
/* ── TELEGRAM ──────────────────────────────────────────────────────────────
*
* THE ONE SEND THIS BATTERY IS ARMED FOR ⟨owner, 2026-09-09⟩. The standing rail
* is "no sends EXCEPT the owner's own Telegram chat for proofs", and a card on
* his own phone IS the Telegram cell — there is no other way to measure this
* host, because its whole surface is a picture that arrives. So the driver
* sends, to `TELEGRAM_ROBERT_CHAT_ID` and nowhere else, ONE message per ask and
* never a second round.
*
* THE ARTIFACT IS THE PHOTO MESSAGE ID ⟨CLAUDE.md §10⟩. `show` prints
* `message_id` for the photo Telegram accepted; a cell is `mounted` only when
* that number came back, because it is the thing that would have to exist if
* the card were on his phone. The hand's own words about having sent it are a
* status, and a status is not the artifact.
*
* THE PRESS IS HIS AND STAYS HIS. The decision card goes out with its doors
* pressable and this lane presses NOTHING — not even `Later`, which on this
* host is a real button in a real chat and pressing it would be answering for
* him. So `decisions` is READ for a press that arrived on its own, and a card
* that drew with nobody having pressed is "shown, press pending": red, by the
* same rule that makes a drawn-but-unactionable face half the product.
*
* WHY THE DATA COMES FROM THE ROAD AND NOT FROM A FIXTURE. The face is drawn
* over whatever the runner's own `execute` printed for that ask, so the cell
* measures the SAME answer every other host would have drawn. An ask the
* runner never answered has no data, and its cell is not-driven with that as
* the reason rather than a pretty picture of a fixture.
*/
/** What the road left for one ask: the face the runner named and the file its
* own answer was written to, OUTSIDE the repo. */
export interface TelegramAsk {
prompt: string;
kind: string | null;
/** A path under SHOT_ROOT holding the hand's printed answer. */
dataFile: string | null;
/** The door words this face is sent with, when it is a decision. */
doors: string[] | null;
/** Why there is nothing to draw, when kind is null. */
gap: string | null;
}
export interface TelegramShown { message_id?: number; sent?: { width?: number; height?: number }; doors?: string[]; ref?: string; unanswerable?: boolean }
/** The last JSON value the hand printed — `--json` puts the face's own object
* on stdout after the human line, so the object is taken and the prose is not
* parsed for facts. */
export function lastJson(out: string): unknown {
const lines = out.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("{") || l.startsWith("["));
for (let i = lines.length - 1; i >= 0; i -= 1) {
try { return JSON.parse(lines[i]!); } catch { /* the next one up */ }
}
// The hand pretty-prints; fall back to the first balanced object in the tail.
const brace = out.indexOf("{");
if (brace < 0) return null;
try { return JSON.parse(out.slice(brace)); } catch { return null; }
}
/** Did the owner press a door on this card? Read from the hand's own
* `decisions`, matched on the `ref` this send packed into its callback data. */
export function pressedRef(decisionsOut: string, ref: string): { word: string } | null {
const rows = lastJson(decisionsOut);
if (!Array.isArray(rows)) return null;
for (const row of rows) {
const r = row as { ref?: unknown; door?: unknown; word?: unknown; label?: unknown };
if (typeof r.ref === "string" && r.ref === ref) {
const word = [r.door, r.word, r.label].find((w): w is string => typeof w === "string");
return { word: word ?? "pressed" };
}
}
return null;
}
/**
* A CARD ALREADY ON HIS PHONE IS NOT SENT AGAIN ⟨rail: at most three messages⟩.
* The decision card went out on the 2026-09-09 06:0x run as photo message_id
* 8386 and is still in the chat with its doors unpressed. Re-sending it to
* re-measure it would put a second identical decision in front of him to score
* ourselves, which is the thing the read asks are careful not to do. So the
* prior photo id is carried as the artifact, the cell SAYS which run put it
* there, and `decisions` is read once for a press that arrived on its own.
* The cell stays red until it did — a prior send can never buy a green.
*/
/** THE ONE PRESS READER for this host, so the standing card and the freshly
* sent one cannot answer the question differently. */
export function readPress(handApiTs: string, word: string | null, ref: string): PressRecord | null {
if (word === null) return null;
const read = run("npx", ["tsx", handApiTs, "decisions", "--json"], 120_000);
const hit = ref === "" ? null : pressedRef(read.out, ref);
return {
word,
reached_runner: hit !== null,
within_ms: null,
how: `snappy-telegram decisions, matched on the ref the send packed (${ref === "" ? "no ref to match — a press could only be read if the send had named one" : "ref present"}). `
+ `Nothing here presses: the doors are on the owner's own phone and pressing one would be answering for him.`,
};
}
export function driveTelegram(handApiTs: string, asks: readonly TelegramAsk[], already: Record<string, { message_id: number; when: string }> = {}): Observation[] {
if (!existsSync(handApiTs)) {
const reason = `snappy-telegram's api.ts is not at ${handApiTs.replace(process.env.HOME ?? "~", "~")}, so this host has no hand to send through.`;
say(` telegram: NOT DRIVEN — ${reason}`);
return PROMPTS.map((prompt) => ({ host: "telegram" as HostId, prompt: prompt.id, driven: false, reason }));
}
const out: Observation[] = [];
for (const prompt of PROMPTS) {
const ask = asks.find((a) => a.prompt === prompt.id);
if (ask === undefined || ask.kind === null) {
const reason = ask?.gap ?? `the road named no face for ${prompt.id}, so there is nothing for this host to draw.`;
say(` telegram/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "telegram", prompt: prompt.id, driven: false, reason });
continue;
}
// ── A FIXTURE IS NOT A MEASUREMENT ⟨the owner, 2026-09-09 10:32⟩ ──────
// "whatever is rendered is not fake data; it exercises the same roads."
// This driver used to send the hand's shipped example when the road left
// no data file, and say so in the cell's reason — a picture of Mara Quill
// scored as a picture of his inbox, with a sentence underneath nobody
// reads. The hand now REFUSES to draw without `--from`, so the only
// honest cell is not-driven with the read that could not run, which is
// the measurement. The denominator does not move: the cell still exists,
// it is just red for the true reason.
if (ask.dataFile === null) {
const reason = `no read answered for ${prompt.id} on this Mac, so there is no real data to draw. `
+ `This host sends the answer of a real read (\`--from -\`) and nothing else; the hand refuses a card with no read behind it.`;
say(` telegram/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "telegram", prompt: prompt.id, driven: false, reason });
continue;
}
const standing = already[prompt.id];
if (standing !== undefined) {
const press = readPress(handApiTs, prompt.press, "");
say(` telegram/${prompt.id}: standing card message_id=${standing.message_id} (not resent)${press === null ? "" : press.reached_runner ? " PRESSED" : " press pending"}`);
out.push({
host: "telegram", prompt: prompt.id, cardElement: true, answered: true, kind: ask.kind, ms: null, press, standing: standing.message_id,
reason: `the card is already on the owner's own chat as photo message_id ${standing.message_id}, put there by the ${standing.when} run, and this lane did not send a second copy of a decision to measure itself`
+ (press === null ? "" : press.reached_runner ? ` — its ${press.word} door was pressed` : " — its doors are still unpressed; shown, press pending"),
});
continue;
}
const args = ["tsx", handApiTs, "show", ask.kind, "--json", "--from", ask.dataFile];
if (ask.doors !== null) args.push("--doors", JSON.stringify(ask.doors));
const started = Date.now();
const sent = run("npx", args, 300_000);
const ms = Date.now() - started;
const shown = (lastJson(sent.out) ?? {}) as TelegramShown;
// THE NUMBER, NOT THE SENTENCE. `message_id` is the photo Telegram
// accepted; without it nothing arrived, however the run exited.
if (typeof shown.message_id !== "number") {
const reason = `snappy-telegram show ${ask.kind} returned no message_id, so no card reached the chat: ${sent.out.trim().split("\n").slice(-2).join(" ").slice(0, 200)}`;
say(` telegram/${prompt.id}: NOT DRIVEN — ${reason}`);
out.push({ host: "telegram", prompt: prompt.id, driven: false, reason });
continue;
}
const press = readPress(handApiTs, prompt.press, typeof shown.ref === "string" ? shown.ref : "");
const drew = shown.sent?.width !== undefined ? ` ${shown.sent.width}x${shown.sent.height}` : "";
const doors = Array.isArray(shown.doors) && shown.doors.length ? ` doors=[${shown.doors.join(", ")}]` : "";
say(` telegram/${prompt.id}: mounted ${ask.kind}${drew}${doors} message_id=${shown.message_id} ${ms} ms${press === null ? "" : press.reached_runner ? " PRESSED" : " press pending"}`);
out.push({
host: "telegram", prompt: prompt.id, cardElement: true, answered: true, kind: ask.kind, ms, press,
reason: `the card arrived on the owner's own chat as photo message_id ${shown.message_id}${drew}${doors}, drawn over the runner's own answer to the ask`
+ (press === null ? "" : press.reached_runner ? ` and its ${press.word} door was pressed` : `, and its doors are unpressed — shown, press pending; the press is the owner's`),
});
}
return out;
}
/* ── THE PRESS ─────────────────────────────────────────────────────────────
* The axis nothing measured before: a card drew, a person pressed LATER, and
* did the RUNNER hear it? The artifact that would have to exist is a row in
* the mini's own calls.jsonl for that hand, after the press, inside the
* window. Its absence is the whole finding — a face that draws and cannot be
* acted on is half the product.
*/
export function pressReachedRunner(hand: string, sinceIso: string, windowMs: number): PressRecord | null {
const tail = run("ssh", ["-o", "ConnectTimeout=8", "mini", "tail -200 ~/.snappy-skills/calls.jsonl"], 30_000);
if (!tail.ok) return null;
const since = Date.parse(sinceIso);
const hit = tail.out.split("\n").some((line) => {
if (!line.includes(`"skill":"${hand}"`) || !line.includes('"tool":"execute"')) return false;
const at = line.match(/"at":"([^"]+)"/)?.[1];
if (at === undefined) return false;
const stamp = Date.parse(at);
return stamp >= since && stamp - since <= windowMs;
});
return { word: "Later", reached_runner: hit, within_ms: hit ? windowMs : null, how: `mini ~/.snappy-skills/calls.jsonl gained an execute for ${hand} within ${windowMs} ms of the press` };
}
export { SHOT_ROOT, existsSync };
/**
* `api.ts hosts` — run the battery, write the corpus, print the ONE NUMBER.
*
* The order is deliberate. THE ROAD FIRST: the same four asks put to the
* runner's own MCP door, so that when a host row comes back red the report can
* already say whether a face existed to draw. Then the hosts, each through its
* own driver. Then the face-family census, which is the red no host can fix.
* Then the clusters, the briefs, the ledger block, and the number.
*
* NOTHING HERE SENDS. The read asks run; the decision ask is STAGED-CLASS and
* this battery does not stage either, because staging puts a card in front of
* the owner and a measurement is not a reason to interrupt him ⟨2026-09-06:
* nothing goes to him until it works⟩. That cell is red with its reason, like
* every other honest red.
*/
import { execFileSync } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
HOSTS,
HOSTS_FILE,
HOSTS_SCHEMA,
PROMPTS,
SHOT_ROOT,
faceFamilyCensus,
familyReachCensus,
publishedKindFamilies,
hostsBrief,
hostsLedgerBlock,
attributedTo,
healthDoorFor,
platformHands,
publishedFamilies,
publishedShapes,
shapelessHands,
verbShapeFold,
recordCell,
redClusters,
scoreCells,
type HostCell,
type HostDriverStatus,
type HostsFile,
type Observation,
type RoadCell,
type RunnerProof,
} from "./hosts.ts";
import { driveCodex, driveTelegram, driveWebHost, savedStates, type TelegramAsk } from "./hosts-drive.ts";
import { BAR_RESOURCE_DIRS, barResources, driveMenubar, startBarRig, type BarRig } from "./hosts-bar.ts";
export interface HostsOptions {
skillsRoot: string;
facesSkillDir: string;
/** The runner MCP door the road pre-check drives. */
runnerUrl: string;
tokenFile: string;
outDir: string;
/** The runner's own `src/face.ts`, whose VERB_SHAPE fold is read, not copied. */
runnerFaceTs: string;
/** Which hosts to attempt; the rest are recorded not-driven with a reason. */
only?: HostId[];
codexModel?: string;
/** The loopback port the bar rig serves the shipped page on. */
barPort?: number;
/** Cards a previous run already put on a host, by prompt: not sent again. */
already?: Record<string, { message_id: number; when: string }>;
}
type HostId = (typeof HOSTS)[number]["id"];
export interface SearchMatch { name: string; verb: string | null; verb_args: string[]; why: string }
/** The runner's OWN ranker, asked the person's own sentence. */
export function readMatches(structured: any): SearchMatch[] {
const rows = structured?.result?.matches;
if (!Array.isArray(rows)) return [];
return rows
.filter((row: any) => row && typeof row.name === "string")
.map((row: any) => ({
name: String(row.name),
verb: typeof row.verb === "string" ? row.verb : null,
verb_args: Array.isArray(row.verb_args) ? row.verb_args.map(String) : [],
why: typeof row.why === "string" ? row.why : "",
}));
}
/** Only the words THIS verb's contract names are sent. An argument the
* contract never declared is the caller inventing grammar, which is the
* defect the probe exists to catch — so a wish the verb has no word for is
* simply dropped, and the call is the plain one a person would have made. */
export function argsForVerb(wish: Record<string, unknown>, verbArgs: readonly string[]): Record<string, unknown> {
const words = new Set(verbArgs.map((a) => a.replace(/[?]$/u, "").replace(/^--/u, "")));
const out: Record<string, unknown> = {};
for (const [key, value] of Object.entries(wish)) if (words.has(key)) out[key] = value;
return out;
}
/**
* THE ASK, WITHOUT THE ADDRESS ON THE ENVELOPE.
*
* "Using Snappy Skills, show me my Skool feed" is what a person TYPES in a
* chat host, and the first three words are routing — they tell ChatGPT which
* MCP to reach, and every host strips them before it composes a search intent.
* Handing them to the runner's ranker is measuring our own preamble: with them
* the top match for the Skool ask is `snappy-gateway get` (a refusal), and
* without them it is `snappy-skool posts` in one hop. MEASURED 2026-09-09.
*/
export function intentOf(typed: string): string {
return typed.replace(/^\s*(?:using|with|through|via)\s+snappy\s+skills[,:]?\s*/iu, "").trim();
}
/** What a person would like, if the verb has a word for it. Never a required
* argument: a read the battery had to invent an argument for is not the read
* a person types. */
const WISHES: Record<string, Record<string, unknown>> = { "gmail-inbox": { limit: 3 } };
/* ── THE ROAD PRE-CHECK ────────────────────────────────────────────────────
* One `execute` per ask through the real runner, reading ONLY the envelope's
* shapes: outcome, the face the runner named, the milliseconds. The rows the
* hand answered with are never read and never written down.
*
* THE FIRST CALL IS THE RUNNER'S OWN, NOT A LIST IN THIS FILE ⟨2026-09-09⟩.
* A literal `tries: ["feed", "posts"]` here measured a verb list somebody in
* this repo maintained, and it drifted: `skool-feed` scored two hops because
* "feed" was tried first, while the runner's `search` — the tool every host
* actually calls before it executes anything — had been naming `posts` for
* that sentence since the slot words landed. So the ask is put to `search`
* word for word as a person types it, and the top match's hand and verb ARE
* the first call. The hop count then means what it says: how many executes a
* host makes when it follows the runner's own ranking.
*/
export async function roadCheck(url: string, token: string, dataDir?: string): Promise<{ road: RoadCell[]; asks: TelegramAsk[] }> {
const post = async (body: unknown, sessionId: string | null): Promise<{ json: any; sessionId: string | null }> => {
const headers: Record<string, string> = { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: `Bearer ${token}` };
if (sessionId) headers["mcp-session-id"] = sessionId;
const res = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
const next = res.headers.get("mcp-session-id") ?? sessionId;
const text = await res.text();
const line = text.split("\n").find((l) => l.startsWith("data: "));
return { json: text.trim() === "" ? {} : JSON.parse(line ? line.slice(6) : text), sessionId: next };
};
let session: string | null = null;
({ sessionId: session } = await post({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "hosts-battery", version: "1" } } }, null));
await post({ jsonrpc: "2.0", method: "notifications/initialized" }, session);
const out: RoadCell[] = [];
// THE HAND'S OWN ANSWER, WRITTEN OUTSIDE THE REPO. The image hosts draw the
// SAME answer every other host would have drawn, so the data is kept — as a
// file under SHOT_ROOT, never in the corpus, because it is the owner's real
// inbox and the corpus carries shapes and never values.
const asks: TelegramAsk[] = [];
for (const spec of PROMPTS) {
if (spec.press !== null) continue; // the decision ask is not executed here; see below
const found = await post({ jsonrpc: "2.0", id: Date.now(), method: "tools/call", params: { name: "search", arguments: { intent: intentOf(spec.text), limit: 5 } } }, session);
const matches = readMatches(found.json?.result?.structuredContent).filter((m) => m.verb !== null);
if (matches.length === 0) {
out.push({ prompt: spec.id, hand: spec.hand, verb: "—", outcome: "search named no verb", kind: null, family: null, resource_uri: null, ms: null, hops: 0,
note: `the runner's own search ranked nothing with a verb for "${spec.text}", so no host could have written a first call at all` });
asks.push({ prompt: spec.id, kind: null, dataFile: null, doors: null, gap: `the runner's search named no verb for ${spec.id}, so there is nothing for an image host to draw.` });
continue;
}
let hops = 0;
let cell: RoadCell | null = null;
for (const match of matches.slice(0, 2)) {
hops += 1;
const args = argsForVerb(WISHES[spec.id] ?? {}, match.verb_args);
const started = Date.now();
const { json } = await post({ jsonrpc: "2.0", id: Date.now(), method: "tools/call", params: { name: "execute", arguments: { name: match.name, verb: match.verb, arguments: args } } }, session);
const env = json?.result?.structuredContent ?? {};
const hint = env.face_hint ?? {};
cell = {
prompt: spec.id, hand: match.name, verb: String(match.verb),
outcome: String(env?.result?.outcome ?? json?.error?.message ?? "no envelope").slice(0, 60),
kind: typeof hint.kind === "string" ? hint.kind : null,
family: typeof hint.family === "string" ? hint.family : null,
resource_uri: typeof hint.resource_uri === "string" ? hint.resource_uri : null,
ms: Date.now() - started,
hops,
note: hops === 1
? `the runner's own search ranked ${match.name} ${match.verb} first for the person's sentence; one call`
: `search's first pick did not name a face and its ${hops}${hops === 2 ? "nd" : "th"} did — a host that trusts the ranking pays ${hops} calls`,
};
if (cell.kind !== null) {
if (dataDir !== undefined) {
const file = join(dataDir, `${spec.id}.json`);
const printed = env?.result?.hand_result;
writeFileSync(file, JSON.stringify(printed ?? null, null, 1));
asks.push({ prompt: spec.id, kind: cell.kind, dataFile: file, doors: null, gap: null });
}
break;
}
}
if (cell) out.push(cell);
if (cell !== null && cell.kind === null) {
asks.push({ prompt: spec.id, kind: null, dataFile: null, doors: null,
gap: `the runner named no face for ${spec.id} (${cell.hand} ${cell.verb} → ${cell.outcome}), so there is nothing for an image host to draw.` });
}
}
// The decision ask is not driven here: `snappy-linkedin post` stages, and a
// staged post is a card in the owner's queue. A battery does not interrupt
// him to score itself.
out.push({
prompt: "linkedin-decide", hand: "snappy-linkedin", verb: "post",
outcome: "not driven", kind: null, family: null, resource_uri: null, ms: null, hops: 0,
note: "`post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it.",
});
// ── AND THE IMAGE HOST DRAWS NOTHING FOR IT EITHER ⟨2026-09-09 11:2x⟩ ────
// This ask used to hand the telegram driver `linkedin-decision` with no data
// file, which drew the faces skill's own shipped fixture — Mara Quill's
// draft — and scored it as the decision cell. The row said so in prose and
// the matrix counted it green anyway. The owner's rule that morning: "nothing
// he sees is a fixture." So the ask carries the same gap the road above
// carries: `post` stages by contract, staging is the decision, and this lane
// does not interrupt him to measure itself. The cell is not-driven with that
// reason, which is the true measurement, and the denominator is unchanged.
asks.push({
prompt: "linkedin-decide", kind: null, dataFile: null, doors: ["Post", "Later"],
gap: "`snappy-linkedin post` stages by contract (`--now` is the only bypass) and staging puts a decision in front of the owner, "
+ "so no read ran and there is no real draft to draw. A card drawn from the shipped fixture instead would be a picture of an "
+ "invented post scored as his — the cell is red for the reason that is true.",
});
return { road: out, asks };
}
/* ── PROVE THE ROAD BEFORE YOU COUNT ───────────────────────────────────────
* The first thing this battery does, before a single ask, is ask the door it
* is about to drive WHO IT IS. `/health` answers a revision; the whole run's
* number is counted against that revision and says so.
*
* It THROWS rather than degrading. A battery that shrugged at an unreachable
* health door would then drive the hosts, score them, and write a corpus
* attributed to nothing — and the next run would compare against it. The
* refusal is the product working: no corpus is written, and the reason names
* the door.
*/
export async function proveRunner(mcpUrl: string, token: string, kernelHead: string | null): Promise<RunnerProof> {
const health = healthDoorFor(mcpUrl);
let body: any;
try {
const res = await fetch(health, { headers: { authorization: `Bearer ${token}`, accept: "application/json" } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
body = await res.json();
} catch (error) {
throw new Error(
`the road was not proved: ${health} did not answer (${error instanceof Error ? error.message : String(error)}). ` +
`No corpus is written — a number counted against a door that cannot say which revision it serves is not a measurement.`,
);
}
const revision = typeof body?.revision === "string" ? body.revision : "";
if (revision === "") {
throw new Error(`the road was not proved: ${health} answered without a \`revision\`. No corpus is written.`);
}
return {
mcp_door: mcpUrl,
health_door: health,
revision,
hands: typeof body?.hands === "number" ? body.hands : null,
kernel_head: kernelHead,
attributed: attributedTo(revision, kernelHead),
proved_at: new Date().toISOString(),
};
}
/**
* The head this worktree is on, or null when git cannot say.
*
* ABBREVIATED ON PURPOSE. A full 40-character sha is indistinguishable from a
* bearer to the corpus's own guard (`^[A-Za-z0-9_-]{40,}$`, the one rule that
* stops a token reaching git), and weakening that guard with an exemption for
* "shas we know are fine" is how the next token gets through. Twelve
* characters identify a commit and the attribution only ever compares
* prefixes against the runner's ten-character stamp.
*/
export function kernelHead(cwd: string): string | null {
try {
return execFileSync("/usr/bin/git", ["rev-parse", "--short=12", "HEAD"], { cwd, encoding: "utf8" }).trim();
} catch {
return null;
}
}
export async function runHosts(options: HostsOptions): Promise<HostsFile> {
const say = (line: string) => console.log(line);
mkdirSync(SHOT_ROOT, { recursive: true });
const token = execFileSync("/bin/cat", [options.tokenFile], { encoding: "utf8" }).trim();
const proof = await proveRunner(options.runnerUrl, token, kernelHead(options.skillsRoot));
say(`road proved: ${proof.revision} · ${proof.hands ?? "?"} hands · ${proof.health_door}`);
if (!proof.attributed) say(` NOTE: the runner is not on this worktree's head (${proof.kernel_head ?? "unknown"}); the number is the runner's`);
say("road: the same asks, put to the runner's own door");
const dataDir = join(SHOT_ROOT, "data");
mkdirSync(dataDir, { recursive: true });
const { road, asks } = await roadCheck(options.runnerUrl, token, dataDir);
for (const r of road) say(` ${r.prompt}: ${r.hand} ${r.verb} → ${r.outcome} face=${r.kind ?? "none"} ${r.ms ?? "-"}ms hops=${r.hops}`);
say("hosts: driving");
const states = savedStates();
const wanted = new Set<HostId>(options.only ?? HOSTS.map((h) => h.id));
// THE BAR'S RIG IS BUILT ONCE AND TORN DOWN, whatever any driver does. It
// holds the bearer in its own process so the page never sees one.
const resources = wanted.has("menubar") ? barResources() : null;
let rig: BarRig | null = null;
if (wanted.has("menubar") && resources !== null) {
rig = startBarRig(resources, options.runnerUrl, options.tokenFile, options.barPort ?? 3196);
await new Promise((resolve) => setTimeout(resolve, 1500));
}
const kinds: Record<string, string | null> = {};
for (const ask of asks) kinds[ask.prompt] = ask.kind;
try {
const observations = [
...(wanted.has("chatgpt") ? driveWebHost("chatgpt", states) : []),
...(wanted.has("claude") ? driveWebHost("claude", states) : []),
...(wanted.has("codex") ? driveCodex(options.codexModel ?? "gpt-5.6-luna") : []),
...(wanted.has("menubar")
? driveMenubar(rig, `no built Snappy.app bundle was found to serve — the bar's own \`index.html\`/\`bar-host.js\` are what this rig runs, and none of ${JSON.stringify(BAR_RESOURCE_DIRS)} holds them. Build it once: cd ~/Projects/snappy-runner-menu-bar-2/apps/snappy-bar && ./scripts/make-app.sh`, kinds)
: []),
...(wanted.has("telegram") ? driveTelegram(join(options.skillsRoot, "snappy-telegram", "api.ts"), asks, options.already ?? {}) : []),
];
return await finishHosts(options, proof, road, observations, say);
} finally {
if (rig !== null) { rig.child.kill("SIGTERM"); }
}
}
async function finishHosts(options: HostsOptions, proof: RunnerProof, road: RoadCell[], observations: readonly Observation[], say: (line: string) => void): Promise<HostsFile> {
// The proof travels all the way to the write. A corpus without it is the
// artifact the next run would compare against, so there is no path to one.
if (proof.revision === "" || proof.mcp_door !== options.runnerUrl) {
throw new Error(`refusing to write the corpus: the number was counted through ${options.runnerUrl} but the proof names ${proof.mcp_door || "no door"}`);
}
const cells: HostCell[] = observations.map(recordCell);
const families = publishedFamilies(options.facesSkillDir);
const fold = verbShapeFold(options.runnerFaceTs);
const shapes = publishedShapes(options.facesSkillDir);
const hands = platformHands(options.skillsRoot, fold, shapes);
say(` shapes ${shapes.join(", ") || "none"} · verb fold ${Object.keys(fold).length} word(s) from the runner`);
const census = faceFamilyCensus(hands, families);
say(`face families: ${families.length} published; ${census.filter((c) => !c.has_family).length} of ${census.length} platform hand(s) name none`);
// THE SAME QUESTION FROM THE FAMILY'S END. The hand axis above cannot see a
// family no hand lands on, so a library that grew by six on 2026-09-09 moved
// the number by two. Every published family gets its own cell here.
const reach = familyReachCensus(options.skillsRoot, families, fold, shapes, publishedKindFamilies(options.facesSkillDir));
say(` ${reach.filter((r) => !r.reachable).length} of ${reach.length} published family(ies) have no read that names them`);
const drivers: HostDriverStatus[] = HOSTS.map((spec) => {
const mine = cells.filter((c) => c.host === spec.id);
const driven = mine.some((c) => c.mount !== "not-driven");
return { host: spec.id, label: spec.label, road: spec.road, surface: spec.surface, driven, reason: mine[0]?.reason ?? "no cell recorded", ownerCommand: spec.ownerCommand };
});
const face_families = { published: families, missing: census, unreachable: reach };
const no_shape = shapelessHands(options.skillsRoot, fold, shapes);
say(` ${no_shape.length} hand(s) have no verb the face road can shape at all (krisp is the worked example)`);
const clusters = redClusters({ cells, face_families, road, no_shape });
const number = scoreCells(cells, census, reach);
const file: HostsFile = { schema: HOSTS_SCHEMA, generated_at: new Date().toISOString(), runner: proof, prompts: PROMPTS, drivers, cells, road, face_families, no_shape, clusters, number };
writeFileSync(join(options.outDir, HOSTS_FILE), JSON.stringify(file, null, 1));
mkdirSync(join(options.outDir, "briefs"), { recursive: true });
for (const cluster of clusters) writeFileSync(join(options.outDir, "briefs", `${cluster.key}.md`), hostsBrief(cluster));
writeFileSync(join(options.outDir, "HOSTS-LEDGER.md"), hostsLedgerBlock(file));
for (const cluster of clusters) say(` ${String(cluster.cells.length).padStart(3)} × ${cluster.key}`);
const matrix = cells.filter((c) => c.mount === "mounted").length;
say(` matrix ${matrix}/${cells.length} · hands with a family ${census.filter((c) => c.has_family).length}/${census.length} · families with a read ${reach.filter((r) => r.reachable).length}/${reach.length}`);
say(number.line);
return file;
}
/**
* `api.ts hosts` — run the battery, write the corpus, print the ONE NUMBER.
*
* The order is deliberate. THE ROAD FIRST: the same four asks put to the
* runner's own MCP door, so that when a host row comes back red the report can
* already say whether a face existed to draw. Then the hosts, each through its
* own driver. Then the face-family census, which is the red no host can fix.
* Then the clusters, the briefs, the ledger block, and the number.
*
* NOTHING HERE SENDS. The read asks run; the decision ask is STAGED-CLASS and
* this battery does not stage either, because staging puts a card in front of
* the owner and a measurement is not a reason to interrupt him ⟨2026-09-06:
* nothing goes to him until it works⟩. That cell is red with its reason, like
* every other honest red.
*/
import { execFileSync } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
HOSTS,
HOSTS_FILE,
HOSTS_SCHEMA,
PROMPTS,
SHOT_ROOT,
faceFamilyCensus,
familyReachCensus,
publishedKindFamilies,
hostsBrief,
hostsLedgerBlock,
attributedTo,
healthDoorFor,
platformHands,
publishedFamilies,
publishedShapes,
shapelessHands,
verbShapeFold,
recordCell,
redClusters,
scoreCells,
type HostCell,
type HostDriverStatus,
type HostsFile,
type Observation,
type RoadCell,
type RunnerProof,
} from "./hosts.ts";
import { driveCodex, driveTelegram, driveWebHost, savedStates, type TelegramAsk } from "./hosts-drive.ts";
import { BAR_RESOURCE_DIRS, barResources, driveMenubar, startBarRig, type BarRig } from "./hosts-bar.ts";
export interface HostsOptions {
skillsRoot: string;
facesSkillDir: string;
/** The runner MCP door the road pre-check drives. */
runnerUrl: string;
tokenFile: string;
outDir: string;
/** The runner's own `src/face.ts`, whose VERB_SHAPE fold is read, not copied. */
runnerFaceTs: string;
/** Which hosts to attempt; the rest are recorded not-driven with a reason. */
only?: HostId[];
codexModel?: string;
/** The loopback port the bar rig serves the shipped page on. */
barPort?: number;
/** Cards a previous run already put on a host, by prompt: not sent again. */
already?: Record<string, { message_id: number; when: string }>;
}
type HostId = (typeof HOSTS)[number]["id"];
export interface SearchMatch { name: string; verb: string | null; verb_args: string[]; why: string }
/** The runner's OWN ranker, asked the person's own sentence. */
export function readMatches(structured: any): SearchMatch[] {
const rows = structured?.result?.matches;
if (!Array.isArray(rows)) return [];
return rows
.filter((row: any) => row && typeof row.name === "string")
.map((row: any) => ({
name: String(row.name),
verb: typeof row.verb === "string" ? row.verb : null,
verb_args: Array.isArray(row.verb_args) ? row.verb_args.map(String) : [],
why: typeof row.why === "string" ? row.why : "",
}));
}
/** Only the words THIS verb's contract names are sent. An argument the
* contract never declared is the caller inventing grammar, which is the
* defect the probe exists to catch — so a wish the verb has no word for is
* simply dropped, and the call is the plain one a person would have made. */
export function argsForVerb(wish: Record<string, unknown>, verbArgs: readonly string[]): Record<string, unknown> {
const words = new Set(verbArgs.map((a) => a.replace(/[?]$/u, "").replace(/^--/u, "")));
const out: Record<string, unknown> = {};
for (const [key, value] of Object.entries(wish)) if (words.has(key)) out[key] = value;
return out;
}
/**
* THE ASK, WITHOUT THE ADDRESS ON THE ENVELOPE.
*
* "Using Snappy Skills, show me my Skool feed" is what a person TYPES in a
* chat host, and the first three words are routing — they tell ChatGPT which
* MCP to reach, and every host strips them before it composes a search intent.
* Handing them to the runner's ranker is measuring our own preamble: with them
* the top match for the Skool ask is `snappy-gateway get` (a refusal), and
* without them it is `snappy-skool posts` in one hop. MEASURED 2026-09-09.
*/
export function intentOf(typed: string): string {
return typed.replace(/^\s*(?:using|with|through|via)\s+snappy\s+skills[,:]?\s*/iu, "").trim();
}
/** What a person would like, if the verb has a word for it. Never a required
* argument: a read the battery had to invent an argument for is not the read
* a person types. */
const WISHES: Record<string, Record<string, unknown>> = { "gmail-inbox": { limit: 3 } };
/* ── THE ROAD PRE-CHECK ────────────────────────────────────────────────────
* One `execute` per ask through the real runner, reading ONLY the envelope's
* shapes: outcome, the face the runner named, the milliseconds. The rows the
* hand answered with are never read and never written down.
*
* THE FIRST CALL IS THE RUNNER'S OWN, NOT A LIST IN THIS FILE ⟨2026-09-09⟩.
* A literal `tries: ["feed", "posts"]` here measured a verb list somebody in
* this repo maintained, and it drifted: `skool-feed` scored two hops because
* "feed" was tried first, while the runner's `search` — the tool every host
* actually calls before it executes anything — had been naming `posts` for
* that sentence since the slot words landed. So the ask is put to `search`
* word for word as a person types it, and the top match's hand and verb ARE
* the first call. The hop count then means what it says: how many executes a
* host makes when it follows the runner's own ranking.
*/
export async function roadCheck(url: string, token: string, dataDir?: string): Promise<{ road: RoadCell[]; asks: TelegramAsk[] }> {
const post = async (body: unknown, sessionId: string | null): Promise<{ json: any; sessionId: string | null }> => {
const headers: Record<string, string> = { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: `Bearer ${token}` };
if (sessionId) headers["mcp-session-id"] = sessionId;
const res = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
const next = res.headers.get("mcp-session-id") ?? sessionId;
const text = await res.text();
const line = text.split("\n").find((l) => l.startsWith("data: "));
return { json: text.trim() === "" ? {} : JSON.parse(line ? line.slice(6) : text), sessionId: next };
};
let session: string | null = null;
({ sessionId: session } = await post({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "hosts-battery", version: "1" } } }, null));
await post({ jsonrpc: "2.0", method: "notifications/initialized" }, session);
const out: RoadCell[] = [];
// THE HAND'S OWN ANSWER, WRITTEN OUTSIDE THE REPO. The image hosts draw the
// SAME answer every other host would have drawn, so the data is kept — as a
// file under SHOT_ROOT, never in the corpus, because it is the owner's real
// inbox and the corpus carries shapes and never values.
const asks: TelegramAsk[] = [];
for (const spec of PROMPTS) {
if (spec.press !== null) continue; // the decision ask is not executed here; see below
const found = await post({ jsonrpc: "2.0", id: Date.now(), method: "tools/call", params: { name: "search", arguments: { intent: intentOf(spec.text), limit: 5 } } }, session);
const matches = readMatches(found.json?.result?.structuredContent).filter((m) => m.verb !== null);
if (matches.length === 0) {
out.push({ prompt: spec.id, hand: spec.hand, verb: "—", outcome: "search named no verb", kind: null, family: null, resource_uri: null, ms: null, hops: 0,
note: `the runner's own search ranked nothing with a verb for "${spec.text}", so no host could have written a first call at all` });
asks.push({ prompt: spec.id, kind: null, dataFile: null, doors: null, gap: `the runner's search named no verb for ${spec.id}, so there is nothing for an image host to draw.` });
continue;
}
let hops = 0;
let cell: RoadCell | null = null;
for (const match of matches.slice(0, 2)) {
hops += 1;
const args = argsForVerb(WISHES[spec.id] ?? {}, match.verb_args);
const started = Date.now();
const { json } = await post({ jsonrpc: "2.0", id: Date.now(), method: "tools/call", params: { name: "execute", arguments: { name: match.name, verb: match.verb, arguments: args } } }, session);
const env = json?.result?.structuredContent ?? {};
const hint = env.face_hint ?? {};
cell = {
prompt: spec.id, hand: match.name, verb: String(match.verb),
outcome: String(env?.result?.outcome ?? json?.error?.message ?? "no envelope").slice(0, 60),
kind: typeof hint.kind === "string" ? hint.kind : null,
family: typeof hint.family === "string" ? hint.family : null,
resource_uri: typeof hint.resource_uri === "string" ? hint.resource_uri : null,
ms: Date.now() - started,
hops,
note: hops === 1
? `the runner's own search ranked ${match.name} ${match.verb} first for the person's sentence; one call`
: `search's first pick did not name a face and its ${hops}${hops === 2 ? "nd" : "th"} did — a host that trusts the ranking pays ${hops} calls`,
};
if (cell.kind !== null) {
if (dataDir !== undefined) {
const file = join(dataDir, `${spec.id}.json`);
const printed = env?.result?.hand_result;
writeFileSync(file, JSON.stringify(printed ?? null, null, 1));
asks.push({ prompt: spec.id, kind: cell.kind, dataFile: file, doors: null, gap: null });
}
break;
}
}
if (cell) out.push(cell);
if (cell !== null && cell.kind === null) {
asks.push({ prompt: spec.id, kind: null, dataFile: null, doors: null,
gap: `the runner named no face for ${spec.id} (${cell.hand} ${cell.verb} → ${cell.outcome}), so there is nothing for an image host to draw.` });
}
}
// The decision ask is not driven here: `snappy-linkedin post` stages, and a
// staged post is a card in the owner's queue. A battery does not interrupt
// him to score itself.
out.push({
prompt: "linkedin-decide", hand: "snappy-linkedin", verb: "post",
outcome: "not driven", kind: null, family: null, resource_uri: null, ms: null, hops: 0,
note: "`post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it.",
});
// ── AND THE IMAGE HOST DRAWS NOTHING FOR IT EITHER ⟨2026-09-09 11:2x⟩ ────
// This ask used to hand the telegram driver `linkedin-decision` with no data
// file, which drew the faces skill's own shipped fixture — Mara Quill's
// draft — and scored it as the decision cell. The row said so in prose and
// the matrix counted it green anyway. The owner's rule that morning: "nothing
// he sees is a fixture." So the ask carries the same gap the road above
// carries: `post` stages by contract, staging is the decision, and this lane
// does not interrupt him to measure itself. The cell is not-driven with that
// reason, which is the true measurement, and the denominator is unchanged.
asks.push({
prompt: "linkedin-decide", kind: null, dataFile: null, doors: ["Post", "Later"],
gap: "`snappy-linkedin post` stages by contract (`--now` is the only bypass) and staging puts a decision in front of the owner, "
+ "so no read ran and there is no real draft to draw. A card drawn from the shipped fixture instead would be a picture of an "
+ "invented post scored as his — the cell is red for the reason that is true.",
});
return { road: out, asks };
}
/* ── PROVE THE ROAD BEFORE YOU COUNT ───────────────────────────────────────
* The first thing this battery does, before a single ask, is ask the door it
* is about to drive WHO IT IS. `/health` answers a revision; the whole run's
* number is counted against that revision and says so.
*
* It THROWS rather than degrading. A battery that shrugged at an unreachable
* health door would then drive the hosts, score them, and write a corpus
* attributed to nothing — and the next run would compare against it. The
* refusal is the product working: no corpus is written, and the reason names
* the door.
*/
export async function proveRunner(mcpUrl: string, token: string, kernelHead: string | null): Promise<RunnerProof> {
const health = healthDoorFor(mcpUrl);
let body: any;
try {
const res = await fetch(health, { headers: { authorization: `Bearer ${token}`, accept: "application/json" } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
body = await res.json();
} catch (error) {
throw new Error(
`the road was not proved: ${health} did not answer (${error instanceof Error ? error.message : String(error)}). ` +
`No corpus is written — a number counted against a door that cannot say which revision it serves is not a measurement.`,
);
}
const revision = typeof body?.revision === "string" ? body.revision : "";
if (revision === "") {
throw new Error(`the road was not proved: ${health} answered without a \`revision\`. No corpus is written.`);
}
return {
mcp_door: mcpUrl,
health_door: health,
revision,
hands: typeof body?.hands === "number" ? body.hands : null,
kernel_head: kernelHead,
attributed: attributedTo(revision, kernelHead),
proved_at: new Date().toISOString(),
};
}
/**
* The head this worktree is on, or null when git cannot say.
*
* ABBREVIATED ON PURPOSE. A full 40-character sha is indistinguishable from a
* bearer to the corpus's own guard (`^[A-Za-z0-9_-]{40,}$`, the one rule that
* stops a token reaching git), and weakening that guard with an exemption for
* "shas we know are fine" is how the next token gets through. Twelve
* characters identify a commit and the attribution only ever compares
* prefixes against the runner's ten-character stamp.
*/
export function kernelHead(cwd: string): string | null {
try {
return execFileSync("/usr/bin/git", ["rev-parse", "--short=12", "HEAD"], { cwd, encoding: "utf8" }).trim();
} catch {
return null;
}
}
export async function runHosts(options: HostsOptions): Promise<HostsFile> {
const say = (line: string) => console.log(line);
mkdirSync(SHOT_ROOT, { recursive: true });
const token = execFileSync("/bin/cat", [options.tokenFile], { encoding: "utf8" }).trim();
const proof = await proveRunner(options.runnerUrl, token, kernelHead(options.skillsRoot));
say(`road proved: ${proof.revision} · ${proof.hands ?? "?"} hands · ${proof.health_door}`);
if (!proof.attributed) say(` NOTE: the runner is not on this worktree's head (${proof.kernel_head ?? "unknown"}); the number is the runner's`);
say("road: the same asks, put to the runner's own door");
const dataDir = join(SHOT_ROOT, "data");
mkdirSync(dataDir, { recursive: true });
const { road, asks } = await roadCheck(options.runnerUrl, token, dataDir);
for (const r of road) say(` ${r.prompt}: ${r.hand} ${r.verb} → ${r.outcome} face=${r.kind ?? "none"} ${r.ms ?? "-"}ms hops=${r.hops}`);
say("hosts: driving");
const states = savedStates();
const wanted = new Set<HostId>(options.only ?? HOSTS.map((h) => h.id));
// THE BAR'S RIG IS BUILT ONCE AND TORN DOWN, whatever any driver does. It
// holds the bearer in its own process so the page never sees one.
const resources = wanted.has("menubar") ? barResources() : null;
let rig: BarRig | null = null;
if (wanted.has("menubar") && resources !== null) {
rig = startBarRig(resources, options.runnerUrl, options.tokenFile, options.barPort ?? 3196);
await new Promise((resolve) => setTimeout(resolve, 1500));
}
const kinds: Record<string, string | null> = {};
for (const ask of asks) kinds[ask.prompt] = ask.kind;
try {
const observations = [
...(wanted.has("chatgpt") ? driveWebHost("chatgpt", states) : []),
...(wanted.has("claude") ? driveWebHost("claude", states) : []),
...(wanted.has("codex") ? driveCodex(options.codexModel ?? "gpt-5.6-luna") : []),
...(wanted.has("menubar")
? driveMenubar(rig, `no built Snappy.app bundle was found to serve — the bar's own \`index.html\`/\`bar-host.js\` are what this rig runs, and none of ${JSON.stringify(BAR_RESOURCE_DIRS)} holds them. Build it once: cd ~/Projects/snappy-runner-menu-bar-2/apps/snappy-bar && ./scripts/make-app.sh`, kinds)
: []),
...(wanted.has("telegram") ? driveTelegram(join(options.skillsRoot, "snappy-telegram", "api.ts"), asks, options.already ?? {}) : []),
];
return await finishHosts(options, proof, road, observations, say);
} finally {
if (rig !== null) { rig.child.kill("SIGTERM"); }
}
}
async function finishHosts(options: HostsOptions, proof: RunnerProof, road: RoadCell[], observations: readonly Observation[], say: (line: string) => void): Promise<HostsFile> {
// The proof travels all the way to the write. A corpus without it is the
// artifact the next run would compare against, so there is no path to one.
if (proof.revision === "" || proof.mcp_door !== options.runnerUrl) {
throw new Error(`refusing to write the corpus: the number was counted through ${options.runnerUrl} but the proof names ${proof.mcp_door || "no door"}`);
}
const cells: HostCell[] = observations.map(recordCell);
const families = publishedFamilies(options.facesSkillDir);
const fold = verbShapeFold(options.runnerFaceTs);
const shapes = publishedShapes(options.facesSkillDir);
const hands = platformHands(options.skillsRoot, fold, shapes);
say(` shapes ${shapes.join(", ") || "none"} · verb fold ${Object.keys(fold).length} word(s) from the runner`);
const census = faceFamilyCensus(hands, families);
say(`face families: ${families.length} published; ${census.filter((c) => !c.has_family).length} of ${census.length} platform hand(s) name none`);
// THE SAME QUESTION FROM THE FAMILY'S END. The hand axis above cannot see a
// family no hand lands on, so a library that grew by six on 2026-09-09 moved
// the number by two. Every published family gets its own cell here.
const reach = familyReachCensus(options.skillsRoot, families, fold, shapes, publishedKindFamilies(options.facesSkillDir));
say(` ${reach.filter((r) => !r.reachable).length} of ${reach.length} published family(ies) have no read that names them`);
const drivers: HostDriverStatus[] = HOSTS.map((spec) => {
const mine = cells.filter((c) => c.host === spec.id);
const driven = mine.some((c) => c.mount !== "not-driven");
return { host: spec.id, label: spec.label, road: spec.road, surface: spec.surface, driven, reason: mine[0]?.reason ?? "no cell recorded", ownerCommand: spec.ownerCommand };
});
const face_families = { published: families, missing: census, unreachable: reach };
const no_shape = shapelessHands(options.skillsRoot, fold, shapes);
say(` ${no_shape.length} hand(s) have no verb the face road can shape at all (krisp is the worked example)`);
const clusters = redClusters({ cells, face_families, road, no_shape });
const number = scoreCells(cells, census, reach);
const file: HostsFile = { schema: HOSTS_SCHEMA, generated_at: new Date().toISOString(), runner: proof, prompts: PROMPTS, drivers, cells, road, face_families, no_shape, clusters, number };
writeFileSync(join(options.outDir, HOSTS_FILE), JSON.stringify(file, null, 1));
mkdirSync(join(options.outDir, "briefs"), { recursive: true });
for (const cluster of clusters) writeFileSync(join(options.outDir, "briefs", `${cluster.key}.md`), hostsBrief(cluster));
writeFileSync(join(options.outDir, "HOSTS-LEDGER.md"), hostsLedgerBlock(file));
for (const cluster of clusters) say(` ${String(cluster.cells.length).padStart(3)} × ${cluster.key}`);
const matrix = cells.filter((c) => c.mount === "mounted").length;
say(` matrix ${matrix}/${cells.length} · hands with a family ${census.filter((c) => c.has_family).length}/${census.length} · families with a read ${reach.filter((r) => r.reachable).length}/${reach.length}`);
say(number.line);
return file;
}
/**
* THE CELL RECORDER AND THE NUMBER — the two things a battery can lie with.
*
* A driver that returns nothing must not produce a green cell; a host that
* answered in prose must not be counted as having drawn; a decision cell whose
* press never reached the runner must not count as whole; and the number must
* never rise by dropping a cell it could not measure. Every test here was
* written against the failure it prevents, and each fails if the guard is
* removed.
*/
import assert from "node:assert/strict";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import {
HOSTS,
HOSTS_FILE,
HOSTS_SCHEMA,
PROMPTS,
SHOT_ROOT,
faceFamilyCensus,
familyForHand,
hostsBrief,
hostsLedgerBlock,
recordCell,
redClusters,
familyReachCensus,
contractFaces,
declaredFaceReads,
publishedKindFamilies,
scoreCells,
attributedTo,
healthDoorFor,
type HostCell,
type HostsFile,
} from "./hosts.ts";
import { driveTelegram, hasStateFor, readCodexTurn } from "./hosts-drive.ts";
import { proveRunner } from "./hosts-run.ts";
const HERE = dirname(fileURLToPath(import.meta.url));
const cell = (over: Partial<HostCell>): HostCell => ({
host: "codex", prompt: "gmail-inbox", mount: "text", reason: "r", ms: null, kind: null, shot: null, press: null, standing: null, ...over,
});
test("a card element in the host's own DOM is the only green", () => {
assert.equal(recordCell({ host: "chatgpt", prompt: "gmail-inbox", cardElement: true, ms: 4100 }).mount, "mounted");
assert.equal(recordCell({ host: "chatgpt", prompt: "gmail-inbox", cardElement: false, answered: true }).mount, "text");
});
test("a host that named a face and drew nothing is TEXT, never mounted", () => {
// The status/artifact defect, in its host-shaped form: `kind` is a claim,
// the card element is the artifact.
const seen = recordCell({ host: "codex", prompt: "gmail-inbox", cardElement: false, answered: true, kind: "gmail-list" });
assert.equal(seen.mount, "text");
assert.equal(seen.kind, "gmail-list");
});
test("a not-driven observation without a reason throws instead of writing a blank red", () => {
assert.throws(() => recordCell({ host: "telegram", prompt: "skool-feed", driven: false }), /carries no reason/);
});
test("a not-driven cell keeps no milliseconds — there was nothing to time", () => {
const seen = recordCell({ host: "claude", prompt: "skool-feed", driven: false, reason: "no saved state", ms: 999 });
assert.equal(seen.ms, null);
});
test("green needs the press on the decision prompt — a card that cannot be acted on is half", () => {
const drew = cell({ host: "chatgpt", prompt: "linkedin-decide", mount: "mounted", ms: 3000 });
assert.equal(scoreCells([drew], []).green, 0, "mounted with no press must not count");
const pressed = { ...drew, press: { word: "Later", reached_runner: true, within_ms: 30_000, how: "calls.jsonl" } };
assert.equal(scoreCells([pressed], []).green, 1);
const missed = { ...drew, press: { word: "Later", reached_runner: false, within_ms: null, how: "calls.jsonl" } };
assert.equal(scoreCells([missed], []).green, 0);
});
test("the number never rises by dropping a cell nobody could measure", () => {
const measured = [cell({ mount: "mounted", prompt: "gmail-inbox" }), cell({ mount: "not-driven", prompt: "skool-feed", host: "telegram" })];
const dropped = measured.filter((c) => c.mount !== "not-driven");
assert.equal(scoreCells(measured, []).total, 2);
assert.ok(scoreCells(measured, []).green / scoreCells(measured, []).total < scoreCells(dropped, []).green / scoreCells(dropped, []).total,
"a shrinking denominator would flatter the score, which is why not-driven stays in it");
});
test("the number's line is the exact contract the morning report reads", () => {
assert.equal(scoreCells([cell({ mount: "mounted" }), cell({ prompt: "skool-feed" })], []).line, "hosts: 1/2 cells");
});
test("the face-family census is the runner's own derivation, not a second one", () => {
assert.equal(familyForHand("snappy-gmail"), "gmail");
assert.equal(familyForHand("statechange"), "statechange");
const census = faceFamilyCensus(["snappy-gmail", "snappy-statechange"], ["gmail", "linkedin"]);
assert.deepEqual(census.map((c) => c.has_family), [true, false]);
assert.match(census[1].reason, /declares no "statechange" family/);
assert.match(census[1].reason, /a face to build, not a host to fix/);
});
test("a platform hand with no face family becomes a named red cluster", () => {
const clusters = redClusters({
cells: [cell({ mount: "not-driven", reason: "no saved state" })],
face_families: { published: ["gmail"], missing: faceFamilyCensus(["snappy-krisp"], ["gmail"]) },
});
const family = clusters.find((c) => c.key === "hosts-no-face-family");
assert.ok(family, "the census produces its own cluster");
assert.match(family!.mechanism, /no host driver can fix them/);
});
test("a road that names no face is its own cluster, ranked before the hosts are blamed", () => {
const clusters = redClusters({
cells: [],
face_families: { published: [], missing: [] },
road: [{ prompt: "skool-feed", hand: "snappy-skool", verb: "feed", outcome: "refused", kind: null, family: null, resource_uri: null, ms: 21, hops: 1, note: "verb_not_in_contract" }],
});
assert.equal(clusters[0].key, "hosts-road-names-no-face");
assert.match(clusters[0].mechanism, /the defect is under the hosts, not in them/);
});
test("an ask that needs a second call is a first-call defect, not a host defect", () => {
const clusters = redClusters({
cells: [], face_families: { published: [], missing: [] },
road: [{ prompt: "skool-feed", hand: "snappy-skool", verb: "posts", outcome: "answered", kind: "skool-feed", family: "skool", resource_uri: "ui://snappy/faces", ms: 1012, hops: 2, note: "the first call refused" }],
});
assert.ok(clusters.some((c) => c.key === "hosts-first-call-refuses"));
});
test("every host that cannot be driven carries the one line the owner runs, or names why none exists", () => {
for (const host of HOSTS) {
assert.ok(host.ownerCommand === null || host.ownerCommand.length > 10, `${host.id} owner command is a real line or explicitly none`);
}
assert.match(HOSTS.find((h) => h.id === "chatgpt")!.ownerCommand!, /agent-browser .*state save chatgpt/);
});
test("the four prompts are the same four for every host, and only one presses", () => {
assert.equal(PROMPTS.length, 4);
assert.deepEqual(PROMPTS.filter((p) => p.press !== null).map((p) => p.press), ["Later"]);
assert.ok(PROMPTS.every((p) => p.press !== "Publish" && p.press !== "Send"), "this battery never presses a send");
});
test("the saved-state gate matches by host word and never by accident", () => {
assert.equal(hasStateFor("chatgpt", ["snappy-site-hackernews.json", "chatgpt-main.json"]), true);
assert.equal(hasStateFor("chatgpt", ["snappy-taught-cap-mt40qwzo.json"]), false);
assert.equal(hasStateFor("claude", ["claude-web.json"]), true);
});
test("a codex turn is read for the MCP hop and the named kind, and a usage limit is not an answer", () => {
const limit = readCodexTurn([
JSON.stringify({ type: "turn.started" }),
JSON.stringify({ type: "turn.failed", error: { message: "You've hit your usage limit." } }),
]);
assert.equal(limit.answered, false);
assert.match(limit.failure!, /usage limit/, "the turn's own failure, not the first warning above it");
const warned = readCodexTurn([
JSON.stringify({ type: "item.completed", item: { type: "error", message: "Exceeded skills context budget of 2%." } }),
JSON.stringify({ type: "turn.failed", error: { message: JSON.stringify({ error: { message: "You've hit your usage limit." } }) } }),
]);
assert.match(warned.failure!, /usage limit/, "a warning above the failure must not be reported as the reason");
assert.equal(warned.warnings.length, 1, "the warning is kept, beside the failure — it is its own finding");
const drew = readCodexTurn([
JSON.stringify({ type: "item.completed", item: { type: "mcp_tool_call", name: "snappy-skills__execute", result: { kind: "gmail-list" } } }),
JSON.stringify({ type: "turn.completed" }),
]);
assert.equal(drew.reachedMcp, true);
assert.equal(drew.kind, "gmail-list");
});
test("the ledger block draws every host row and never a blank cell", () => {
const file = {
generated_at: "2026-09-09T06:00:00.000Z",
runner: { mcp_door: "https://runner.example/runner/mcp", health_door: "https://runner.example/runner/health", revision: "1da5a73711-41156a7220a9-1879d420a2", hands: 98, kernel_head: "1da5a73711be", attributed: true, proved_at: "2026-09-09T10:57:31.000Z" },
cells: HOSTS.flatMap((h) => PROMPTS.map((p) => cell({ host: h.id, prompt: p.id, mount: "not-driven", reason: "x" }))),
road: [{ prompt: "gmail-inbox", hand: "snappy-gmail", verb: "list", outcome: "answered", kind: "gmail-list", family: "gmail", resource_uri: "ui://snappy/faces", ms: 1538, hops: 1, note: "one call" }],
face_families: { published: ["gmail"], missing: [] },
clusters: [{ key: "hosts-not-driven-codex", headline: "codex could not be driven", mechanism: "m", cells: ["c"], ownerCommand: null }],
drivers: HOSTS.map((h) => ({ host: h.id, label: h.label, road: h.road, surface: h.surface, driven: false, reason: "x", ownerCommand: h.ownerCommand })),
number: { green: 0, total: 20, line: "hosts: 0/20 cells" },
} as unknown as HostsFile;
const block = hostsLedgerBlock(file);
for (const host of HOSTS) assert.ok(block.includes(host.label), `${host.label} has a row`);
assert.ok(!block.includes("| — |"), "no cell is blank; a cell nobody measured says not driven");
assert.ok(block.includes("hosts: 0/20 cells"));
});
/**
* MEASURED 2026-09-09 (hosts-5): the four Telegram cells were green on photos
* a PREVIOUS run had put in the chat, carried by `--already` so this lane sent
* nothing. Nothing was timed, so the ledger drew `**mounted** ? ms` — which on
* the owner's glass is indistinguishable from a card this run drew. A cell is
* only as true as the artifact it implies, and the artifact here is a photo id.
*/
test("a card standing on a previous run's photo says which photo, never `? ms`", () => {
const standing = recordCell({ host: "telegram", prompt: "gmail-inbox", cardElement: true, ms: null, standing: 8387, reason: "carried" });
assert.equal(standing.mount, "mounted");
assert.equal(standing.standing, 8387);
const file = {
generated_at: "2026-09-09T09:55:59.249Z",
runner: { mcp_door: "https://runner.example/runner/mcp", health_door: "https://runner.example/runner/health", revision: "1da5a73711-41156a7220a9-1879d420a2", hands: 98, kernel_head: "1da5a73711be", attributed: true, proved_at: "2026-09-09T10:57:31.000Z" },
cells: [standing],
road: [],
face_families: { published: [], missing: [] },
clusters: [],
drivers: HOSTS.map((h) => ({ host: h.id, label: h.label, road: h.road, surface: h.surface, driven: false, reason: "x", ownerCommand: h.ownerCommand })),
number: { green: 0, total: 20, line: "hosts: 0/20 cells" },
} as unknown as HostsFile;
const block = hostsLedgerBlock(file);
assert.match(block, /\*\*mounted\*\* · standing #8387/);
assert.ok(!block.includes("**mounted** ? ms"), "a cell nothing timed never claims a measurement it does not have");
// A cell this run DID draw still reads in milliseconds.
const fresh = { ...file, cells: [recordCell({ host: "telegram", prompt: "gmail-inbox", cardElement: true, ms: 4314 })] } as unknown as HostsFile;
assert.match(hostsLedgerBlock(fresh), /\*\*mounted\*\* 4314 ms/);
});
/* ── THE DOOR THE NUMBER WAS COUNTED AGAINST ───────────────────────────────
* hosts-5's named gap. The same battery read 1/20 against a local runner and
* 8/20 against the mini's within the same hour, and the corpus said nothing
* about which door either number came from — so the two were comparable to
* the eye and not comparable in fact.
*/
test("the health door is derived from the MCP door, never configured beside it", () => {
assert.equal(healthDoorFor("https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp"), "https://roberts-mac-mini.tailca7f42.ts.net/runner/health");
assert.equal(healthDoorFor("http://127.0.0.1:8899/runner/mcp/"), "http://127.0.0.1:8899/runner/health");
// A door that is not an /mcp door still gets its health beside it, rather
// than a second URL somebody has to keep in step ⟨duplicate roads⟩.
assert.equal(healthDoorFor("http://127.0.0.1:8899/runner"), "http://127.0.0.1:8899/runner/health");
});
test("a runner revision is attributed to a head only when it names it", () => {
const head = "1da5a73711bea7d5bc2ba482ef695000b0d8414f";
assert.equal(attributedTo("1da5a73711-41156a7220a9-1879d420a2", head), true);
assert.equal(attributedTo("8d37ad10f9-2b589e8db210-f57a6ec39c", head), false);
// No head to compare against is not a match. An unknown is never a yes.
assert.equal(attributedTo("1da5a73711-x-y", null), false);
// A stub too short to identify a commit cannot buy an attribution.
assert.equal(attributedTo("1da5-x-y", head), false);
});
test("a door that cannot say which revision it serves writes no corpus at all", async () => {
// The refusal is the product working: the alternative is a number attributed
// to nothing, which the NEXT run would be compared against.
await assert.rejects(
() => proveRunner("http://127.0.0.1:1/runner/mcp", "not-a-real-token", "head"),
/the road was not proved/,
"an unreachable health door refuses rather than degrading",
);
});
test("the ledger prints the number's door and revision on the same line as the number", () => {
const file = {
generated_at: "2026-09-09T10:00:00.000Z",
runner: { mcp_door: "https://mini.example/runner/mcp", health_door: "https://mini.example/runner/health", revision: "1da5a73711-41156a7220a9-1879d420a2", hands: 98, kernel_head: "1da5a73711be", attributed: true, proved_at: "2026-09-09T09:59:00.000Z" },
cells: [cell({ host: "menubar", prompt: "gmail-inbox", mount: "mounted", ms: 4000 })],
road: [],
face_families: { published: [], missing: [] },
clusters: [],
drivers: HOSTS.map((h) => ({ host: h.id, label: h.label, road: h.road, surface: h.surface, driven: false, reason: "x", ownerCommand: h.ownerCommand })),
number: { green: 1, total: 84, line: "hosts: 1/84 cells" },
} as unknown as HostsFile;
const block = hostsLedgerBlock(file);
assert.match(block, /hosts: 1\/84 cells\*\* — counted against `1da5a73711-41156a7220a9-1879d420a2` through `https:\/\/mini\.example\/runner\/mcp`/);
assert.match(block, /98 hands/);
// And when the runner is NOT on this tree's head, the ledger says so rather
// than letting the reader assume the number is a verdict on their own tree.
const elsewhere = { ...file, runner: { ...(file as any).runner, revision: "8d37ad10f9-x-y", attributed: false } } as unknown as HostsFile;
assert.match(hostsLedgerBlock(elsewhere), /NOT this worktree's head/);
});
test("a brief tells its lane the anti-fix, including counting our own host green", () => {
const brief = hostsBrief({ key: "k", headline: "h", mechanism: "m", cells: ["c"], ownerCommand: "run this" });
assert.match(brief, /never `Publish`/);
assert.match(brief, /Driving the loop's OWN AppBridge host/);
assert.match(brief, /Dropping the cell from the matrix/);
});
/* ── THE COMMITTED CORPUS ──────────────────────────────────────────────────*/
test("the committed hosts corpus carries shapes, never a person's values", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return; // the file appears the first time `hosts` runs
const strings: string[] = [];
const walk = (o: unknown): void => {
if (typeof o === "string") strings.push(o);
else if (Array.isArray(o)) o.forEach(walk);
else if (o && typeof o === "object") Object.values(o as Record<string, unknown>).forEach(walk);
};
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
walk(parsed);
const hits = strings.flatMap((s) => s.match(/[\w.+-]+@[\w-]+\.[\w.]+|boulos|(?:\+?1[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}/giu) ?? []);
assert.deepEqual(hits, [], `personal values in the hosts corpus: ${hits.slice(0, 5).join(", ")}`);
assert.equal(parsed.schema, HOSTS_SCHEMA);
// A bearer is 65 bytes on this collection's runners; no token-shaped string
// may ever reach a committed corpus.
assert.deepEqual(strings.filter((s) => /^[A-Za-z0-9_-]{40,}$/.test(s)), [], "no token-shaped value in the corpus");
});
test("no screenshot path in the committed corpus points inside the repo", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return;
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
for (const c of parsed.cells) {
if (c.shot === null) continue;
assert.ok(c.shot.startsWith(SHOT_ROOT), `${c.shot} lives outside the repo — the owner's glass is not a commit`);
}
});
test("the committed corpus's number is the one its own cells add up to", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return;
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
// THE THIRD AXIS IS IN THE SUM OR IT IS NOT IN THE NUMBER ⟨2026-09-09⟩. When
// `familyReachCensus` landed, this recomputation still added two axes and
// reported 15/53 against a corpus that says 30/84 — a test that would have
// let the file and the number disagree forever.
const recomputed = scoreCells(parsed.cells, parsed.face_families.missing, parsed.face_families.unreachable ?? []);
assert.deepEqual(parsed.number, recomputed, "a number that does not recompute from its own cells is a claim, not a measurement");
});
test("the committed corpus names the door and revision its number was counted against", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return;
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
assert.ok(parsed.runner, "a committed number with no runner beside it is not a measurement — hosts-5's named gap");
assert.match(parsed.runner.revision, /^[0-9a-f]{7,}-/u, "the revision is the runner's own stamp, kernel segment first");
assert.equal(parsed.runner.health_door, healthDoorFor(parsed.runner.mcp_door), "the proof was read beside the door that was driven, never from a second URL");
});
/**
* THE FOLD INTO THE ONE LEDGER. `hosts` is a verb on the loop, not a second
* loop, so the loop's own ledger must carry the matrix — and must carry
* NOTHING when the battery has not run, because an absent measurement is
* absent, never a zero.
*/
test("loop's ledger folds the hosts block in, and folds nothing when there is none", async () => {
const { ledger, hostsBlockFor } = await import("./loop.ts");
const summary = { skills: 1, pass: 1, fail: 0, defer: 0 };
assert.equal(hostsBlockFor("/nonexistent-directory"), "", "absence stays absence");
assert.ok(!ledger([], summary, "").includes("hosts —"), "no block, no row");
const block = hostsBlockFor(HERE);
if (block === "") return; // the battery has not run in this checkout
const withHosts = ledger([], summary, block);
assert.ok(withHosts.includes("hosts:"), "the number reaches the loop's own ledger");
assert.ok(withHosts.includes("the road under the cells"), "so does the reading of the red cells");
});
test("a hand whose verbs fold onto no shape is reported and never counted", () => {
const clusters = redClusters({
cells: [], face_families: { published: ["gmail"], missing: [] },
no_shape: [{ skill: "snappy-krisp", verbs: ["fetch-meetings", "fetch-document"] }],
});
const found = clusters.find((c) => c.key === "hosts-verb-folds-onto-no-shape");
assert.ok(found, "the shapeless hands get their own cluster");
assert.match(found!.mechanism, /never reach the family question/);
// Reported, never counted: the number is host cells plus face-family cells.
assert.equal(scoreCells([], []).total, 0, "a shapeless hand adds nothing to the denominator");
});
test("a published family with no read that names it is a red cell, never an absent one", () => {
const shapes = ["list", "one", "profile"];
const fold = { posts: "list", search: "list" };
const root = mkdtempSync(join(tmpdir(), "hosts-reach-"));
// A family whose hand reads: green, and the cell NAMES the read.
mkdirSync(join(root, "snappy-skool"), { recursive: true });
writeFileSync(join(root, "snappy-skool", "api.ts"), 'export const CONTRACT = {\n verbs: {\n posts: { args: [] },\n },\n};\n');
// A family whose hand exists and folds onto nothing: red, and it says which.
mkdirSync(join(root, "snappy-krisp"), { recursive: true });
writeFileSync(join(root, "snappy-krisp", "api.ts"), 'export const CONTRACT = {\n verbs: {\n "fetch-meetings": { args: [] },\n },\n};\n');
// A family no hand carries the name of at all: red, for a different reason.
const cells = familyReachCensus(root, ["skool", "krisp", "imageset"], fold, shapes, {});
assert.equal(cells.length, 3, "every published family gets a cell — a denominator that cannot see a family flatters");
const skool = cells.find((c) => c.family === "skool")!;
assert.equal(skool.reachable, true);
assert.match(skool.read ?? "", /snappy-skool posts → list/);
const krisp = cells.find((c) => c.family === "krisp")!;
assert.equal(krisp.reachable, false);
assert.match(krisp.reason, /fetch-meetings/, "the reason must name the verb that folds onto nothing");
assert.match(krisp.reason, /naming the kind on the verb/, "and say the fix is the hand's own declaration, not a face to build");
const imageset = cells.find((c) => c.family === "imageset")!;
assert.equal(imageset.reachable, false);
assert.match(imageset.reason, /snappy-imageset/, "the reason must name the hand that does not exist");
rmSync(root, { recursive: true, force: true });
});
test("a hand that DECLARES a face reaches a family it is not named after", () => {
// THE DEFECT THIS IS WRITTEN AGAINST ⟨2026-09-09⟩: `snappy-thumbnails` draws
// the `imageset` faces and no rule made out of a hand's NAME can know it, so
// the family read red while a hand answered for it every day.
const shapes = ["list", "decision"];
const root = mkdtempSync(join(tmpdir(), "hosts-reach-declared-"));
mkdirSync(join(root, "snappy-thumbnails"), { recursive: true });
writeFileSync(join(root, "snappy-thumbnails", "api.ts"),
'export const CONTRACT = {\n verbs: {\n audit: { args: [] },\n variants: { args: [], effect: "read", face: "imageset-grid" },\n },\n};\n');
// The kind→family map is the BUILD REPORT's, passed in — never split off the
// kind's first hyphen, which `media-google-doc` would break.
const kindFamilies = { "imageset-grid": "imageset", "thumbnails-set": "thumbnails" };
assert.deepEqual(contractFaces(join(root, "snappy-thumbnails", "api.ts")), { variants: "imageset-grid" },
"the face word must be read onto ITS OWN verb and no other");
assert.deepEqual(declaredFaceReads(root, kindFamilies),
[{ hand: "snappy-thumbnails", verb: "variants", kind: "imageset-grid", family: "imageset" }]);
const cells = familyReachCensus(root, ["imageset", "thumbnails"], {}, shapes, kindFamilies);
const imageset = cells.find((c) => c.family === "imageset")!;
assert.equal(imageset.reachable, true, "a declared face is a read that names the family");
assert.match(imageset.read ?? "", /snappy-thumbnails variants → imageset-grid/);
assert.match(imageset.reason, /declares/);
// AND IT DOES NOT GREEN THE FAMILY IT DID NOT NAME. `thumbnails` has the hand
// of its own name and no verb folding onto a shape, so it stays red with the
// reason it always had — a declaration is a read, not a blanket.
const thumbnails = cells.find((c) => c.family === "thumbnails")!;
assert.equal(thumbnails.reachable, false, JSON.stringify(thumbnails));
assert.match(thumbnails.reason, /audit, variants/);
rmSync(root, { recursive: true, force: true });
});
test("kind → family comes from the build report, never from the kind's first hyphen", () => {
const root = mkdtempSync(join(tmpdir(), "hosts-kind-family-"));
assert.deepEqual(publishedKindFamilies(root), {}, "no report is an empty map, never a guess");
mkdirSync(join(root, "dist"), { recursive: true });
writeFileSync(join(root, "dist", "build-report.json"), JSON.stringify({
draws: [
{ kind: "media-google-doc", family: "media", member: "one" },
{ kind: "linkedin-post-published", family: "linkedin", member: "one" },
{ kind: "imageset-grid", family: "imageset", member: "decision" },
],
}));
assert.deepEqual(publishedKindFamilies(root), {
"media-google-doc": "media", "linkedin-post-published": "linkedin", "imageset-grid": "imageset",
}, "a first-hyphen split would have filed these under `media`/`linkedin`… and `imageset`, two of three wrong");
rmSync(root, { recursive: true, force: true });
});
test("the reach axis grows the denominator and never the green by itself", () => {
const shapes = ["list"];
const root = mkdtempSync(join(tmpdir(), "hosts-reach-sum-"));
const reach = familyReachCensus(root, ["a", "b"], {}, shapes, {});
assert.equal(scoreCells([], [], reach).total, 2);
assert.equal(scoreCells([], [], reach).green, 0, "two families nothing reaches are two reds, not two absences");
rmSync(root, { recursive: true, force: true });
});
// --- the telegram host cannot send a fixture ------------------------------
// ⟨the owner, 2026-09-09 10:32: "nothing he sees is a fixture — whatever is
// rendered is not fake data; it exercises the same roads."⟩ This driver used
// to fall back to the hand's shipped example when the road answered nothing,
// and the cell said so in prose while the matrix counted it. The guard is
// structural now: no data file, no send.
test("a telegram cell with no real read is not-driven, and nothing is sent", () => {
const hand = join(dirname(fileURLToPath(import.meta.url)), "..", "snappy-telegram", "api.ts");
assert.ok(existsSync(hand), "the hand is where the battery looks for it");
const observed = driveTelegram(hand, [
{ prompt: "gmail-inbox", kind: "gmail-list", dataFile: null, doors: null, gap: null },
]);
const cell = observed.find((o) => o.prompt === "gmail-inbox")!;
assert.equal(cell.driven, false, "a cell with no read behind it can never be green");
assert.match(String(cell.reason), /no read answered/);
// The denominator does not move: every prompt still has a cell.
assert.equal(observed.length, PROMPTS.length);
});
/**
* THE CELL RECORDER AND THE NUMBER — the two things a battery can lie with.
*
* A driver that returns nothing must not produce a green cell; a host that
* answered in prose must not be counted as having drawn; a decision cell whose
* press never reached the runner must not count as whole; and the number must
* never rise by dropping a cell it could not measure. Every test here was
* written against the failure it prevents, and each fails if the guard is
* removed.
*/
import assert from "node:assert/strict";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import {
HOSTS,
HOSTS_FILE,
HOSTS_SCHEMA,
PROMPTS,
SHOT_ROOT,
faceFamilyCensus,
familyForHand,
hostsBrief,
hostsLedgerBlock,
recordCell,
redClusters,
familyReachCensus,
contractFaces,
declaredFaceReads,
publishedKindFamilies,
scoreCells,
attributedTo,
healthDoorFor,
type HostCell,
type HostsFile,
} from "./hosts.ts";
import { driveTelegram, hasStateFor, readCodexTurn } from "./hosts-drive.ts";
import { proveRunner } from "./hosts-run.ts";
const HERE = dirname(fileURLToPath(import.meta.url));
const cell = (over: Partial<HostCell>): HostCell => ({
host: "codex", prompt: "gmail-inbox", mount: "text", reason: "r", ms: null, kind: null, shot: null, press: null, standing: null, ...over,
});
test("a card element in the host's own DOM is the only green", () => {
assert.equal(recordCell({ host: "chatgpt", prompt: "gmail-inbox", cardElement: true, ms: 4100 }).mount, "mounted");
assert.equal(recordCell({ host: "chatgpt", prompt: "gmail-inbox", cardElement: false, answered: true }).mount, "text");
});
test("a host that named a face and drew nothing is TEXT, never mounted", () => {
// The status/artifact defect, in its host-shaped form: `kind` is a claim,
// the card element is the artifact.
const seen = recordCell({ host: "codex", prompt: "gmail-inbox", cardElement: false, answered: true, kind: "gmail-list" });
assert.equal(seen.mount, "text");
assert.equal(seen.kind, "gmail-list");
});
test("a not-driven observation without a reason throws instead of writing a blank red", () => {
assert.throws(() => recordCell({ host: "telegram", prompt: "skool-feed", driven: false }), /carries no reason/);
});
test("a not-driven cell keeps no milliseconds — there was nothing to time", () => {
const seen = recordCell({ host: "claude", prompt: "skool-feed", driven: false, reason: "no saved state", ms: 999 });
assert.equal(seen.ms, null);
});
test("green needs the press on the decision prompt — a card that cannot be acted on is half", () => {
const drew = cell({ host: "chatgpt", prompt: "linkedin-decide", mount: "mounted", ms: 3000 });
assert.equal(scoreCells([drew], []).green, 0, "mounted with no press must not count");
const pressed = { ...drew, press: { word: "Later", reached_runner: true, within_ms: 30_000, how: "calls.jsonl" } };
assert.equal(scoreCells([pressed], []).green, 1);
const missed = { ...drew, press: { word: "Later", reached_runner: false, within_ms: null, how: "calls.jsonl" } };
assert.equal(scoreCells([missed], []).green, 0);
});
test("the number never rises by dropping a cell nobody could measure", () => {
const measured = [cell({ mount: "mounted", prompt: "gmail-inbox" }), cell({ mount: "not-driven", prompt: "skool-feed", host: "telegram" })];
const dropped = measured.filter((c) => c.mount !== "not-driven");
assert.equal(scoreCells(measured, []).total, 2);
assert.ok(scoreCells(measured, []).green / scoreCells(measured, []).total < scoreCells(dropped, []).green / scoreCells(dropped, []).total,
"a shrinking denominator would flatter the score, which is why not-driven stays in it");
});
test("the number's line is the exact contract the morning report reads", () => {
assert.equal(scoreCells([cell({ mount: "mounted" }), cell({ prompt: "skool-feed" })], []).line, "hosts: 1/2 cells");
});
test("the face-family census is the runner's own derivation, not a second one", () => {
assert.equal(familyForHand("snappy-gmail"), "gmail");
assert.equal(familyForHand("statechange"), "statechange");
const census = faceFamilyCensus(["snappy-gmail", "snappy-statechange"], ["gmail", "linkedin"]);
assert.deepEqual(census.map((c) => c.has_family), [true, false]);
assert.match(census[1].reason, /declares no "statechange" family/);
assert.match(census[1].reason, /a face to build, not a host to fix/);
});
test("a platform hand with no face family becomes a named red cluster", () => {
const clusters = redClusters({
cells: [cell({ mount: "not-driven", reason: "no saved state" })],
face_families: { published: ["gmail"], missing: faceFamilyCensus(["snappy-krisp"], ["gmail"]) },
});
const family = clusters.find((c) => c.key === "hosts-no-face-family");
assert.ok(family, "the census produces its own cluster");
assert.match(family!.mechanism, /no host driver can fix them/);
});
test("a road that names no face is its own cluster, ranked before the hosts are blamed", () => {
const clusters = redClusters({
cells: [],
face_families: { published: [], missing: [] },
road: [{ prompt: "skool-feed", hand: "snappy-skool", verb: "feed", outcome: "refused", kind: null, family: null, resource_uri: null, ms: 21, hops: 1, note: "verb_not_in_contract" }],
});
assert.equal(clusters[0].key, "hosts-road-names-no-face");
assert.match(clusters[0].mechanism, /the defect is under the hosts, not in them/);
});
test("an ask that needs a second call is a first-call defect, not a host defect", () => {
const clusters = redClusters({
cells: [], face_families: { published: [], missing: [] },
road: [{ prompt: "skool-feed", hand: "snappy-skool", verb: "posts", outcome: "answered", kind: "skool-feed", family: "skool", resource_uri: "ui://snappy/faces", ms: 1012, hops: 2, note: "the first call refused" }],
});
assert.ok(clusters.some((c) => c.key === "hosts-first-call-refuses"));
});
test("every host that cannot be driven carries the one line the owner runs, or names why none exists", () => {
for (const host of HOSTS) {
assert.ok(host.ownerCommand === null || host.ownerCommand.length > 10, `${host.id} owner command is a real line or explicitly none`);
}
assert.match(HOSTS.find((h) => h.id === "chatgpt")!.ownerCommand!, /agent-browser .*state save chatgpt/);
});
test("the four prompts are the same four for every host, and only one presses", () => {
assert.equal(PROMPTS.length, 4);
assert.deepEqual(PROMPTS.filter((p) => p.press !== null).map((p) => p.press), ["Later"]);
assert.ok(PROMPTS.every((p) => p.press !== "Publish" && p.press !== "Send"), "this battery never presses a send");
});
test("the saved-state gate matches by host word and never by accident", () => {
assert.equal(hasStateFor("chatgpt", ["snappy-site-hackernews.json", "chatgpt-main.json"]), true);
assert.equal(hasStateFor("chatgpt", ["snappy-taught-cap-mt40qwzo.json"]), false);
assert.equal(hasStateFor("claude", ["claude-web.json"]), true);
});
test("a codex turn is read for the MCP hop and the named kind, and a usage limit is not an answer", () => {
const limit = readCodexTurn([
JSON.stringify({ type: "turn.started" }),
JSON.stringify({ type: "turn.failed", error: { message: "You've hit your usage limit." } }),
]);
assert.equal(limit.answered, false);
assert.match(limit.failure!, /usage limit/, "the turn's own failure, not the first warning above it");
const warned = readCodexTurn([
JSON.stringify({ type: "item.completed", item: { type: "error", message: "Exceeded skills context budget of 2%." } }),
JSON.stringify({ type: "turn.failed", error: { message: JSON.stringify({ error: { message: "You've hit your usage limit." } }) } }),
]);
assert.match(warned.failure!, /usage limit/, "a warning above the failure must not be reported as the reason");
assert.equal(warned.warnings.length, 1, "the warning is kept, beside the failure — it is its own finding");
const drew = readCodexTurn([
JSON.stringify({ type: "item.completed", item: { type: "mcp_tool_call", name: "snappy-skills__execute", result: { kind: "gmail-list" } } }),
JSON.stringify({ type: "turn.completed" }),
]);
assert.equal(drew.reachedMcp, true);
assert.equal(drew.kind, "gmail-list");
});
test("the ledger block draws every host row and never a blank cell", () => {
const file = {
generated_at: "2026-09-09T06:00:00.000Z",
runner: { mcp_door: "https://runner.example/runner/mcp", health_door: "https://runner.example/runner/health", revision: "1da5a73711-41156a7220a9-1879d420a2", hands: 98, kernel_head: "1da5a73711be", attributed: true, proved_at: "2026-09-09T10:57:31.000Z" },
cells: HOSTS.flatMap((h) => PROMPTS.map((p) => cell({ host: h.id, prompt: p.id, mount: "not-driven", reason: "x" }))),
road: [{ prompt: "gmail-inbox", hand: "snappy-gmail", verb: "list", outcome: "answered", kind: "gmail-list", family: "gmail", resource_uri: "ui://snappy/faces", ms: 1538, hops: 1, note: "one call" }],
face_families: { published: ["gmail"], missing: [] },
clusters: [{ key: "hosts-not-driven-codex", headline: "codex could not be driven", mechanism: "m", cells: ["c"], ownerCommand: null }],
drivers: HOSTS.map((h) => ({ host: h.id, label: h.label, road: h.road, surface: h.surface, driven: false, reason: "x", ownerCommand: h.ownerCommand })),
number: { green: 0, total: 20, line: "hosts: 0/20 cells" },
} as unknown as HostsFile;
const block = hostsLedgerBlock(file);
for (const host of HOSTS) assert.ok(block.includes(host.label), `${host.label} has a row`);
assert.ok(!block.includes("| — |"), "no cell is blank; a cell nobody measured says not driven");
assert.ok(block.includes("hosts: 0/20 cells"));
});
/**
* MEASURED 2026-09-09 (hosts-5): the four Telegram cells were green on photos
* a PREVIOUS run had put in the chat, carried by `--already` so this lane sent
* nothing. Nothing was timed, so the ledger drew `**mounted** ? ms` — which on
* the owner's glass is indistinguishable from a card this run drew. A cell is
* only as true as the artifact it implies, and the artifact here is a photo id.
*/
test("a card standing on a previous run's photo says which photo, never `? ms`", () => {
const standing = recordCell({ host: "telegram", prompt: "gmail-inbox", cardElement: true, ms: null, standing: 8387, reason: "carried" });
assert.equal(standing.mount, "mounted");
assert.equal(standing.standing, 8387);
const file = {
generated_at: "2026-09-09T09:55:59.249Z",
runner: { mcp_door: "https://runner.example/runner/mcp", health_door: "https://runner.example/runner/health", revision: "1da5a73711-41156a7220a9-1879d420a2", hands: 98, kernel_head: "1da5a73711be", attributed: true, proved_at: "2026-09-09T10:57:31.000Z" },
cells: [standing],
road: [],
face_families: { published: [], missing: [] },
clusters: [],
drivers: HOSTS.map((h) => ({ host: h.id, label: h.label, road: h.road, surface: h.surface, driven: false, reason: "x", ownerCommand: h.ownerCommand })),
number: { green: 0, total: 20, line: "hosts: 0/20 cells" },
} as unknown as HostsFile;
const block = hostsLedgerBlock(file);
assert.match(block, /\*\*mounted\*\* · standing #8387/);
assert.ok(!block.includes("**mounted** ? ms"), "a cell nothing timed never claims a measurement it does not have");
// A cell this run DID draw still reads in milliseconds.
const fresh = { ...file, cells: [recordCell({ host: "telegram", prompt: "gmail-inbox", cardElement: true, ms: 4314 })] } as unknown as HostsFile;
assert.match(hostsLedgerBlock(fresh), /\*\*mounted\*\* 4314 ms/);
});
/* ── THE DOOR THE NUMBER WAS COUNTED AGAINST ───────────────────────────────
* hosts-5's named gap. The same battery read 1/20 against a local runner and
* 8/20 against the mini's within the same hour, and the corpus said nothing
* about which door either number came from — so the two were comparable to
* the eye and not comparable in fact.
*/
test("the health door is derived from the MCP door, never configured beside it", () => {
assert.equal(healthDoorFor("https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp"), "https://roberts-mac-mini.tailca7f42.ts.net/runner/health");
assert.equal(healthDoorFor("http://127.0.0.1:8899/runner/mcp/"), "http://127.0.0.1:8899/runner/health");
// A door that is not an /mcp door still gets its health beside it, rather
// than a second URL somebody has to keep in step ⟨duplicate roads⟩.
assert.equal(healthDoorFor("http://127.0.0.1:8899/runner"), "http://127.0.0.1:8899/runner/health");
});
test("a runner revision is attributed to a head only when it names it", () => {
const head = "1da5a73711bea7d5bc2ba482ef695000b0d8414f";
assert.equal(attributedTo("1da5a73711-41156a7220a9-1879d420a2", head), true);
assert.equal(attributedTo("8d37ad10f9-2b589e8db210-f57a6ec39c", head), false);
// No head to compare against is not a match. An unknown is never a yes.
assert.equal(attributedTo("1da5a73711-x-y", null), false);
// A stub too short to identify a commit cannot buy an attribution.
assert.equal(attributedTo("1da5-x-y", head), false);
});
test("a door that cannot say which revision it serves writes no corpus at all", async () => {
// The refusal is the product working: the alternative is a number attributed
// to nothing, which the NEXT run would be compared against.
await assert.rejects(
() => proveRunner("http://127.0.0.1:1/runner/mcp", "not-a-real-token", "head"),
/the road was not proved/,
"an unreachable health door refuses rather than degrading",
);
});
test("the ledger prints the number's door and revision on the same line as the number", () => {
const file = {
generated_at: "2026-09-09T10:00:00.000Z",
runner: { mcp_door: "https://mini.example/runner/mcp", health_door: "https://mini.example/runner/health", revision: "1da5a73711-41156a7220a9-1879d420a2", hands: 98, kernel_head: "1da5a73711be", attributed: true, proved_at: "2026-09-09T09:59:00.000Z" },
cells: [cell({ host: "menubar", prompt: "gmail-inbox", mount: "mounted", ms: 4000 })],
road: [],
face_families: { published: [], missing: [] },
clusters: [],
drivers: HOSTS.map((h) => ({ host: h.id, label: h.label, road: h.road, surface: h.surface, driven: false, reason: "x", ownerCommand: h.ownerCommand })),
number: { green: 1, total: 84, line: "hosts: 1/84 cells" },
} as unknown as HostsFile;
const block = hostsLedgerBlock(file);
assert.match(block, /hosts: 1\/84 cells\*\* — counted against `1da5a73711-41156a7220a9-1879d420a2` through `https:\/\/mini\.example\/runner\/mcp`/);
assert.match(block, /98 hands/);
// And when the runner is NOT on this tree's head, the ledger says so rather
// than letting the reader assume the number is a verdict on their own tree.
const elsewhere = { ...file, runner: { ...(file as any).runner, revision: "8d37ad10f9-x-y", attributed: false } } as unknown as HostsFile;
assert.match(hostsLedgerBlock(elsewhere), /NOT this worktree's head/);
});
test("a brief tells its lane the anti-fix, including counting our own host green", () => {
const brief = hostsBrief({ key: "k", headline: "h", mechanism: "m", cells: ["c"], ownerCommand: "run this" });
assert.match(brief, /never `Publish`/);
assert.match(brief, /Driving the loop's OWN AppBridge host/);
assert.match(brief, /Dropping the cell from the matrix/);
});
/* ── THE COMMITTED CORPUS ──────────────────────────────────────────────────*/
test("the committed hosts corpus carries shapes, never a person's values", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return; // the file appears the first time `hosts` runs
const strings: string[] = [];
const walk = (o: unknown): void => {
if (typeof o === "string") strings.push(o);
else if (Array.isArray(o)) o.forEach(walk);
else if (o && typeof o === "object") Object.values(o as Record<string, unknown>).forEach(walk);
};
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
walk(parsed);
const hits = strings.flatMap((s) => s.match(/[\w.+-]+@[\w-]+\.[\w.]+|boulos|(?:\+?1[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}/giu) ?? []);
assert.deepEqual(hits, [], `personal values in the hosts corpus: ${hits.slice(0, 5).join(", ")}`);
assert.equal(parsed.schema, HOSTS_SCHEMA);
// A bearer is 65 bytes on this collection's runners; no token-shaped string
// may ever reach a committed corpus.
assert.deepEqual(strings.filter((s) => /^[A-Za-z0-9_-]{40,}$/.test(s)), [], "no token-shaped value in the corpus");
});
test("no screenshot path in the committed corpus points inside the repo", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return;
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
for (const c of parsed.cells) {
if (c.shot === null) continue;
assert.ok(c.shot.startsWith(SHOT_ROOT), `${c.shot} lives outside the repo — the owner's glass is not a commit`);
}
});
test("the committed corpus's number is the one its own cells add up to", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return;
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
// THE THIRD AXIS IS IN THE SUM OR IT IS NOT IN THE NUMBER ⟨2026-09-09⟩. When
// `familyReachCensus` landed, this recomputation still added two axes and
// reported 15/53 against a corpus that says 30/84 — a test that would have
// let the file and the number disagree forever.
const recomputed = scoreCells(parsed.cells, parsed.face_families.missing, parsed.face_families.unreachable ?? []);
assert.deepEqual(parsed.number, recomputed, "a number that does not recompute from its own cells is a claim, not a measurement");
});
test("the committed corpus names the door and revision its number was counted against", () => {
const path = join(HERE, HOSTS_FILE);
if (!existsSync(path)) return;
const parsed = JSON.parse(readFileSync(path, "utf8")) as HostsFile;
assert.ok(parsed.runner, "a committed number with no runner beside it is not a measurement — hosts-5's named gap");
assert.match(parsed.runner.revision, /^[0-9a-f]{7,}-/u, "the revision is the runner's own stamp, kernel segment first");
assert.equal(parsed.runner.health_door, healthDoorFor(parsed.runner.mcp_door), "the proof was read beside the door that was driven, never from a second URL");
});
/**
* THE FOLD INTO THE ONE LEDGER. `hosts` is a verb on the loop, not a second
* loop, so the loop's own ledger must carry the matrix — and must carry
* NOTHING when the battery has not run, because an absent measurement is
* absent, never a zero.
*/
test("loop's ledger folds the hosts block in, and folds nothing when there is none", async () => {
const { ledger, hostsBlockFor } = await import("./loop.ts");
const summary = { skills: 1, pass: 1, fail: 0, defer: 0 };
assert.equal(hostsBlockFor("/nonexistent-directory"), "", "absence stays absence");
assert.ok(!ledger([], summary, "").includes("hosts —"), "no block, no row");
const block = hostsBlockFor(HERE);
if (block === "") return; // the battery has not run in this checkout
const withHosts = ledger([], summary, block);
assert.ok(withHosts.includes("hosts:"), "the number reaches the loop's own ledger");
assert.ok(withHosts.includes("the road under the cells"), "so does the reading of the red cells");
});
test("a hand whose verbs fold onto no shape is reported and never counted", () => {
const clusters = redClusters({
cells: [], face_families: { published: ["gmail"], missing: [] },
no_shape: [{ skill: "snappy-krisp", verbs: ["fetch-meetings", "fetch-document"] }],
});
const found = clusters.find((c) => c.key === "hosts-verb-folds-onto-no-shape");
assert.ok(found, "the shapeless hands get their own cluster");
assert.match(found!.mechanism, /never reach the family question/);
// Reported, never counted: the number is host cells plus face-family cells.
assert.equal(scoreCells([], []).total, 0, "a shapeless hand adds nothing to the denominator");
});
test("a published family with no read that names it is a red cell, never an absent one", () => {
const shapes = ["list", "one", "profile"];
const fold = { posts: "list", search: "list" };
const root = mkdtempSync(join(tmpdir(), "hosts-reach-"));
// A family whose hand reads: green, and the cell NAMES the read.
mkdirSync(join(root, "snappy-skool"), { recursive: true });
writeFileSync(join(root, "snappy-skool", "api.ts"), 'export const CONTRACT = {\n verbs: {\n posts: { args: [] },\n },\n};\n');
// A family whose hand exists and folds onto nothing: red, and it says which.
mkdirSync(join(root, "snappy-krisp"), { recursive: true });
writeFileSync(join(root, "snappy-krisp", "api.ts"), 'export const CONTRACT = {\n verbs: {\n "fetch-meetings": { args: [] },\n },\n};\n');
// A family no hand carries the name of at all: red, for a different reason.
const cells = familyReachCensus(root, ["skool", "krisp", "imageset"], fold, shapes, {});
assert.equal(cells.length, 3, "every published family gets a cell — a denominator that cannot see a family flatters");
const skool = cells.find((c) => c.family === "skool")!;
assert.equal(skool.reachable, true);
assert.match(skool.read ?? "", /snappy-skool posts → list/);
const krisp = cells.find((c) => c.family === "krisp")!;
assert.equal(krisp.reachable, false);
assert.match(krisp.reason, /fetch-meetings/, "the reason must name the verb that folds onto nothing");
assert.match(krisp.reason, /naming the kind on the verb/, "and say the fix is the hand's own declaration, not a face to build");
const imageset = cells.find((c) => c.family === "imageset")!;
assert.equal(imageset.reachable, false);
assert.match(imageset.reason, /snappy-imageset/, "the reason must name the hand that does not exist");
rmSync(root, { recursive: true, force: true });
});
test("a hand that DECLARES a face reaches a family it is not named after", () => {
// THE DEFECT THIS IS WRITTEN AGAINST ⟨2026-09-09⟩: `snappy-thumbnails` draws
// the `imageset` faces and no rule made out of a hand's NAME can know it, so
// the family read red while a hand answered for it every day.
const shapes = ["list", "decision"];
const root = mkdtempSync(join(tmpdir(), "hosts-reach-declared-"));
mkdirSync(join(root, "snappy-thumbnails"), { recursive: true });
writeFileSync(join(root, "snappy-thumbnails", "api.ts"),
'export const CONTRACT = {\n verbs: {\n audit: { args: [] },\n variants: { args: [], effect: "read", face: "imageset-grid" },\n },\n};\n');
// The kind→family map is the BUILD REPORT's, passed in — never split off the
// kind's first hyphen, which `media-google-doc` would break.
const kindFamilies = { "imageset-grid": "imageset", "thumbnails-set": "thumbnails" };
assert.deepEqual(contractFaces(join(root, "snappy-thumbnails", "api.ts")), { variants: "imageset-grid" },
"the face word must be read onto ITS OWN verb and no other");
assert.deepEqual(declaredFaceReads(root, kindFamilies),
[{ hand: "snappy-thumbnails", verb: "variants", kind: "imageset-grid", family: "imageset" }]);
const cells = familyReachCensus(root, ["imageset", "thumbnails"], {}, shapes, kindFamilies);
const imageset = cells.find((c) => c.family === "imageset")!;
assert.equal(imageset.reachable, true, "a declared face is a read that names the family");
assert.match(imageset.read ?? "", /snappy-thumbnails variants → imageset-grid/);
assert.match(imageset.reason, /declares/);
// AND IT DOES NOT GREEN THE FAMILY IT DID NOT NAME. `thumbnails` has the hand
// of its own name and no verb folding onto a shape, so it stays red with the
// reason it always had — a declaration is a read, not a blanket.
const thumbnails = cells.find((c) => c.family === "thumbnails")!;
assert.equal(thumbnails.reachable, false, JSON.stringify(thumbnails));
assert.match(thumbnails.reason, /audit, variants/);
rmSync(root, { recursive: true, force: true });
});
test("kind → family comes from the build report, never from the kind's first hyphen", () => {
const root = mkdtempSync(join(tmpdir(), "hosts-kind-family-"));
assert.deepEqual(publishedKindFamilies(root), {}, "no report is an empty map, never a guess");
mkdirSync(join(root, "dist"), { recursive: true });
writeFileSync(join(root, "dist", "build-report.json"), JSON.stringify({
draws: [
{ kind: "media-google-doc", family: "media", member: "one" },
{ kind: "linkedin-post-published", family: "linkedin", member: "one" },
{ kind: "imageset-grid", family: "imageset", member: "decision" },
],
}));
assert.deepEqual(publishedKindFamilies(root), {
"media-google-doc": "media", "linkedin-post-published": "linkedin", "imageset-grid": "imageset",
}, "a first-hyphen split would have filed these under `media`/`linkedin`… and `imageset`, two of three wrong");
rmSync(root, { recursive: true, force: true });
});
test("the reach axis grows the denominator and never the green by itself", () => {
const shapes = ["list"];
const root = mkdtempSync(join(tmpdir(), "hosts-reach-sum-"));
const reach = familyReachCensus(root, ["a", "b"], {}, shapes, {});
assert.equal(scoreCells([], [], reach).total, 2);
assert.equal(scoreCells([], [], reach).green, 0, "two families nothing reaches are two reds, not two absences");
rmSync(root, { recursive: true, force: true });
});
// --- the telegram host cannot send a fixture ------------------------------
// ⟨the owner, 2026-09-09 10:32: "nothing he sees is a fixture — whatever is
// rendered is not fake data; it exercises the same roads."⟩ This driver used
// to fall back to the hand's shipped example when the road answered nothing,
// and the cell said so in prose while the matrix counted it. The guard is
// structural now: no data file, no send.
test("a telegram cell with no real read is not-driven, and nothing is sent", () => {
const hand = join(dirname(fileURLToPath(import.meta.url)), "..", "snappy-telegram", "api.ts");
assert.ok(existsSync(hand), "the hand is where the battery looks for it");
const observed = driveTelegram(hand, [
{ prompt: "gmail-inbox", kind: "gmail-list", dataFile: null, doors: null, gap: null },
]);
const cell = observed.find((o) => o.prompt === "gmail-inbox")!;
assert.equal(cell.driven, false, "a cell with no read behind it can never be green");
assert.match(String(cell.reason), /no read answered/);
// The denominator does not move: every prompt still has a cell.
assert.equal(observed.length, PROMPTS.length);
});
/**
* THE HOSTS BATTERY — the same four asks, put to every host, counted.
*
* The owner, 2026-09-09 01:3x ET, on one green cell: "this is barely scraping
* it — it has to truly be seamless and work every six ways from here to Sunday
* and back, for LinkedIn, for Skool, for Statechange, for everything under
* God's sky."
*
* One cell had been proven: in the ChatGPT app, signed in as him, "Using
* Snappy Skills, show me my Gmail inbox, three messages" MOUNTED the Gmail
* card. One cell is not a matrix, and a matrix nobody counts is a feeling.
* So this module is the MATRIX and the NUMBER: host × prompt, each cell
* `mounted` / `text` / `not-driven`, and one line at the end.
*
* WHY IT LIVES INSIDE THE LOOP AND NOT BESIDE IT ⟨CLAUDE.md §4, duplicate
* roads are banned⟩. `probe` measures the wire, `render` measures ONE host
* this repo wrote itself, `lint` grades the source. A face that draws
* perfectly in the loop's own AppBridge host and does not appear in ChatGPT
* is a defect none of the three can see, because none of them ever asked a
* host somebody else wrote. `hosts` is that third measurement, and it is a
* verb on the SAME api, writing into the SAME ledger, ranked by the SAME
* clusterer — not a second loop with a second ledger that stops agreeing.
*
* THE TWO LAWS THIS FILE IS WRITTEN AGAINST:
*
* A STATUS IS ONLY AS TRUE AS THE ARTIFACT IT IMPLIES ⟨CLAUDE.md §10⟩. A
* host is `mounted` only when a CARD ELEMENT was found in that host's own
* DOM — an iframe, an app frame, a face root. A host that answered in prose
* about the inbox is `text`, and it is RED, because the product's whole
* claim is the drawing.
*
* NEVER A GREEN BY ABSENCE. A host that could not be driven is a named
* `not-driven` cell carrying the reason and the ONE LINE the owner runs to
* make it drivable — never omitted, never counted as passing, never
* silently reduced out of the denominator.
*
* SHAPES, NEVER VALUES. Nothing here records a subject line, a sender, a
* message body or a phone number: a cell holds booleans, milliseconds, a face
* KIND string, and a path to a screenshot that lives OUTSIDE the repo.
* `hosts.test.ts` guards the committed file the same way `loop.test.ts`
* guards the probe corpus.
*/
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
export const HOSTS_FILE = "tool-design-hosts.json";
export const HOSTS_SCHEMA = "snappy-tool-design/hosts@1";
/** Screenshots and driver logs live OUTSIDE the repo — the owner's glass is
* not a commit. `hosts.test.ts` asserts no cell path points inside it. */
export const SHOT_ROOT = "/tmp/hosts-battery";
/* ── THE PROMPTS ───────────────────────────────────────────────────────────
* The same four asks for every host, because the matrix only means something
* when the row and the column vary and the QUESTION does not. Three reads and
* one decision: the decision is the one that carries a press, and the press is
* the axis nothing before this measured at all.
*/
export interface PromptSpec {
id: string;
/** Word for word, what a person types. */
text: string;
/** The hand this ask should reach, so a miss can be named against a road. */
hand: string;
/** The face family the answer should take. */
family: string;
/** LATER on a decision face. `null` on a read — there is nothing to press. */
press: string | null;
}
export const PROMPTS: readonly PromptSpec[] = [
{ id: "gmail-inbox", text: "Using Snappy Skills, show me my Gmail inbox, three messages", hand: "snappy-gmail", family: "gmail", press: null },
{ id: "skool-feed", text: "Using Snappy Skills, show me my Skool feed", hand: "snappy-skool", family: "skool", press: null },
{ id: "linkedin-profile", text: "Using Snappy Skills, show me my LinkedIn profile", hand: "snappy-linkedin", family: "linkedin", press: null },
// NEVER "Publish". LATER is the only word this battery presses: the press
// must prove the ROAD from the host back to the runner, and proving it with
// a send would post to a person to measure a wire ⟨CLAUDE.md §6⟩.
{ id: "linkedin-decide", text: "Using Snappy Skills, draft a LinkedIn post about launch checklists and let me decide", hand: "snappy-linkedin", family: "linkedin", press: "Later" },
] as const;
/* ── THE HOSTS ─────────────────────────────────────────────────────────────
* A host is a place a person actually asks from. `surface` is what the host
* can even do with a card, and it is the difference between a defect and a
* fact: a terminal that answers in prose is not broken, it has no card
* surface — so its cells are red for a REASON that names the host's own
* ceiling, and the brief that comes out of it is about the host, not the face.
*/
export type HostId = "chatgpt" | "claude" | "codex" | "menubar" | "telegram";
export type CardSurface = "widget" | "webview" | "image" | "none";
export interface HostSpec {
id: HostId;
label: string;
/** How this host is reached at all. */
road: string;
surface: CardSurface;
/** The ONE line the owner runs to make this host drivable, when it is not. */
ownerCommand: string | null;
}
export const HOSTS: readonly HostSpec[] = [
{ id: "chatgpt", label: "ChatGPT web app", road: "agent-browser --state chatgpt → https://chatgpt.com", surface: "widget", ownerCommand: `agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt` },
{ id: "claude", label: "Claude.ai web app", road: "agent-browser --state claude → https://claude.ai", surface: "widget", ownerCommand: `agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude` },
// The codex road is CONFIGURED and logged in (`codex mcp list` shows
// snappy-skills enabled, Auth OAuth); what stops it is the account's own
// credit window, which is his to open and nobody else's.
{ id: "codex", label: "Codex CLI", road: "codex exec --json (mcp_servers.snappy-skills)", surface: "none", ownerCommand: `open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>` },
{ id: "menubar", label: "Snappy menu bar", road: "SnappyBar popover WKWebView over its local runner", surface: "webview", ownerCommand: `cd ~/Projects/snappy-runner/apps/snappy-bar && ./scripts/make-app.sh && open dist/Snappy.app # then: npx tsx api.ts hosts --only menubar --token-file <file>` },
{ id: "telegram", label: "Telegram on the phone", road: "snappy-telegram show (PNG) + decisions", surface: "image", ownerCommand: null },
] as const;
/* ── A CELL ────────────────────────────────────────────────────────────────*/
/** What the host did with the answer. `mounted` is the only green. */
export type Mount = "mounted" | "text" | "not-driven";
export interface PressRecord {
word: string;
/** The mini's calls.jsonl gained an `execute` for the hand within the window. */
reached_runner: boolean;
within_ms: number | null;
/** How the press was looked for, so a false can be read. */
how: string;
}
export interface HostCell {
host: HostId;
prompt: string;
mount: Mount;
/** Why this cell is what it is. Required on every non-`mounted` cell. */
reason: string;
/** Time from the ask to the card appearing. `null` when it never appeared. */
ms: number | null;
/** The face kind the tool result named, when the host let it be seen. */
kind: string | null;
/** A path under SHOT_ROOT — outside the repo, never committed. */
shot: string | null;
press: PressRecord | null;
/**
* THE ARTIFACT THIS CELL IS STANDING ON, when it is not one this run made.
* A card carried by `--already` is green on a photo a PREVIOUS run put in
* the chat, and it has no milliseconds because nothing was timed. Written
* `**mounted** ? ms`, that cell read on the owner's glass exactly like a
* card this run drew — the status/artifact defect in miniature. So the
* message id rides the cell and the ledger prints it instead.
*/
standing: number | null;
}
/**
* THE ONE RECORDER. Every driver hands its observation here and nothing else
* decides a cell's colour, because two deciders would disagree the first time
* either moved.
*
* A card element present is `mounted`. A host that answered without one is
* `text`. A host that never got the ask is `not-driven` — and a not-driven
* observation that carries no reason is a bug in the DRIVER, so it throws
* rather than writing a cell nobody can read.
*/
export interface Observation {
host: HostId;
prompt: string;
/** Did a card element exist in the host's own DOM / output? */
cardElement?: boolean;
/** Did the host answer at all? */
answered?: boolean;
driven?: boolean;
reason?: string;
ms?: number | null;
kind?: string | null;
shot?: string | null;
press?: PressRecord | null;
/** A prior run's card this observation stands on, by message id. */
standing?: number | null;
}
export function recordCell(seen: Observation): HostCell {
const base = { host: seen.host, prompt: seen.prompt, ms: seen.ms ?? null, kind: seen.kind ?? null, shot: seen.shot ?? null, press: seen.press ?? null, standing: seen.standing ?? null };
if (seen.driven === false) {
if (!seen.reason) throw new Error(`not-driven cell ${seen.host}/${seen.prompt} carries no reason — a red cell must name what would make it drivable`);
return { ...base, mount: "not-driven", reason: seen.reason, ms: null };
}
if (seen.cardElement === true) {
return { ...base, mount: "mounted", reason: seen.reason ?? "a card element was present in the host's own DOM" };
}
return {
...base,
mount: "text",
reason: seen.reason ?? (seen.answered === false
? "the host produced no answer before the driver's deadline"
: "the host answered without a card element — text only"),
ms: null,
};
}
/* ── THE ROAD UNDER THE CELL ───────────────────────────────────────────────
* A red cell says a host did not draw. It does not say WHY, and the two whys
* are different lanes: the runner never named a face, or the runner named one
* and the host ignored it. So every prompt is also put to the runner directly
* — the same MCP door the host would have reached — and the answer recorded.
*
* This is deliberately NOT a host cell and NOT in the number. Driving our own
* door and counting it green is precisely the anti-fix the briefs warn about.
* It is the READING of a red cell, nothing more.
*/
export interface RoadCell {
prompt: string;
hand: string;
verb: string;
outcome: string;
/** The face the runner named, if it named one. */
kind: string | null;
family: string | null;
resource_uri: string | null;
ms: number | null;
/** How many calls it took a caller to get here from the person's own words. */
hops: number;
note: string;
}
/* ── THE FACE-FAMILY CENSUS ────────────────────────────────────────────────
* A red cell that no host can fix: the hand answers and NO FACE FAMILY EXISTS
* for it, so every host on every row draws prose by construction.
*
* The derivation is the RUNNER'S OWN, read from its source of truth rather
* than restated: `snappy-runner/src/face.ts#familyForHand` is the hand's name
* minus the collection prefix, and the families are `snappy-faces`'
* `FACE_FAMILIES`. Copying either would put a third vocabulary in the world.
*/
export interface FaceFamilyCell {
skill: string;
family: string;
has_family: boolean;
reason: string;
}
/** `snappy-gmail` → `gmail`. The runner's rule, restated in one place. */
export function familyForHand(name: string): string {
return name.startsWith("snappy-") ? name.slice("snappy-".length) : name;
}
/**
* Which hands answer with no face at all.
*
* `hands` is the set of skills whose answers a person would expect DRAWN — a
* platform read. It is passed in rather than guessed here so the caller can
* derive it from the contracts it already holds.
*/
export function faceFamilyCensus(hands: readonly string[], families: readonly string[]): FaceFamilyCell[] {
const known = new Set(families);
return [...hands].sort().map((skill) => {
const family = familyForHand(skill);
const has_family = known.has(family);
return {
skill,
family,
has_family,
reason: has_family
? `snappy-faces declares the "${family}" family`
: `snappy-faces declares no "${family}" family, so every host draws ${skill}'s answers as prose — a face to build, not a host to fix`,
};
});
}
/* ── THE FAMILY-REACH CENSUS ───────────────────────────────────────────────
* THE SAME QUESTION FROM THE OTHER END, AND IT IS NOT THE SAME CELL.
*
* `faceFamilyCensus` walks the HANDS and asks whether a face exists for each.
* That axis can only see families some hand already lands on, so the six
* families the library gained on 2026-09-09 — krisp, knowledge, transcripts,
* imageset, walkthrough, doc — entered the number as TWO cells (knowledge,
* transcripts) and four invisible ones. A face nobody's words can reach is a
* real way the product fails a person, and a denominator that cannot see it
* flatters ⟨CLAUDE.md §10⟩.
*
* So every PUBLISHED family gets a cell, and its green is A READ THAT NAMES
* IT — asked in the runner's own two roads, in the runner's own order:
*
* 1. THE HAND DECLARED THE FACE. `verbs.<verb>.face` names a kind and the
* build report says which family that kind belongs to. This is how a hand
* reaches a family it is not named after, and it is the road the runner
* takes first (`face.ts` step 2, above its own derivation).
* 2. THE NAME ROUTE. `familyForHand` inverted — `snappy-<family>` — holding a
* verb that folds onto one of the manifest's shapes through the runner's
* `VERB_SHAPE`. Read out of the runner's source, never restated: an alias
* table here saying "docs means doc" would be a third vocabulary and would
* report a reach the runner does not have.
*
* MEASURED 2026-09-09 on kernel a5a2380, BEFORE the declarations: 15/31. The
* sixteen red split two ways and the fix differed. `krisp`, `thumbnails`,
* `walkthrough` and `deploy` had the hand and no shapeable verb; `imageset`,
* `doc`, `data`, `code`, `agent`, `schedule` and the rest had no hand of that
* name at all, though the manifest's own notes say which hands they were built
* for. Both are now the SAME fix — the hand says which face its read draws —
* and a family still red after it is one no hand makes yet, which is the
* honest answer and stays in the denominator.
*/
export interface FamilyReachCell {
family: string;
/** `<hand> <verb> → <shape>` — the read a person's words land on. */
read: string | null;
reachable: boolean;
reason: string;
}
/** The hand the runner's own rule routes to this family: `familyForHand`
* inverted, which is exactly `snappy-<family>` and nothing else. */
export function handForFamily(family: string): string {
return `snappy-${family}`;
}
export function familyReachCensus(
skillsRoot: string,
families: readonly string[],
fold: Record<string, string>,
shapes: readonly string[],
kindFamilies: Record<string, string>,
): FamilyReachCell[] {
const shapeWords = new Set(shapes);
const declared = declaredFaceReads(skillsRoot, kindFamilies);
return [...families].sort().map((family) => {
// 1. A HAND DECLARED IT. `verbs.<verb>.face` names a kind, and the build
// report says which family that kind belongs to — so a hand reaches a
// family it is not NAMED after, which no rule made out of the hand's
// name can do. This is checked first because the hand is the source and
// the name route below is the derivation ⟨2026-09-09⟩.
const said = declared.find((d) => d.family === family);
if (said !== undefined) {
return {
family,
read: `${said.hand} ${said.verb} → ${said.kind}`,
reachable: true,
reason: `${said.hand}'s contract declares "${said.verb}" draws ${said.kind}, which the build report files under "${family}" — the hand named the face and nothing was derived over it`,
};
}
const hand = handForFamily(family);
const api = join(skillsRoot, hand, "api.ts");
if (!existsSync(api)) {
return {
family,
read: null,
reachable: false,
reason: `no hand named ${hand} exists and no hand's contract declares a "${family}" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "${family}" faces, however well they draw. `
+ `If a hand already makes this thing, it says so with \`verbs.<verb>.face\`; if none does, this family is made by no hand yet and that is the honest red`,
};
}
const verbs = contractVerbs(api);
const drawable = verbs.filter((verb) => shapeWords.has(verb) || shapeWords.has(fold[verb] ?? ""));
if (drawable.length === 0) {
return {
family,
read: null,
reachable: false,
reason: `${hand} exists, declares no \`face\` on any verb, and none of its verbs (${verbs.join(", ") || "none declared"}) folds onto a shape the manifest publishes — so the runner cannot decide which "${family}" face an answer takes. `
+ `The fix is the hand naming the kind on the verb that draws it, not a face to build`,
};
}
const verb = drawable[0];
return {
family,
read: `${hand} ${verb} → ${shapeWords.has(verb) ? verb : fold[verb]}`,
reachable: true,
reason: `${hand} ${verb} folds onto ${shapeWords.has(verb) ? verb : fold[verb]}, so a person's read reaches the "${family}" family`,
};
});
}
/* ── THE JOIN THE HAND DECLARES ⟨2026-09-09⟩ ────────────────────────────────
*
* The census above began as one question — does `snappy-<family>` exist and
* does one of its verbs fold onto a shape — and that question can only ever
* find a family a hand is NAMED after. `snappy-thumbnails` draws the
* `imageset` faces; `snappy-docs` draws `doc-card`; `snappy-agent-host` draws
* `agent-run`. Sixteen of thirty-one families were red on kernel a5a2380 and
* half of them for that reason alone.
*
* So the hand declares the kind on its verb (`verbs.<verb>.face`), the runner
* reads that declaration before its own derivation, and this census counts it.
* ONE mechanism in three places, each reading the same words — never a table
* here saying "thumbnails means imageset", which would be the third vocabulary
* ⟨CLAUDE.md §4⟩ and would be wrong the first week.
*/
export interface DeclaredFaceRead { hand: string; verb: string; kind: string; family: string }
/** Every `verbs.<verb>.face` a hand's contract declares, with the family the
* build report files that kind under. PARSED, never spawned: this census runs
* in the middle of the hosts battery and cannot start 98 child processes. */
export function declaredFaceReads(skillsRoot: string, kindFamilies: Record<string, string>): DeclaredFaceRead[] {
const out: DeclaredFaceRead[] = [];
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith("snappy-")) continue;
for (const [verb, kind] of Object.entries(contractFaces(join(skillsRoot, entry.name, "api.ts")))) {
const family = kindFamilies[kind];
if (family === undefined) continue;
out.push({ hand: entry.name, verb, kind, family });
}
}
return out.sort((a, b) => a.hand.localeCompare(b.hand) || a.verb.localeCompare(b.verb));
}
/** One hand's declared faces, verb → kind. The verb blocks are sliced at the
* same four-space `"verb": {` boundary `contractVerbs` finds them at, so a
* `face` word can never be read onto the verb above or below it. */
export function contractFaces(apiTs: string): Record<string, string> {
if (!existsSync(apiTs)) return {};
const source = readFileSync(apiTs, "utf8");
const start = source.indexOf("verbs: {");
if (start < 0) return {};
const block = source.slice(start, start + 40_000);
const heads = [...block.matchAll(/^\s{4}"?([a-z][a-z0-9-]*)"?:\s*\{/gm)];
const faces: Record<string, string> = {};
for (let i = 0; i < heads.length; i += 1) {
const from = heads[i]!.index!;
const to = i + 1 < heads.length ? heads[i + 1]!.index! : block.length;
const face = block.slice(from, to).match(/\bface:\s*"([a-z0-9-]+)"/);
if (face !== null) faces[heads[i]![1]!] = face[1]!;
}
return faces;
}
/** Kind → family, from the faces skill's OWN build report — the same file the
* runner reads for the same answer. Never split off the kind's first hyphen:
* `linkedin-post-published` and `media-google-doc` would both come out wrong,
* and a family map that is wrong for two rows is a census that lies twice. */
export function publishedKindFamilies(facesSkillDir: string): Record<string, string> {
const report = join(facesSkillDir, "dist", "build-report.json");
if (!existsSync(report)) return {};
try {
const draws = (JSON.parse(readFileSync(report, "utf8")) as { draws?: unknown }).draws;
if (!Array.isArray(draws)) return {};
const out: Record<string, string> = {};
for (const d of draws as { kind?: unknown; family?: unknown }[]) {
if (typeof d?.kind === "string" && typeof d.family === "string") out[d.kind] = d.family;
}
return out;
} catch { return {}; }
}
/** The families snappy-faces publishes, read from its manifest at call time. */
export function publishedFamilies(facesSkillDir: string): string[] {
const manifest = join(facesSkillDir, "library", "src", "manifest.ts");
if (!existsSync(manifest)) return [];
const source = readFileSync(manifest, "utf8");
const block = source.slice(source.indexOf("FACE_FAMILIES"));
return [...new Set([...block.matchAll(/slug:\s*"([a-z0-9-]+)"/g)].map((m) => m[1]))];
}
/**
* THE VERB→SHAPE FOLD, read from the RUNNER rather than restated.
*
* `snappy-runner/src/face.ts` holds `VERB_SHAPE`: the words hands actually use
* ("feed", "posts", "recent", "me") folded onto the manifest's closed shape
* vocabulary. Copying that table here would be a THIRD vocabulary for one idea
* ⟨CLAUDE.md §4⟩, and the copy would be wrong the first week. So it is parsed
* out of the runner's own source at call time. When the runner is not on this
* machine the fold falls back to the shape words themselves — a smaller,
* honest answer, never a guessed bigger one.
*/
export function verbShapeFold(runnerFaceTs: string): Record<string, string> {
if (!existsSync(runnerFaceTs)) return {};
const source = readFileSync(runnerFaceTs, "utf8");
const start = source.indexOf("const VERB_SHAPE");
if (start < 0) return {};
const block = source.slice(start, source.indexOf("};", start));
const fold: Record<string, string> = {};
for (const [, verb, shape] of block.matchAll(/"?([a-z][a-z-]*)"?:\s*"([a-z]+)"/g)) fold[verb] = shape;
return fold;
}
/** The verb names one hand's contract declares. Parsed, never assumed. */
export function contractVerbs(apiTs: string): string[] {
if (!existsSync(apiTs)) return [];
const source = readFileSync(apiTs, "utf8");
const start = source.indexOf("verbs: {");
if (start < 0) return [];
const block = source.slice(start, start + 40_000);
return [...new Set([...block.matchAll(/^\s{4}"?([a-z][a-z0-9-]*)"?:\s*\{/gm)].map((m) => m[1]))];
}
/**
* The platform hands — the ones whose answers a person expects DRAWN.
*
* DERIVED THE WAY THE RUNNER DERIVES, never listed. `faceFor` asks two
* questions in order: does this verb's own word fold onto one of the
* manifest's SHAPES, and does the hand's name land on one of its FAMILIES. A
* hand with a verb that folds onto a shape is a hand the runner will TRY to
* draw — so it is exactly the set where a missing family becomes prose on a
* person's screen. `snappy-deploy push` folds onto nothing and is correctly
* absent; `snappy-skool posts` folds onto `list` and is correctly present, and
* an earlier credential-based guess missed it because its contract declares no
* `requires` at all ⟨measured 2026-09-09⟩.
*/
export function platformHands(skillsRoot: string, fold: Record<string, string>, shapes: readonly string[]): string[] {
const shapeWords = new Set(shapes);
const found: string[] = [];
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith("snappy-")) continue;
const api = join(skillsRoot, entry.name, "api.ts");
if (!existsSync(api)) continue;
const drawable = contractVerbs(api).some((verb) => shapeWords.has(verb) || shapeWords.has(fold[verb] ?? ""));
if (drawable) found.push(entry.name);
}
return found.sort();
}
/**
* THE HANDS THE FACE ROAD CANNOT EVEN SEE.
*
* `faceFor` needs TWO answers, and the census above only grades the second.
* A hand whose every verb folds onto NO shape never reaches the family
* question at all: the runner answers `"<verb>" folds onto none of
* snappy-faces' shapes … so which face this answer takes is not decidable
* from the contract`, and the person gets prose no matter which host they
* asked from and no matter how many families exist.
*
* `snappy-krisp` is the worked example ⟨measured 2026-09-09⟩: `fetch-meetings`,
* `fetch-document` and `fetch-action-items` fold onto nothing, so Krisp is
* invisible to the face road even though "meetings" is exactly the kind of
* thing a person asks to be SHOWN.
*
* Most rows here are legitimately faceless — `snappy-ffmpeg concat` has no
* picture of itself to draw — so this list is REPORTED and never counted. The
* fix, where there is one, is one word in the runner's own `VERB_SHAPE`, not a
* new table here.
*/
export function shapelessHands(skillsRoot: string, fold: Record<string, string>, shapes: readonly string[]): Array<{ skill: string; verbs: string[] }> {
const shapeWords = new Set(shapes);
const drawable = new Set(platformHands(skillsRoot, fold, shapes));
const out: Array<{ skill: string; verbs: string[] }> = [];
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith("snappy-") || drawable.has(entry.name)) continue;
const verbs = contractVerbs(join(skillsRoot, entry.name, "api.ts"));
if (verbs.length && !verbs.some((v) => shapeWords.has(v) || shapeWords.has(fold[v] ?? ""))) out.push({ skill: entry.name, verbs });
}
return out.sort((a, b) => a.skill.localeCompare(b.skill));
}
/** The manifest's own shape words, in its own order. */
export function publishedShapes(facesSkillDir: string): string[] {
const manifest = join(facesSkillDir, "library", "src", "manifest.ts");
if (!existsSync(manifest)) return [];
const source = readFileSync(manifest, "utf8");
const block = source.match(/FACE_MEMBER_ORDER[^=]*=\s*\[([^\]]*)\]/)?.[1] ?? "";
return [...block.matchAll(/"([a-z]+)"/g)].map((m) => m[1]);
}
/* ── THE NUMBER ────────────────────────────────────────────────────────────*/
export interface HostsNumber {
green: number;
total: number;
line: string;
}
/**
* ONE cell per (host, prompt), ONE per platform hand with no face family, and
* ONE per PUBLISHED family with no read that names it — because each is a real
* way the product fails a person and leaving any of them out of the denominator
* is how a matrix flatters itself.
*
* THE THIRD AXIS EXISTS BECAUSE THE SECOND COULD NOT SEE THE LIBRARY GROW
* ⟨2026-09-09⟩. Six families landed that day; the hand axis counted two of them
* and was blind to four, so a run could report a bigger library as no change at
* all. The denominator grows by exactly the families that exist.
*
* Green is `mounted`, and on the decision prompt green ALSO requires the press
* to have reached the runner: a card that draws and cannot be acted on is half
* the product, and half counted as whole is the status/artifact defect again.
*/
export function scoreCells(
cells: readonly HostCell[],
families: readonly FaceFamilyCell[],
reach: readonly FamilyReachCell[] = [],
): HostsNumber {
const pressNeeded = new Map(PROMPTS.map((p) => [p.id, p.press !== null]));
const green = cells.filter((cell) => {
if (cell.mount !== "mounted") return false;
return pressNeeded.get(cell.prompt) === true ? cell.press?.reached_runner === true : true;
}).length
+ families.filter((f) => f.has_family).length
+ reach.filter((f) => f.reachable).length;
const total = cells.length + families.length + reach.length;
return { green, total, line: `hosts: ${green}/${total} cells` };
}
/* ── THE RED CLUSTERS ──────────────────────────────────────────────────────
* The loop's law: a list of failures is a list; a list of MECHANISMS
* compounds. So the reds are grouped by the ONE thing that would have to
* change, and each group becomes a brief a lane can be handed whole.
*/
export interface RedCluster {
key: string;
headline: string;
mechanism: string;
cells: string[];
/** The one line the owner runs, when the fix is his and not a lane's. */
ownerCommand: string | null;
}
export function redClusters(file: Pick<HostsFile, "cells" | "face_families"> & Partial<Pick<HostsFile, "road" | "no_shape">>): RedCluster[] {
const out: RedCluster[] = [];
const roadMisses = (file.road ?? []).filter((r) => r.kind === null);
if (roadMisses.length) {
out.push({
key: "hosts-road-names-no-face",
headline: `${roadMisses.length} of the four asks never reach a named face on the runner itself`,
mechanism: `Before any host is blamed, the same ask was put to the runner's own MCP door. These came back with no face named, so no host on any row could have drawn them — the defect is under the hosts, not in them.`,
cells: roadMisses.map((r) => `${r.prompt}: ${r.hand} ${r.verb} → ${r.outcome}, ${r.note}`),
ownerCommand: null,
});
}
const slowFirstHop = (file.road ?? []).filter((r) => r.kind !== null && r.hops > 1);
if (slowFirstHop.length) {
out.push({
key: "hosts-first-call-refuses",
headline: `${slowFirstHop.length} ask(s) need a second call before a face exists`,
mechanism: `The person's own words do not name the contract's verb, so the FIRST call a host writes refuses and the face only appears on the retry. Every host pays that hop, and a host that does not retry never draws at all ⟨rules 15, 32, and B11: a first call that refuses is a defect of the projection⟩.`,
cells: slowFirstHop.map((r) => `${r.prompt}: ${r.hops} hop(s) — ${r.note}`),
ownerCommand: null,
});
}
const byHost = new Map<HostId, HostCell[]>();
for (const cell of file.cells) {
if (cell.mount === "mounted") continue;
byHost.set(cell.host, [...(byHost.get(cell.host) ?? []), cell]);
}
for (const [host, cells] of [...byHost.entries()].sort((a, b) => b[1].length - a[1].length)) {
const spec = HOSTS.find((h) => h.id === host)!;
const notDriven = cells.filter((c) => c.mount === "not-driven");
const key = notDriven.length === cells.length ? `hosts-not-driven-${host}` : `hosts-does-not-mount-${host}`;
out.push({
key,
headline: notDriven.length === cells.length
? `${spec.label} could not be driven at all`
: `${spec.label} does not mount ${cells.map((c) => c.prompt).join(", ")}`,
mechanism: notDriven.length === cells.length
? `Every ask to ${spec.label} is unreachable: ${notDriven[0].reason}. Until that is true the host is a blank column, and a blank column is not a passing one.`
: `${spec.label} received the ask and answered without a card element. Its surface is "${spec.surface}", so a face is possible there and is not arriving: ${cells.find((c) => c.mount === "text")?.reason}`,
cells: cells.map((c) => `${c.host}/${c.prompt}: ${c.mount} — ${c.reason}`),
ownerCommand: spec.ownerCommand,
});
}
const shapeless = (file.no_shape ?? []);
if (shapeless.length) {
out.push({
key: "hosts-verb-folds-onto-no-shape",
headline: `${shapeless.length} hand(s) have no verb the face road can even shape`,
mechanism: `The runner asks two questions in order — does this verb's word fold onto one of the manifest's shapes, and does the hand's name land on a family. These hands fail the FIRST one, so they never reach the family question and answer prose in every host however many families exist. Most are legitimately faceless (\`snappy-ffmpeg concat\` has no picture of itself). The ones that are not — \`snappy-krisp fetch-meetings\` is the worked example — are fixed by one word in the runner's own VERB_SHAPE fold, never by a new table.`,
cells: shapeless.map((h) => `${h.skill}: ${h.verbs.slice(0, 6).join(", ")}`),
ownerCommand: null,
});
}
const missing = file.face_families.missing.filter((f) => !f.has_family);
if (missing.length) {
out.push({
key: "hosts-no-face-family",
headline: `${missing.length} platform hand(s) name no face family`,
mechanism: `The runner derives a face family from the hand's own name (snappy-runner/src/face.ts#familyForHand) against snappy-faces' FACE_FAMILIES. These hands land on a family that does not exist, so their answers are prose in EVERY host — no host driver can fix them and no screenshot will ever exist for them. The fix is a face family in the faces library, one per platform, in that platform's own look.`,
cells: missing.map((f) => `${f.skill} → "${f.family}": ${f.reason}`),
ownerCommand: null,
});
}
const unreachable = (file.face_families.unreachable ?? []).filter((f) => !f.reachable);
if (unreachable.length) {
out.push({
key: "hosts-family-has-no-read",
headline: `${unreachable.length} published face family(ies) no read can reach`,
mechanism: `A family is only as true as the sentence that reaches it. The runner takes TWO roads from an answer to a family, in this order: the hand's own declaration — \`HAND_CONTRACT.verbs.<verb>.face: "<kind>"\` — and, for hands that declare nothing, the derivation \`familyForHand\` (the hand's name minus the prefix) with VERB_SHAPE folding the verb onto a slot. These families pass neither. They draw beautifully and nobody's words arrive at them. THE FIX IS ALMOST NEVER A FACE. It is one of three, and the cell below says which: (1) a hand already makes this thing and does not say so — it names the kind on the verb that draws it, and prints the rows that kind declares as an ADDITIVE fold, renaming nothing; (2) the hand's read prints text or the wrong shape, so the declaration would be a status over a missing artifact — the read gains a JSON answer first; (3) no hand makes this thing at all, which is an honest red that stays in the denominator until some hand does.`,
cells: unreachable.map((f) => `"${f.family}": ${f.reason}`),
ownerCommand: null,
});
}
return out;
}
/* ── THE DOOR THE NUMBER WAS COUNTED AGAINST ───────────────────────────────
* A NUMBER WITHOUT ITS RUNNER IS NOT A MEASUREMENT ⟨hosts-5's named gap,
* 2026-09-09⟩. The same battery, the same Mac, the same minute: against a
* local runner whose `search` names no verb the matrix read 1/20, and against
* the mini's it read 8/20 — while the SHIPPED bar had drawn the Gmail and
* Skool faces minutes earlier. The difference was the DOOR, and nothing in
* the corpus said which door, so the two numbers were comparable-looking and
* not comparable. Worse, a number attributed to a kernel head the runner is
* not serving is a status/artifact defect ⟨CLAUDE.md §10⟩: the state claims
* "this head scores 36" and the artifact that would have to exist — a runner
* on that head — does not.
*
* So the battery PROVES THE ROAD BEFORE IT COUNTS: `GET <door>/health` with
* the same bearer, which must answer a `revision`. That revision and that
* door ride in the corpus beside the number and print on the ledger, and a
* run that cannot prove them writes NO corpus at all — an unattributed number
* is worth less than no number, because it will be compared to the next one.
*/
export interface RunnerProof {
/** The MCP door every ask on this run was put to. */
mcp_door: string;
/** The door the proof was read from — the MCP door without its `/mcp`. */
health_door: string;
/** What `/health` answered. The number is counted against THIS. */
revision: string;
/** Hands the runner was holding when it answered. */
hands: number | null;
/** The kernel head this worktree is on, when it could be read. */
kernel_head: string | null;
/**
* Does the runner's revision name the kernel head the number is being
* attributed to? False is not a refusal — it is a fact the ledger prints,
* so nobody reads this run's number as a verdict on their own tree.
*/
attributed: boolean;
proved_at: string;
}
/** The health door beside an MCP door. `…/runner/mcp` → `…/runner/health`. */
export function healthDoorFor(mcpUrl: string): string {
return `${mcpUrl.replace(/\/+$/u, "").replace(/\/mcp$/u, "")}/health`;
}
/**
* The runner stamps its revision `<kernel>-<body>-<faces>`; only the first
* segment is the kernel commit, and it is abbreviated. A head "matches" when
* one is a prefix of the other, in whichever direction the abbreviation ran.
*/
export function attributedTo(revision: string, kernelHead: string | null): boolean {
if (kernelHead === null) return false;
const stamped = revision.split("-")[0] ?? "";
if (stamped.length < 7) return false;
return kernelHead.startsWith(stamped) || stamped.startsWith(kernelHead);
}
/* ── THE FILE ──────────────────────────────────────────────────────────────*/
export interface HostDriverStatus {
host: HostId;
label: string;
road: string;
surface: CardSurface;
driven: boolean;
reason: string;
ownerCommand: string | null;
}
export interface HostsFile {
schema: typeof HOSTS_SCHEMA;
generated_at: string;
/** The door and revision this run's number was counted against. */
runner: RunnerProof;
prompts: readonly PromptSpec[];
drivers: HostDriverStatus[];
cells: HostCell[];
road: RoadCell[];
face_families: { published: string[]; missing: FaceFamilyCell[]; unreachable: FamilyReachCell[] };
/** Hands the face road never reaches, reported and deliberately not counted. */
no_shape: Array<{ skill: string; verbs: string[] }>;
clusters: RedCluster[];
number: HostsNumber;
}
/** The matrix as a person reads it — the block `loop`'s LEDGER carries. */
export function hostsLedgerBlock(file: HostsFile): string {
const cell = (host: HostId, prompt: string): string => {
const found = file.cells.find((c) => c.host === host && c.prompt === prompt);
if (!found) return "—";
if (found.mount === "mounted") {
// A cell this run did not time is a cell this run did not draw. Say which.
if (found.standing !== null) return `**mounted** · standing #${found.standing}`;
return `**mounted** ${found.ms ?? "?"} ms`;
}
if (found.mount === "text") return "text";
return "not driven";
};
const header = `| host | ${PROMPTS.map((p) => p.id).join(" | ")} |`;
const rule = `|---|${PROMPTS.map(() => "---").join("|")}|`;
const rows = HOSTS.map((h) => `| ${h.label} | ${PROMPTS.map((p) => cell(h.id, p.id)).join(" | ")} |`);
const reds = file.clusters.map((c, i) => `| ${i + 1} | \`${c.key}\` | ${c.cells.length} | ${c.headline} |`);
const tick = "`";
const reachRows = (file.face_families.unreachable ?? []).map((f) => {
const read = f.read === null ? "**none**" : `${tick}${f.read}${tick}`;
return `| ${tick}${f.family}${tick} | ${read} | ${f.reachable ? "reached" : f.reason} |`;
});
const owner = file.drivers.filter((d) => !d.driven && d.ownerCommand)
.map((d) => `- **${d.label}** — ${d.reason}\n \`\`\`bash\n ${d.ownerCommand}\n \`\`\``);
// THE NUMBER AND ITS DOOR TRAVEL TOGETHER. Two runs of this battery against
// two runners are two different measurements, and printing the number alone
// invites the second to be read as progress over the first.
const proof = file.runner;
const attribution = proof.attributed
? `, which is the kernel head this run was cut from`
: `, which is NOT this worktree's head (${proof.kernel_head ?? "unknown"}) — the number is the runner's, not the tree's`;
return `## hosts — the same four asks, every host, counted
Generated ${file.generated_at} by \`api.ts hosts\`.
**${file.number.line}** — counted against \`${proof.revision}\` through \`${proof.mcp_door}\`${attribution}. ${proof.hands ?? "?"} hands, proved at ${proof.proved_at} on \`${proof.health_door}\`.
${header}
${rule}
${rows.join("\n")}
Green is a CARD ELEMENT in that host's own DOM; on \`linkedin-decide\` green also
requires the press to have reached the runner. A host that could not be driven
is a named red cell, never a blank one.
### the road under the cells — what the runner itself answers
The same asks, put to the runner's own MCP door, so a red cell can be READ: a
face the runner never named could not have been drawn by any host on any row.
Not a host and deliberately NOT in the number.
| ask | hand · verb | outcome | face named | ms | hops |
|---|---|---|---|---:|---:|
${file.road.map((r) => `| ${r.prompt} | \`${r.hand} ${r.verb}\` | ${r.outcome} | ${r.kind ?? "**none**"} | ${r.ms ?? "—"} | ${r.hops} |`).join("\n")}
### the families, and the read that reaches each
Every family \`snappy-faces\` publishes gets a cell, and its green is A READ THAT
NAMES IT — a hand the runner's own \`familyForHand\` routes there, holding a verb
that folds onto a shape. A family with no read draws for nobody, and that is
counted, because a library that grows without reads grows the picture and not
the product.
| family | the read that names it | |
|---|---|---|
${reachRows.join("\n")}
### red clusters
| # | cluster | cells | what has to change |
|---:|---|---:|---|
${reds.join("\n")}
${owner.length ? `### what only the owner can run\n\n${owner.join("\n")}\n` : ""}`;
}
/** One brief per red cluster, in the shape `loop` already hands a lane. */
export function hostsBrief(cluster: RedCluster): string {
return `# Lane brief — ${cluster.key}
**${cluster.headline}** · ${cluster.cells.length} cell(s)
## Role and prohibitions
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
- NEVER install on any Mac, deploy, or push.
- NEVER send, post, spend or delete through any hand — a press is \`Later\`, never \`Publish\`.
- NEVER export or commit the owner's browser state.
- \`git commit --only <explicit paths>\`; never \`git add -A\`; never \`git stash\`.
- nice -n 19 every compile; no \`tsc --watch\`.
## The mechanism
${cluster.mechanism}
## The measured cells
\`\`\`
${cluster.cells.join("\n")}
\`\`\`
${cluster.ownerCommand ? `## The one line the owner runs first\n\n\`\`\`bash\n${cluster.ownerCommand}\n\`\`\`\n\nUntil that has been run this cluster cannot be closed by a lane, and reporting\nit closed is the status/artifact defect ⟨CLAUDE.md §10⟩.\n` : ""}
## The anti-fix — what would look like progress and is not
- Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote.
- Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself.
- Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against.
## The proof required
1. \`npx tsx skills/snappy-tool-design/api.ts hosts\` before and after, with this cluster's cell count falling.
2. The screenshot path for every cell that turned green, under \`${SHOT_ROOT}\`.
3. \`SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh\` still passing.
4. The whole matrix pasted, not the one row — no other cell may fall.
`;
}
/**
* THE HOSTS BATTERY — the same four asks, put to every host, counted.
*
* The owner, 2026-09-09 01:3x ET, on one green cell: "this is barely scraping
* it — it has to truly be seamless and work every six ways from here to Sunday
* and back, for LinkedIn, for Skool, for Statechange, for everything under
* God's sky."
*
* One cell had been proven: in the ChatGPT app, signed in as him, "Using
* Snappy Skills, show me my Gmail inbox, three messages" MOUNTED the Gmail
* card. One cell is not a matrix, and a matrix nobody counts is a feeling.
* So this module is the MATRIX and the NUMBER: host × prompt, each cell
* `mounted` / `text` / `not-driven`, and one line at the end.
*
* WHY IT LIVES INSIDE THE LOOP AND NOT BESIDE IT ⟨CLAUDE.md §4, duplicate
* roads are banned⟩. `probe` measures the wire, `render` measures ONE host
* this repo wrote itself, `lint` grades the source. A face that draws
* perfectly in the loop's own AppBridge host and does not appear in ChatGPT
* is a defect none of the three can see, because none of them ever asked a
* host somebody else wrote. `hosts` is that third measurement, and it is a
* verb on the SAME api, writing into the SAME ledger, ranked by the SAME
* clusterer — not a second loop with a second ledger that stops agreeing.
*
* THE TWO LAWS THIS FILE IS WRITTEN AGAINST:
*
* A STATUS IS ONLY AS TRUE AS THE ARTIFACT IT IMPLIES ⟨CLAUDE.md §10⟩. A
* host is `mounted` only when a CARD ELEMENT was found in that host's own
* DOM — an iframe, an app frame, a face root. A host that answered in prose
* about the inbox is `text`, and it is RED, because the product's whole
* claim is the drawing.
*
* NEVER A GREEN BY ABSENCE. A host that could not be driven is a named
* `not-driven` cell carrying the reason and the ONE LINE the owner runs to
* make it drivable — never omitted, never counted as passing, never
* silently reduced out of the denominator.
*
* SHAPES, NEVER VALUES. Nothing here records a subject line, a sender, a
* message body or a phone number: a cell holds booleans, milliseconds, a face
* KIND string, and a path to a screenshot that lives OUTSIDE the repo.
* `hosts.test.ts` guards the committed file the same way `loop.test.ts`
* guards the probe corpus.
*/
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
export const HOSTS_FILE = "tool-design-hosts.json";
export const HOSTS_SCHEMA = "snappy-tool-design/hosts@1";
/** Screenshots and driver logs live OUTSIDE the repo — the owner's glass is
* not a commit. `hosts.test.ts` asserts no cell path points inside it. */
export const SHOT_ROOT = "/tmp/hosts-battery";
/* ── THE PROMPTS ───────────────────────────────────────────────────────────
* The same four asks for every host, because the matrix only means something
* when the row and the column vary and the QUESTION does not. Three reads and
* one decision: the decision is the one that carries a press, and the press is
* the axis nothing before this measured at all.
*/
export interface PromptSpec {
id: string;
/** Word for word, what a person types. */
text: string;
/** The hand this ask should reach, so a miss can be named against a road. */
hand: string;
/** The face family the answer should take. */
family: string;
/** LATER on a decision face. `null` on a read — there is nothing to press. */
press: string | null;
}
export const PROMPTS: readonly PromptSpec[] = [
{ id: "gmail-inbox", text: "Using Snappy Skills, show me my Gmail inbox, three messages", hand: "snappy-gmail", family: "gmail", press: null },
{ id: "skool-feed", text: "Using Snappy Skills, show me my Skool feed", hand: "snappy-skool", family: "skool", press: null },
{ id: "linkedin-profile", text: "Using Snappy Skills, show me my LinkedIn profile", hand: "snappy-linkedin", family: "linkedin", press: null },
// NEVER "Publish". LATER is the only word this battery presses: the press
// must prove the ROAD from the host back to the runner, and proving it with
// a send would post to a person to measure a wire ⟨CLAUDE.md §6⟩.
{ id: "linkedin-decide", text: "Using Snappy Skills, draft a LinkedIn post about launch checklists and let me decide", hand: "snappy-linkedin", family: "linkedin", press: "Later" },
] as const;
/* ── THE HOSTS ─────────────────────────────────────────────────────────────
* A host is a place a person actually asks from. `surface` is what the host
* can even do with a card, and it is the difference between a defect and a
* fact: a terminal that answers in prose is not broken, it has no card
* surface — so its cells are red for a REASON that names the host's own
* ceiling, and the brief that comes out of it is about the host, not the face.
*/
export type HostId = "chatgpt" | "claude" | "codex" | "menubar" | "telegram";
export type CardSurface = "widget" | "webview" | "image" | "none";
export interface HostSpec {
id: HostId;
label: string;
/** How this host is reached at all. */
road: string;
surface: CardSurface;
/** The ONE line the owner runs to make this host drivable, when it is not. */
ownerCommand: string | null;
}
export const HOSTS: readonly HostSpec[] = [
{ id: "chatgpt", label: "ChatGPT web app", road: "agent-browser --state chatgpt → https://chatgpt.com", surface: "widget", ownerCommand: `agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt` },
{ id: "claude", label: "Claude.ai web app", road: "agent-browser --state claude → https://claude.ai", surface: "widget", ownerCommand: `agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude` },
// The codex road is CONFIGURED and logged in (`codex mcp list` shows
// snappy-skills enabled, Auth OAuth); what stops it is the account's own
// credit window, which is his to open and nobody else's.
{ id: "codex", label: "Codex CLI", road: "codex exec --json (mcp_servers.snappy-skills)", surface: "none", ownerCommand: `open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>` },
{ id: "menubar", label: "Snappy menu bar", road: "SnappyBar popover WKWebView over its local runner", surface: "webview", ownerCommand: `cd ~/Projects/snappy-runner/apps/snappy-bar && ./scripts/make-app.sh && open dist/Snappy.app # then: npx tsx api.ts hosts --only menubar --token-file <file>` },
{ id: "telegram", label: "Telegram on the phone", road: "snappy-telegram show (PNG) + decisions", surface: "image", ownerCommand: null },
] as const;
/* ── A CELL ────────────────────────────────────────────────────────────────*/
/** What the host did with the answer. `mounted` is the only green. */
export type Mount = "mounted" | "text" | "not-driven";
export interface PressRecord {
word: string;
/** The mini's calls.jsonl gained an `execute` for the hand within the window. */
reached_runner: boolean;
within_ms: number | null;
/** How the press was looked for, so a false can be read. */
how: string;
}
export interface HostCell {
host: HostId;
prompt: string;
mount: Mount;
/** Why this cell is what it is. Required on every non-`mounted` cell. */
reason: string;
/** Time from the ask to the card appearing. `null` when it never appeared. */
ms: number | null;
/** The face kind the tool result named, when the host let it be seen. */
kind: string | null;
/** A path under SHOT_ROOT — outside the repo, never committed. */
shot: string | null;
press: PressRecord | null;
/**
* THE ARTIFACT THIS CELL IS STANDING ON, when it is not one this run made.
* A card carried by `--already` is green on a photo a PREVIOUS run put in
* the chat, and it has no milliseconds because nothing was timed. Written
* `**mounted** ? ms`, that cell read on the owner's glass exactly like a
* card this run drew — the status/artifact defect in miniature. So the
* message id rides the cell and the ledger prints it instead.
*/
standing: number | null;
}
/**
* THE ONE RECORDER. Every driver hands its observation here and nothing else
* decides a cell's colour, because two deciders would disagree the first time
* either moved.
*
* A card element present is `mounted`. A host that answered without one is
* `text`. A host that never got the ask is `not-driven` — and a not-driven
* observation that carries no reason is a bug in the DRIVER, so it throws
* rather than writing a cell nobody can read.
*/
export interface Observation {
host: HostId;
prompt: string;
/** Did a card element exist in the host's own DOM / output? */
cardElement?: boolean;
/** Did the host answer at all? */
answered?: boolean;
driven?: boolean;
reason?: string;
ms?: number | null;
kind?: string | null;
shot?: string | null;
press?: PressRecord | null;
/** A prior run's card this observation stands on, by message id. */
standing?: number | null;
}
export function recordCell(seen: Observation): HostCell {
const base = { host: seen.host, prompt: seen.prompt, ms: seen.ms ?? null, kind: seen.kind ?? null, shot: seen.shot ?? null, press: seen.press ?? null, standing: seen.standing ?? null };
if (seen.driven === false) {
if (!seen.reason) throw new Error(`not-driven cell ${seen.host}/${seen.prompt} carries no reason — a red cell must name what would make it drivable`);
return { ...base, mount: "not-driven", reason: seen.reason, ms: null };
}
if (seen.cardElement === true) {
return { ...base, mount: "mounted", reason: seen.reason ?? "a card element was present in the host's own DOM" };
}
return {
...base,
mount: "text",
reason: seen.reason ?? (seen.answered === false
? "the host produced no answer before the driver's deadline"
: "the host answered without a card element — text only"),
ms: null,
};
}
/* ── THE ROAD UNDER THE CELL ───────────────────────────────────────────────
* A red cell says a host did not draw. It does not say WHY, and the two whys
* are different lanes: the runner never named a face, or the runner named one
* and the host ignored it. So every prompt is also put to the runner directly
* — the same MCP door the host would have reached — and the answer recorded.
*
* This is deliberately NOT a host cell and NOT in the number. Driving our own
* door and counting it green is precisely the anti-fix the briefs warn about.
* It is the READING of a red cell, nothing more.
*/
export interface RoadCell {
prompt: string;
hand: string;
verb: string;
outcome: string;
/** The face the runner named, if it named one. */
kind: string | null;
family: string | null;
resource_uri: string | null;
ms: number | null;
/** How many calls it took a caller to get here from the person's own words. */
hops: number;
note: string;
}
/* ── THE FACE-FAMILY CENSUS ────────────────────────────────────────────────
* A red cell that no host can fix: the hand answers and NO FACE FAMILY EXISTS
* for it, so every host on every row draws prose by construction.
*
* The derivation is the RUNNER'S OWN, read from its source of truth rather
* than restated: `snappy-runner/src/face.ts#familyForHand` is the hand's name
* minus the collection prefix, and the families are `snappy-faces`'
* `FACE_FAMILIES`. Copying either would put a third vocabulary in the world.
*/
export interface FaceFamilyCell {
skill: string;
family: string;
has_family: boolean;
reason: string;
}
/** `snappy-gmail` → `gmail`. The runner's rule, restated in one place. */
export function familyForHand(name: string): string {
return name.startsWith("snappy-") ? name.slice("snappy-".length) : name;
}
/**
* Which hands answer with no face at all.
*
* `hands` is the set of skills whose answers a person would expect DRAWN — a
* platform read. It is passed in rather than guessed here so the caller can
* derive it from the contracts it already holds.
*/
export function faceFamilyCensus(hands: readonly string[], families: readonly string[]): FaceFamilyCell[] {
const known = new Set(families);
return [...hands].sort().map((skill) => {
const family = familyForHand(skill);
const has_family = known.has(family);
return {
skill,
family,
has_family,
reason: has_family
? `snappy-faces declares the "${family}" family`
: `snappy-faces declares no "${family}" family, so every host draws ${skill}'s answers as prose — a face to build, not a host to fix`,
};
});
}
/* ── THE FAMILY-REACH CENSUS ───────────────────────────────────────────────
* THE SAME QUESTION FROM THE OTHER END, AND IT IS NOT THE SAME CELL.
*
* `faceFamilyCensus` walks the HANDS and asks whether a face exists for each.
* That axis can only see families some hand already lands on, so the six
* families the library gained on 2026-09-09 — krisp, knowledge, transcripts,
* imageset, walkthrough, doc — entered the number as TWO cells (knowledge,
* transcripts) and four invisible ones. A face nobody's words can reach is a
* real way the product fails a person, and a denominator that cannot see it
* flatters ⟨CLAUDE.md §10⟩.
*
* So every PUBLISHED family gets a cell, and its green is A READ THAT NAMES
* IT — asked in the runner's own two roads, in the runner's own order:
*
* 1. THE HAND DECLARED THE FACE. `verbs.<verb>.face` names a kind and the
* build report says which family that kind belongs to. This is how a hand
* reaches a family it is not named after, and it is the road the runner
* takes first (`face.ts` step 2, above its own derivation).
* 2. THE NAME ROUTE. `familyForHand` inverted — `snappy-<family>` — holding a
* verb that folds onto one of the manifest's shapes through the runner's
* `VERB_SHAPE`. Read out of the runner's source, never restated: an alias
* table here saying "docs means doc" would be a third vocabulary and would
* report a reach the runner does not have.
*
* MEASURED 2026-09-09 on kernel a5a2380, BEFORE the declarations: 15/31. The
* sixteen red split two ways and the fix differed. `krisp`, `thumbnails`,
* `walkthrough` and `deploy` had the hand and no shapeable verb; `imageset`,
* `doc`, `data`, `code`, `agent`, `schedule` and the rest had no hand of that
* name at all, though the manifest's own notes say which hands they were built
* for. Both are now the SAME fix — the hand says which face its read draws —
* and a family still red after it is one no hand makes yet, which is the
* honest answer and stays in the denominator.
*/
export interface FamilyReachCell {
family: string;
/** `<hand> <verb> → <shape>` — the read a person's words land on. */
read: string | null;
reachable: boolean;
reason: string;
}
/** The hand the runner's own rule routes to this family: `familyForHand`
* inverted, which is exactly `snappy-<family>` and nothing else. */
export function handForFamily(family: string): string {
return `snappy-${family}`;
}
export function familyReachCensus(
skillsRoot: string,
families: readonly string[],
fold: Record<string, string>,
shapes: readonly string[],
kindFamilies: Record<string, string>,
): FamilyReachCell[] {
const shapeWords = new Set(shapes);
const declared = declaredFaceReads(skillsRoot, kindFamilies);
return [...families].sort().map((family) => {
// 1. A HAND DECLARED IT. `verbs.<verb>.face` names a kind, and the build
// report says which family that kind belongs to — so a hand reaches a
// family it is not NAMED after, which no rule made out of the hand's
// name can do. This is checked first because the hand is the source and
// the name route below is the derivation ⟨2026-09-09⟩.
const said = declared.find((d) => d.family === family);
if (said !== undefined) {
return {
family,
read: `${said.hand} ${said.verb} → ${said.kind}`,
reachable: true,
reason: `${said.hand}'s contract declares "${said.verb}" draws ${said.kind}, which the build report files under "${family}" — the hand named the face and nothing was derived over it`,
};
}
const hand = handForFamily(family);
const api = join(skillsRoot, hand, "api.ts");
if (!existsSync(api)) {
return {
family,
read: null,
reachable: false,
reason: `no hand named ${hand} exists and no hand's contract declares a "${family}" face on any verb — the two roads the runner takes — so no sentence a person types can land on the "${family}" faces, however well they draw. `
+ `If a hand already makes this thing, it says so with \`verbs.<verb>.face\`; if none does, this family is made by no hand yet and that is the honest red`,
};
}
const verbs = contractVerbs(api);
const drawable = verbs.filter((verb) => shapeWords.has(verb) || shapeWords.has(fold[verb] ?? ""));
if (drawable.length === 0) {
return {
family,
read: null,
reachable: false,
reason: `${hand} exists, declares no \`face\` on any verb, and none of its verbs (${verbs.join(", ") || "none declared"}) folds onto a shape the manifest publishes — so the runner cannot decide which "${family}" face an answer takes. `
+ `The fix is the hand naming the kind on the verb that draws it, not a face to build`,
};
}
const verb = drawable[0];
return {
family,
read: `${hand} ${verb} → ${shapeWords.has(verb) ? verb : fold[verb]}`,
reachable: true,
reason: `${hand} ${verb} folds onto ${shapeWords.has(verb) ? verb : fold[verb]}, so a person's read reaches the "${family}" family`,
};
});
}
/* ── THE JOIN THE HAND DECLARES ⟨2026-09-09⟩ ────────────────────────────────
*
* The census above began as one question — does `snappy-<family>` exist and
* does one of its verbs fold onto a shape — and that question can only ever
* find a family a hand is NAMED after. `snappy-thumbnails` draws the
* `imageset` faces; `snappy-docs` draws `doc-card`; `snappy-agent-host` draws
* `agent-run`. Sixteen of thirty-one families were red on kernel a5a2380 and
* half of them for that reason alone.
*
* So the hand declares the kind on its verb (`verbs.<verb>.face`), the runner
* reads that declaration before its own derivation, and this census counts it.
* ONE mechanism in three places, each reading the same words — never a table
* here saying "thumbnails means imageset", which would be the third vocabulary
* ⟨CLAUDE.md §4⟩ and would be wrong the first week.
*/
export interface DeclaredFaceRead { hand: string; verb: string; kind: string; family: string }
/** Every `verbs.<verb>.face` a hand's contract declares, with the family the
* build report files that kind under. PARSED, never spawned: this census runs
* in the middle of the hosts battery and cannot start 98 child processes. */
export function declaredFaceReads(skillsRoot: string, kindFamilies: Record<string, string>): DeclaredFaceRead[] {
const out: DeclaredFaceRead[] = [];
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith("snappy-")) continue;
for (const [verb, kind] of Object.entries(contractFaces(join(skillsRoot, entry.name, "api.ts")))) {
const family = kindFamilies[kind];
if (family === undefined) continue;
out.push({ hand: entry.name, verb, kind, family });
}
}
return out.sort((a, b) => a.hand.localeCompare(b.hand) || a.verb.localeCompare(b.verb));
}
/** One hand's declared faces, verb → kind. The verb blocks are sliced at the
* same four-space `"verb": {` boundary `contractVerbs` finds them at, so a
* `face` word can never be read onto the verb above or below it. */
export function contractFaces(apiTs: string): Record<string, string> {
if (!existsSync(apiTs)) return {};
const source = readFileSync(apiTs, "utf8");
const start = source.indexOf("verbs: {");
if (start < 0) return {};
const block = source.slice(start, start + 40_000);
const heads = [...block.matchAll(/^\s{4}"?([a-z][a-z0-9-]*)"?:\s*\{/gm)];
const faces: Record<string, string> = {};
for (let i = 0; i < heads.length; i += 1) {
const from = heads[i]!.index!;
const to = i + 1 < heads.length ? heads[i + 1]!.index! : block.length;
const face = block.slice(from, to).match(/\bface:\s*"([a-z0-9-]+)"/);
if (face !== null) faces[heads[i]![1]!] = face[1]!;
}
return faces;
}
/** Kind → family, from the faces skill's OWN build report — the same file the
* runner reads for the same answer. Never split off the kind's first hyphen:
* `linkedin-post-published` and `media-google-doc` would both come out wrong,
* and a family map that is wrong for two rows is a census that lies twice. */
export function publishedKindFamilies(facesSkillDir: string): Record<string, string> {
const report = join(facesSkillDir, "dist", "build-report.json");
if (!existsSync(report)) return {};
try {
const draws = (JSON.parse(readFileSync(report, "utf8")) as { draws?: unknown }).draws;
if (!Array.isArray(draws)) return {};
const out: Record<string, string> = {};
for (const d of draws as { kind?: unknown; family?: unknown }[]) {
if (typeof d?.kind === "string" && typeof d.family === "string") out[d.kind] = d.family;
}
return out;
} catch { return {}; }
}
/** The families snappy-faces publishes, read from its manifest at call time. */
export function publishedFamilies(facesSkillDir: string): string[] {
const manifest = join(facesSkillDir, "library", "src", "manifest.ts");
if (!existsSync(manifest)) return [];
const source = readFileSync(manifest, "utf8");
const block = source.slice(source.indexOf("FACE_FAMILIES"));
return [...new Set([...block.matchAll(/slug:\s*"([a-z0-9-]+)"/g)].map((m) => m[1]))];
}
/**
* THE VERB→SHAPE FOLD, read from the RUNNER rather than restated.
*
* `snappy-runner/src/face.ts` holds `VERB_SHAPE`: the words hands actually use
* ("feed", "posts", "recent", "me") folded onto the manifest's closed shape
* vocabulary. Copying that table here would be a THIRD vocabulary for one idea
* ⟨CLAUDE.md §4⟩, and the copy would be wrong the first week. So it is parsed
* out of the runner's own source at call time. When the runner is not on this
* machine the fold falls back to the shape words themselves — a smaller,
* honest answer, never a guessed bigger one.
*/
export function verbShapeFold(runnerFaceTs: string): Record<string, string> {
if (!existsSync(runnerFaceTs)) return {};
const source = readFileSync(runnerFaceTs, "utf8");
const start = source.indexOf("const VERB_SHAPE");
if (start < 0) return {};
const block = source.slice(start, source.indexOf("};", start));
const fold: Record<string, string> = {};
for (const [, verb, shape] of block.matchAll(/"?([a-z][a-z-]*)"?:\s*"([a-z]+)"/g)) fold[verb] = shape;
return fold;
}
/** The verb names one hand's contract declares. Parsed, never assumed. */
export function contractVerbs(apiTs: string): string[] {
if (!existsSync(apiTs)) return [];
const source = readFileSync(apiTs, "utf8");
const start = source.indexOf("verbs: {");
if (start < 0) return [];
const block = source.slice(start, start + 40_000);
return [...new Set([...block.matchAll(/^\s{4}"?([a-z][a-z0-9-]*)"?:\s*\{/gm)].map((m) => m[1]))];
}
/**
* The platform hands — the ones whose answers a person expects DRAWN.
*
* DERIVED THE WAY THE RUNNER DERIVES, never listed. `faceFor` asks two
* questions in order: does this verb's own word fold onto one of the
* manifest's SHAPES, and does the hand's name land on one of its FAMILIES. A
* hand with a verb that folds onto a shape is a hand the runner will TRY to
* draw — so it is exactly the set where a missing family becomes prose on a
* person's screen. `snappy-deploy push` folds onto nothing and is correctly
* absent; `snappy-skool posts` folds onto `list` and is correctly present, and
* an earlier credential-based guess missed it because its contract declares no
* `requires` at all ⟨measured 2026-09-09⟩.
*/
export function platformHands(skillsRoot: string, fold: Record<string, string>, shapes: readonly string[]): string[] {
const shapeWords = new Set(shapes);
const found: string[] = [];
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith("snappy-")) continue;
const api = join(skillsRoot, entry.name, "api.ts");
if (!existsSync(api)) continue;
const drawable = contractVerbs(api).some((verb) => shapeWords.has(verb) || shapeWords.has(fold[verb] ?? ""));
if (drawable) found.push(entry.name);
}
return found.sort();
}
/**
* THE HANDS THE FACE ROAD CANNOT EVEN SEE.
*
* `faceFor` needs TWO answers, and the census above only grades the second.
* A hand whose every verb folds onto NO shape never reaches the family
* question at all: the runner answers `"<verb>" folds onto none of
* snappy-faces' shapes … so which face this answer takes is not decidable
* from the contract`, and the person gets prose no matter which host they
* asked from and no matter how many families exist.
*
* `snappy-krisp` is the worked example ⟨measured 2026-09-09⟩: `fetch-meetings`,
* `fetch-document` and `fetch-action-items` fold onto nothing, so Krisp is
* invisible to the face road even though "meetings" is exactly the kind of
* thing a person asks to be SHOWN.
*
* Most rows here are legitimately faceless — `snappy-ffmpeg concat` has no
* picture of itself to draw — so this list is REPORTED and never counted. The
* fix, where there is one, is one word in the runner's own `VERB_SHAPE`, not a
* new table here.
*/
export function shapelessHands(skillsRoot: string, fold: Record<string, string>, shapes: readonly string[]): Array<{ skill: string; verbs: string[] }> {
const shapeWords = new Set(shapes);
const drawable = new Set(platformHands(skillsRoot, fold, shapes));
const out: Array<{ skill: string; verbs: string[] }> = [];
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith("snappy-") || drawable.has(entry.name)) continue;
const verbs = contractVerbs(join(skillsRoot, entry.name, "api.ts"));
if (verbs.length && !verbs.some((v) => shapeWords.has(v) || shapeWords.has(fold[v] ?? ""))) out.push({ skill: entry.name, verbs });
}
return out.sort((a, b) => a.skill.localeCompare(b.skill));
}
/** The manifest's own shape words, in its own order. */
export function publishedShapes(facesSkillDir: string): string[] {
const manifest = join(facesSkillDir, "library", "src", "manifest.ts");
if (!existsSync(manifest)) return [];
const source = readFileSync(manifest, "utf8");
const block = source.match(/FACE_MEMBER_ORDER[^=]*=\s*\[([^\]]*)\]/)?.[1] ?? "";
return [...block.matchAll(/"([a-z]+)"/g)].map((m) => m[1]);
}
/* ── THE NUMBER ────────────────────────────────────────────────────────────*/
export interface HostsNumber {
green: number;
total: number;
line: string;
}
/**
* ONE cell per (host, prompt), ONE per platform hand with no face family, and
* ONE per PUBLISHED family with no read that names it — because each is a real
* way the product fails a person and leaving any of them out of the denominator
* is how a matrix flatters itself.
*
* THE THIRD AXIS EXISTS BECAUSE THE SECOND COULD NOT SEE THE LIBRARY GROW
* ⟨2026-09-09⟩. Six families landed that day; the hand axis counted two of them
* and was blind to four, so a run could report a bigger library as no change at
* all. The denominator grows by exactly the families that exist.
*
* Green is `mounted`, and on the decision prompt green ALSO requires the press
* to have reached the runner: a card that draws and cannot be acted on is half
* the product, and half counted as whole is the status/artifact defect again.
*/
export function scoreCells(
cells: readonly HostCell[],
families: readonly FaceFamilyCell[],
reach: readonly FamilyReachCell[] = [],
): HostsNumber {
const pressNeeded = new Map(PROMPTS.map((p) => [p.id, p.press !== null]));
const green = cells.filter((cell) => {
if (cell.mount !== "mounted") return false;
return pressNeeded.get(cell.prompt) === true ? cell.press?.reached_runner === true : true;
}).length
+ families.filter((f) => f.has_family).length
+ reach.filter((f) => f.reachable).length;
const total = cells.length + families.length + reach.length;
return { green, total, line: `hosts: ${green}/${total} cells` };
}
/* ── THE RED CLUSTERS ──────────────────────────────────────────────────────
* The loop's law: a list of failures is a list; a list of MECHANISMS
* compounds. So the reds are grouped by the ONE thing that would have to
* change, and each group becomes a brief a lane can be handed whole.
*/
export interface RedCluster {
key: string;
headline: string;
mechanism: string;
cells: string[];
/** The one line the owner runs, when the fix is his and not a lane's. */
ownerCommand: string | null;
}
export function redClusters(file: Pick<HostsFile, "cells" | "face_families"> & Partial<Pick<HostsFile, "road" | "no_shape">>): RedCluster[] {
const out: RedCluster[] = [];
const roadMisses = (file.road ?? []).filter((r) => r.kind === null);
if (roadMisses.length) {
out.push({
key: "hosts-road-names-no-face",
headline: `${roadMisses.length} of the four asks never reach a named face on the runner itself`,
mechanism: `Before any host is blamed, the same ask was put to the runner's own MCP door. These came back with no face named, so no host on any row could have drawn them — the defect is under the hosts, not in them.`,
cells: roadMisses.map((r) => `${r.prompt}: ${r.hand} ${r.verb} → ${r.outcome}, ${r.note}`),
ownerCommand: null,
});
}
const slowFirstHop = (file.road ?? []).filter((r) => r.kind !== null && r.hops > 1);
if (slowFirstHop.length) {
out.push({
key: "hosts-first-call-refuses",
headline: `${slowFirstHop.length} ask(s) need a second call before a face exists`,
mechanism: `The person's own words do not name the contract's verb, so the FIRST call a host writes refuses and the face only appears on the retry. Every host pays that hop, and a host that does not retry never draws at all ⟨rules 15, 32, and B11: a first call that refuses is a defect of the projection⟩.`,
cells: slowFirstHop.map((r) => `${r.prompt}: ${r.hops} hop(s) — ${r.note}`),
ownerCommand: null,
});
}
const byHost = new Map<HostId, HostCell[]>();
for (const cell of file.cells) {
if (cell.mount === "mounted") continue;
byHost.set(cell.host, [...(byHost.get(cell.host) ?? []), cell]);
}
for (const [host, cells] of [...byHost.entries()].sort((a, b) => b[1].length - a[1].length)) {
const spec = HOSTS.find((h) => h.id === host)!;
const notDriven = cells.filter((c) => c.mount === "not-driven");
const key = notDriven.length === cells.length ? `hosts-not-driven-${host}` : `hosts-does-not-mount-${host}`;
out.push({
key,
headline: notDriven.length === cells.length
? `${spec.label} could not be driven at all`
: `${spec.label} does not mount ${cells.map((c) => c.prompt).join(", ")}`,
mechanism: notDriven.length === cells.length
? `Every ask to ${spec.label} is unreachable: ${notDriven[0].reason}. Until that is true the host is a blank column, and a blank column is not a passing one.`
: `${spec.label} received the ask and answered without a card element. Its surface is "${spec.surface}", so a face is possible there and is not arriving: ${cells.find((c) => c.mount === "text")?.reason}`,
cells: cells.map((c) => `${c.host}/${c.prompt}: ${c.mount} — ${c.reason}`),
ownerCommand: spec.ownerCommand,
});
}
const shapeless = (file.no_shape ?? []);
if (shapeless.length) {
out.push({
key: "hosts-verb-folds-onto-no-shape",
headline: `${shapeless.length} hand(s) have no verb the face road can even shape`,
mechanism: `The runner asks two questions in order — does this verb's word fold onto one of the manifest's shapes, and does the hand's name land on a family. These hands fail the FIRST one, so they never reach the family question and answer prose in every host however many families exist. Most are legitimately faceless (\`snappy-ffmpeg concat\` has no picture of itself). The ones that are not — \`snappy-krisp fetch-meetings\` is the worked example — are fixed by one word in the runner's own VERB_SHAPE fold, never by a new table.`,
cells: shapeless.map((h) => `${h.skill}: ${h.verbs.slice(0, 6).join(", ")}`),
ownerCommand: null,
});
}
const missing = file.face_families.missing.filter((f) => !f.has_family);
if (missing.length) {
out.push({
key: "hosts-no-face-family",
headline: `${missing.length} platform hand(s) name no face family`,
mechanism: `The runner derives a face family from the hand's own name (snappy-runner/src/face.ts#familyForHand) against snappy-faces' FACE_FAMILIES. These hands land on a family that does not exist, so their answers are prose in EVERY host — no host driver can fix them and no screenshot will ever exist for them. The fix is a face family in the faces library, one per platform, in that platform's own look.`,
cells: missing.map((f) => `${f.skill} → "${f.family}": ${f.reason}`),
ownerCommand: null,
});
}
const unreachable = (file.face_families.unreachable ?? []).filter((f) => !f.reachable);
if (unreachable.length) {
out.push({
key: "hosts-family-has-no-read",
headline: `${unreachable.length} published face family(ies) no read can reach`,
mechanism: `A family is only as true as the sentence that reaches it. The runner takes TWO roads from an answer to a family, in this order: the hand's own declaration — \`HAND_CONTRACT.verbs.<verb>.face: "<kind>"\` — and, for hands that declare nothing, the derivation \`familyForHand\` (the hand's name minus the prefix) with VERB_SHAPE folding the verb onto a slot. These families pass neither. They draw beautifully and nobody's words arrive at them. THE FIX IS ALMOST NEVER A FACE. It is one of three, and the cell below says which: (1) a hand already makes this thing and does not say so — it names the kind on the verb that draws it, and prints the rows that kind declares as an ADDITIVE fold, renaming nothing; (2) the hand's read prints text or the wrong shape, so the declaration would be a status over a missing artifact — the read gains a JSON answer first; (3) no hand makes this thing at all, which is an honest red that stays in the denominator until some hand does.`,
cells: unreachable.map((f) => `"${f.family}": ${f.reason}`),
ownerCommand: null,
});
}
return out;
}
/* ── THE DOOR THE NUMBER WAS COUNTED AGAINST ───────────────────────────────
* A NUMBER WITHOUT ITS RUNNER IS NOT A MEASUREMENT ⟨hosts-5's named gap,
* 2026-09-09⟩. The same battery, the same Mac, the same minute: against a
* local runner whose `search` names no verb the matrix read 1/20, and against
* the mini's it read 8/20 — while the SHIPPED bar had drawn the Gmail and
* Skool faces minutes earlier. The difference was the DOOR, and nothing in
* the corpus said which door, so the two numbers were comparable-looking and
* not comparable. Worse, a number attributed to a kernel head the runner is
* not serving is a status/artifact defect ⟨CLAUDE.md §10⟩: the state claims
* "this head scores 36" and the artifact that would have to exist — a runner
* on that head — does not.
*
* So the battery PROVES THE ROAD BEFORE IT COUNTS: `GET <door>/health` with
* the same bearer, which must answer a `revision`. That revision and that
* door ride in the corpus beside the number and print on the ledger, and a
* run that cannot prove them writes NO corpus at all — an unattributed number
* is worth less than no number, because it will be compared to the next one.
*/
export interface RunnerProof {
/** The MCP door every ask on this run was put to. */
mcp_door: string;
/** The door the proof was read from — the MCP door without its `/mcp`. */
health_door: string;
/** What `/health` answered. The number is counted against THIS. */
revision: string;
/** Hands the runner was holding when it answered. */
hands: number | null;
/** The kernel head this worktree is on, when it could be read. */
kernel_head: string | null;
/**
* Does the runner's revision name the kernel head the number is being
* attributed to? False is not a refusal — it is a fact the ledger prints,
* so nobody reads this run's number as a verdict on their own tree.
*/
attributed: boolean;
proved_at: string;
}
/** The health door beside an MCP door. `…/runner/mcp` → `…/runner/health`. */
export function healthDoorFor(mcpUrl: string): string {
return `${mcpUrl.replace(/\/+$/u, "").replace(/\/mcp$/u, "")}/health`;
}
/**
* The runner stamps its revision `<kernel>-<body>-<faces>`; only the first
* segment is the kernel commit, and it is abbreviated. A head "matches" when
* one is a prefix of the other, in whichever direction the abbreviation ran.
*/
export function attributedTo(revision: string, kernelHead: string | null): boolean {
if (kernelHead === null) return false;
const stamped = revision.split("-")[0] ?? "";
if (stamped.length < 7) return false;
return kernelHead.startsWith(stamped) || stamped.startsWith(kernelHead);
}
/* ── THE FILE ──────────────────────────────────────────────────────────────*/
export interface HostDriverStatus {
host: HostId;
label: string;
road: string;
surface: CardSurface;
driven: boolean;
reason: string;
ownerCommand: string | null;
}
export interface HostsFile {
schema: typeof HOSTS_SCHEMA;
generated_at: string;
/** The door and revision this run's number was counted against. */
runner: RunnerProof;
prompts: readonly PromptSpec[];
drivers: HostDriverStatus[];
cells: HostCell[];
road: RoadCell[];
face_families: { published: string[]; missing: FaceFamilyCell[]; unreachable: FamilyReachCell[] };
/** Hands the face road never reaches, reported and deliberately not counted. */
no_shape: Array<{ skill: string; verbs: string[] }>;
clusters: RedCluster[];
number: HostsNumber;
}
/** The matrix as a person reads it — the block `loop`'s LEDGER carries. */
export function hostsLedgerBlock(file: HostsFile): string {
const cell = (host: HostId, prompt: string): string => {
const found = file.cells.find((c) => c.host === host && c.prompt === prompt);
if (!found) return "—";
if (found.mount === "mounted") {
// A cell this run did not time is a cell this run did not draw. Say which.
if (found.standing !== null) return `**mounted** · standing #${found.standing}`;
return `**mounted** ${found.ms ?? "?"} ms`;
}
if (found.mount === "text") return "text";
return "not driven";
};
const header = `| host | ${PROMPTS.map((p) => p.id).join(" | ")} |`;
const rule = `|---|${PROMPTS.map(() => "---").join("|")}|`;
const rows = HOSTS.map((h) => `| ${h.label} | ${PROMPTS.map((p) => cell(h.id, p.id)).join(" | ")} |`);
const reds = file.clusters.map((c, i) => `| ${i + 1} | \`${c.key}\` | ${c.cells.length} | ${c.headline} |`);
const tick = "`";
const reachRows = (file.face_families.unreachable ?? []).map((f) => {
const read = f.read === null ? "**none**" : `${tick}${f.read}${tick}`;
return `| ${tick}${f.family}${tick} | ${read} | ${f.reachable ? "reached" : f.reason} |`;
});
const owner = file.drivers.filter((d) => !d.driven && d.ownerCommand)
.map((d) => `- **${d.label}** — ${d.reason}\n \`\`\`bash\n ${d.ownerCommand}\n \`\`\``);
// THE NUMBER AND ITS DOOR TRAVEL TOGETHER. Two runs of this battery against
// two runners are two different measurements, and printing the number alone
// invites the second to be read as progress over the first.
const proof = file.runner;
const attribution = proof.attributed
? `, which is the kernel head this run was cut from`
: `, which is NOT this worktree's head (${proof.kernel_head ?? "unknown"}) — the number is the runner's, not the tree's`;
return `## hosts — the same four asks, every host, counted
Generated ${file.generated_at} by \`api.ts hosts\`.
**${file.number.line}** — counted against \`${proof.revision}\` through \`${proof.mcp_door}\`${attribution}. ${proof.hands ?? "?"} hands, proved at ${proof.proved_at} on \`${proof.health_door}\`.
${header}
${rule}
${rows.join("\n")}
Green is a CARD ELEMENT in that host's own DOM; on \`linkedin-decide\` green also
requires the press to have reached the runner. A host that could not be driven
is a named red cell, never a blank one.
### the road under the cells — what the runner itself answers
The same asks, put to the runner's own MCP door, so a red cell can be READ: a
face the runner never named could not have been drawn by any host on any row.
Not a host and deliberately NOT in the number.
| ask | hand · verb | outcome | face named | ms | hops |
|---|---|---|---|---:|---:|
${file.road.map((r) => `| ${r.prompt} | \`${r.hand} ${r.verb}\` | ${r.outcome} | ${r.kind ?? "**none**"} | ${r.ms ?? "—"} | ${r.hops} |`).join("\n")}
### the families, and the read that reaches each
Every family \`snappy-faces\` publishes gets a cell, and its green is A READ THAT
NAMES IT — a hand the runner's own \`familyForHand\` routes there, holding a verb
that folds onto a shape. A family with no read draws for nobody, and that is
counted, because a library that grows without reads grows the picture and not
the product.
| family | the read that names it | |
|---|---|---|
${reachRows.join("\n")}
### red clusters
| # | cluster | cells | what has to change |
|---:|---|---:|---|
${reds.join("\n")}
${owner.length ? `### what only the owner can run\n\n${owner.join("\n")}\n` : ""}`;
}
/** One brief per red cluster, in the shape `loop` already hands a lane. */
export function hostsBrief(cluster: RedCluster): string {
return `# Lane brief — ${cluster.key}
**${cluster.headline}** · ${cluster.cells.length} cell(s)
## Role and prohibitions
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
driver or the face family named below and nothing else.
- NEVER install on any Mac, deploy, or push.
- NEVER send, post, spend or delete through any hand — a press is \`Later\`, never \`Publish\`.
- NEVER export or commit the owner's browser state.
- \`git commit --only <explicit paths>\`; never \`git add -A\`; never \`git stash\`.
- nice -n 19 every compile; no \`tsc --watch\`.
## The mechanism
${cluster.mechanism}
## The measured cells
\`\`\`
${cluster.cells.join("\n")}
\`\`\`
${cluster.ownerCommand ? `## The one line the owner runs first\n\n\`\`\`bash\n${cluster.ownerCommand}\n\`\`\`\n\nUntil that has been run this cluster cannot be closed by a lane, and reporting\nit closed is the status/artifact defect ⟨CLAUDE.md §10⟩.\n` : ""}
## The anti-fix — what would look like progress and is not
- Driving the loop's OWN AppBridge host and reporting the host cell green. That host is ours; the point of this battery is the hosts somebody else wrote.
- Dropping the cell from the matrix because it cannot be driven. A shrinking denominator is how a number flatters itself.
- Recording a face as mounted because the tool result NAMED a kind. A named kind with nothing drawn is exactly the status/artifact defect this repo is written against.
## The proof required
1. \`npx tsx skills/snappy-tool-design/api.ts hosts\` before and after, with this cluster's cell count falling.
2. The screenshot path for every cell that turned green, under \`${SHOT_ROOT}\`.
3. \`SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh\` still passing.
4. The whole matrix pasted, not the one row — no other cell may fall.
`;
}
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { lintSkill } from "./api.ts";
import {
PROBE_SCHEMA,
RENDER_SCHEMA,
REQUIRED_METRICS,
faceKindsDrawn,
probeFor,
readProbeFile,
readRenderFile,
renderFor,
type ProbeFile,
type RenderFile,
} from "./corpus.ts";
import { clusterFailures, clusterKey, ledger } from "./loop.ts";
import { drawOwner, gradeFamilies } from "./render.ts";
const HERE = dirname(fileURLToPath(import.meta.url));
/** A probe file exactly as `probe --via mini` writes one, small enough to read. */
const FIXTURE: ProbeFile = {
schema: PROBE_SCHEMA,
generated_at: "2026-09-08T22:00:00.000Z",
server: {
published_tools: 5,
tools_list_tokens: 1439,
metrics: REQUIRED_METRICS,
fresh_client: true,
handshake_after_deploy: true,
revision_first: "rev-a",
revision_last: "rev-a",
},
skills: {
"snappy-fixture": {
reads: [
{ verb: "list", limit: 1, chars: 1000, bytes: 1000, duration_ms: 700, request_count: 1, outcome: "answered" },
{ verb: "list", limit: 5, chars: 2000, bytes: 2000, duration_ms: 800, request_count: 1, outcome: "answered" },
],
refusals: [{ verb: "list", outcome: "refused", code: "missing_credential", message: "FIXTURE_KEY is not set", fix: "set FIXTURE_KEY" }],
unmet_requirement: [{ verb: "list", elapsed_ms: 12, child_processes: 0 }],
search_ranks: [1],
},
},
};
test("the probe corpus folds server facts into every skill's slice", () => {
const slice = probeFor(FIXTURE, "snappy-fixture");
assert.equal(slice?.published_tools, 5);
assert.equal(slice?.tools_list_tokens, 1439);
assert.deepEqual(slice?.metrics, REQUIRED_METRICS);
assert.equal(slice?.fresh_client, true);
assert.equal(slice?.reads?.length, 2);
});
test("a skill the probe never reached keeps its DEFER — absence stays absence", () => {
assert.equal(probeFor(FIXTURE, "snappy-never-probed"), null);
assert.equal(probeFor(null, "snappy-fixture"), null);
});
test("a probe file with the wrong schema is not read at all", () => {
assert.equal(readProbeFile(join(HERE, "does-not-exist")), null);
});
test("the render corpus grades a skill on the AND of the families it owns", () => {
const file: RenderFile = {
schema: RENDER_SCHEMA,
generated_at: "2026-09-08T22:00:00.000Z",
host: { ext_apps: "1.7.5", widths: [360, 720], resource_uri: "ui://snappy/faces" },
kinds: [],
families: {
gmail: { auto_fit: true, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true },
linkedin: { auto_fit: false, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true },
},
skill_families: { "snappy-faces": ["gmail", "linkedin"], "snappy-gmail": ["gmail"], "snappy-linkedin": ["linkedin"] },
};
assert.equal(renderFor(file, "snappy-gmail")?.values.auto_fit, true);
assert.equal(renderFor(file, "snappy-linkedin")?.values.auto_fit, false);
// one bad family makes the owner of both bad — a face family is the unit
assert.equal(renderFor(file, "snappy-faces")?.values.auto_fit, false);
// a skill with no family is vacuous, and says so through an empty family list
assert.deepEqual(renderFor(file, "snappy-telegram"), { values: {}, families: [] });
});
test("families are graded from what the host saw, not from a declaration", () => {
const graded = gradeFamilies([
{ kind: "gmail-list", family: "gmail", member: "list", component: "X", skill: "snappy-gmail", buttons: [], press: null, shot: "a.png", threw: null,
widths: { "360": { first_paint_ms: 10, drew: "drew", height: 400, nested_scroll: false, painted: 40 }, "720": { first_paint_ms: 9, drew: "drew", height: 380, nested_scroll: false, painted: 40 } } },
{ kind: "gmail-thread", family: "gmail", member: "thread", component: "Y", skill: "snappy-gmail", buttons: [], press: null, shot: null, threw: null,
widths: { "360": { first_paint_ms: 11, drew: "drew", height: 900, nested_scroll: true, painted: 60 }, "720": { first_paint_ms: 8, drew: "drew", height: 700, nested_scroll: false, painted: 60 } } },
], "snappy-faces");
assert.equal(graded.families.gmail.auto_fit, false, "one nested scroller fails the family");
assert.equal(graded.families.gmail.dated_real_host_screenshot, false, "one kind with no shot fails the family");
assert.deepEqual(graded.skill_families["snappy-gmail"], ["gmail"]);
});
test("the owning hand is READ off the fixture path, never a hand-written map", () => {
// the shape the bundle's own build-report writes since faces moved into
// skills/<hand>/faces/ — the path IS face-homes' answer, already resolved
assert.equal(drawOwner("../snappy-gmail/faces/fixtures/gmail-list.json"), "snappy-gmail");
assert.equal(drawOwner("../snappy-statechange/faces/fixtures/statechange-feed.json"), "snappy-statechange");
// a family the faces library still keeps names no hand, and claims none
assert.equal(drawOwner("fixtures/stripe-charge.json"), null);
});
test("every family the run saw gets an owner — the publisher, plus the hand that hosts it", () => {
const row = (kind: string, family: string, skill: string | null) => ({
kind, family, member: "list", component: "C", skill, buttons: [], press: null, shot: `${kind}.png`, threw: null,
widths: { "360": { first_paint_ms: 5, drew: "drew", height: 100, nested_scroll: false, painted: 20 } },
});
const graded = gradeFamilies([row("gmail-list", "gmail", "snappy-gmail"), row("stripe-charge", "stripe", null)], "snappy-faces");
// the publisher draws every family in its own bundle, so it is graded on every one
assert.deepEqual(graded.skill_families["snappy-faces"], ["gmail", "stripe"]);
assert.deepEqual(graded.skill_families["snappy-gmail"], ["gmail"]);
// no family is left with nobody accountable for it
const owned = new Set(Object.values(graded.skill_families).flat());
assert.deepEqual([...owned].sort(), Object.keys(graded.families).sort());
});
test("a hand is graded on the family its own reads answered with, even when the library keeps it", () => {
const file: RenderFile = {
schema: RENDER_SCHEMA,
generated_at: "2026-09-09T12:00:00.000Z",
host: { ext_apps: "1.7.5", widths: [360, 720], resource_uri: "ui://snappy/faces" },
kinds: [{ kind: "imageset-grid", family: "imageset", member: "grid", component: "G", skill: null, buttons: [], press: null, shot: "g.png", threw: null, widths: {} }],
families: { imageset: { auto_fit: false, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true } },
skill_families: { "snappy-faces": ["imageset"] },
};
// snappy-image hosts no faces, but its probe read came back drawn as imageset-grid
assert.deepEqual(renderFor(file, "snappy-image", ["imageset-grid"])?.families, ["imageset"]);
assert.equal(renderFor(file, "snappy-image", ["imageset-grid"])?.values.auto_fit, false);
// and a hand whose reads named no face still owns nothing
assert.deepEqual(renderFor(file, "snappy-image", []), { values: {}, families: [] });
});
test("faceKindsDrawn reads the kinds off the recorded reads, deduplicated", () => {
assert.deepEqual(faceKindsDrawn({ reads: [
{ verb: "list", face: { kind: "skool-feed" } },
{ verb: "recent", face: { kind: "skool-feed" } },
{ verb: "one", face: { textFallback: true } },
] } as never), ["skool-feed"]);
assert.deepEqual(faceKindsDrawn(null), []);
});
test("a skill that owns no face family DEFERS rules 50-53 — it never PASSES them vacuously", async () => {
// THE DEFECT ⟨2026-09-09⟩: this arm answered PASS "…is vacuous", so 94 of 98
// skills read 98 PASS / 0 FAIL on four rules nobody had measured, and the
// zero floor vouched for it. snappy-ads publishes no face family.
const report = await lintSkill("snappy-ads");
for (const id of [50, 51, 52, 53]) {
const rule = report.rules.find((candidate) => candidate.id === id);
assert.ok(rule, `rule ${id} is evaluated`);
assert.equal(rule.status, "DEFER", `rule ${id} defers instead of passing on nothing (detail: ${rule.detail})`);
assert.match(rule.detail, /never measured|no real-host render probe/);
}
});
test("two skills failing the same way are ONE cluster, not two", () => {
const shared = { group: "B11", rule: "No optional positional precedes a commonly-supplied argument", sources: ["S11"], mode: "static" as const, status: "FAIL" as const, pass: false };
const reports = [
{ skill: "snappy-imessage", pass: 0, fail: 1, defer: 0, rules: [{ ...shared, id: 59, detail: "recent(contact?, limit?) — contact? before limit?" }] },
{ skill: "snappy-youtube", pass: 0, fail: 1, defer: 0, rules: [{ ...shared, id: 59, detail: "recent(channel?, limit?) — channel? before limit?" }] },
];
const clusters = clusterFailures(reports as never);
assert.equal(clusters.length, 1, "one mechanism, one cluster");
assert.equal(clusters[0].key, "r59-optional-positional-before-the-count");
assert.deepEqual(clusters[0].skills, ["snappy-imessage", "snappy-youtube"]);
assert.match(clusters[0].mechanism, /bare count lands in the wrong slot/);
});
test("different mechanisms under one rule stay apart", () => {
const absent = clusterKey({ id: 60, detail: "AGENTS.md carries no generated contract block; run fix-loader" } as never);
const stale = clusterKey({ id: 60, detail: "generated block is stale against HAND_CONTRACT; run fix-loader" } as never);
assert.notEqual(absent.key, stale.key);
});
test("the ledger ranks by defects one fix kills, biggest first", () => {
const clusters = clusterFailures([
{ skill: "a", pass: 0, fail: 1, defer: 0, rules: [{ id: 6, mode: "static", rule: "desc", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "description differs from contract source" }] },
{ skill: "b", pass: 0, fail: 1, defer: 0, rules: [{ id: 6, mode: "static", rule: "desc", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "description differs from contract source" }] },
{ skill: "c", pass: 0, fail: 1, defer: 0, rules: [{ id: 6, mode: "static", rule: "desc", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "description differs from contract source" }] },
{ skill: "d", pass: 0, fail: 1, defer: 0, rules: [{ id: 17, mode: "static", rule: "limit", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "limit default/maximum missing: list" }] },
] as never);
assert.equal(clusters[0].skills.length, 3);
assert.equal(clusters[1].skills.length, 1);
const text = ledger(clusters, { skills: 4, pass: 0, fail: 4, defer: 0 });
assert.ok(text.indexOf("r6-description-not-projected") < text.indexOf("r17-unbounded-collection-read"), "the bigger kill ranks first");
assert.match(text, /Re-run after every landing/);
});
/**
* THE POINT OF THE WHOLE LANE. These rules could only answer DEFER before a
* probe existed. They are graded here against the corpus the probe actually
* wrote, so this test goes red the moment the evidence stops being produced.
*/
test("rules that could only DEFER are graded once the probe corpus exists", async (t) => {
if (!existsSync(join(HERE, "tool-design-probe.json"))) {
t.skip("no tool-design-probe.json yet — run `api.ts probe --via mini`");
return;
}
const corpus = readProbeFile(HERE);
// Rule 24 compares a limit 1/5/25 series, so the subject must be a skill
// whose read actually takes a limit — otherwise the test grades absence.
const probed = Object.keys(corpus?.skills ?? {}).find((name) => (corpus?.skills[name].reads ?? []).some((read) => read.limit !== undefined));
assert.ok(probed, "the corpus holds at least one skill probed across a limit series");
const report = await lintSkill(probed!);
for (const id of [23, 24, 37, 38, 43, 56, 58]) {
assert.notEqual(report.rules.find((rule) => rule.id === id)?.status, "DEFER", `rule ${id} still defers with a corpus present`);
}
assert.equal(report.rules.find((rule) => rule.id === 37)?.status, "PASS", "five published tools is inside the ten-tool budget");
});
test("the committed probe corpus carries shapes, never a person's values", async () => {
const { readFileSync } = await import("node:fs");
const { scrubPersonal } = await import("./probe.ts");
// String VALUES only: a byte count or a timestamp is a number, not a phone.
const strings: string[] = [];
const walk = (o: unknown): void => {
if (typeof o === "string") strings.push(o);
else if (Array.isArray(o)) o.forEach(walk);
else if (o && typeof o === "object") Object.values(o as Record<string, unknown>).forEach(walk);
};
walk(JSON.parse(readFileSync(new URL("./tool-design-probe.json", import.meta.url), "utf8")));
const hits = strings.flatMap((s) => s.match(/[\w.+-]+@[\w-]+\.[\w.]+|boulos|(?:\+?1[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}/giu) ?? []);
assert.deepEqual(hits, [], `personal values in the corpus: ${hits.slice(0, 5).join(", ")}`);
assert.equal(scrubPersonal("mail mara@quillworks.example at 555-123-4567, Boulos"), "mail [email] at [phone], [name]");
});
/* ── A PERSON'S ASK IS A LANE, AND IT OUTRANKS EVERY RULE ────────────────────
*
* ⟨owner, 2026-09-09 01:4x: "a fix field that files a lane"⟩. The runner's
* `POST /fix-request` door appends one line per note somebody typed on a skill
* card; this loop reads that file and ranks his words above the linter's.
*
* EVERY WORD IN THIS FIXTURE IS INVENTED. The real file is a person's own
* sentences about his own mail, calendar and money — a test that shipped them
* would put personal data in git to prove a feature about personal data. The
* generated `briefs/person-*.md` and the loop's `LEDGER.md` are gitignored for
* the same reason, and the last test here is what keeps that true.
*/
const ASK_LINES = [
{ at: "2026-09-09T04:00:00.000Z", skill: "snappy-fixture", words: "the week view should start on Monday", verb: "week", face_kind: "fixture-week", revision: "rev-a" },
{ at: "2026-09-09T05:00:00.000Z", skill: "snappy-fixture", words: "and the all-day row should be at the top", verb: null, face_kind: null, revision: "rev-a" },
{ at: "2026-09-09T04:30:00.000Z", skill: "snappy-other", words: "it should say who it is from before what it is about", verb: "list", face_kind: null, revision: "rev-a" },
];
function writeAskFile(lines: unknown[], extra = ""): string {
const path = join(mkdtempSync(join(tmpdir(), "fix-requests-")), "fix-requests.jsonl");
writeFileSync(path, `${lines.map((line) => JSON.stringify(line)).join("\n")}\n${extra}`);
return path;
}
test("an appended line round-trips out of the file the runner's door writes", async () => {
const { readFixRequests } = await import("./loop.ts");
const asks = readFixRequests(writeAskFile(ASK_LINES));
assert.equal(asks.length, 3);
assert.equal(asks[0]!.words, "the week view should start on Monday");
assert.equal(asks[0]!.verb, "week");
assert.equal(asks[0]!.face_kind, "fixture-week");
assert.equal(asks[1]!.verb, null, "an absent verb is null, never the empty string a card would draw as a verb named nothing");
});
test("a half-written line loses itself and nothing behind it — the writer is a live appender", async () => {
const { readFixRequests } = await import("./loop.ts");
// A torn last line is what a reader racing an append actually sees.
const asks = readFixRequests(writeAskFile(ASK_LINES, '{"skill":"snappy-fixt'));
assert.equal(asks.length, 3, "one torn line threw away every good line behind it");
// …and a line with no words is not an ask, however well-formed its JSON.
assert.equal(readFixRequests(writeAskFile([{ at: "2026-09-09T04:00:00.000Z", skill: "snappy-fixture", words: " " }])).length, 0);
});
test("one cluster per hand he wrote about, his newest sentence as the mechanism, his words verbatim", async () => {
const { personClusters, readFixRequests } = await import("./loop.ts");
const clusters = personClusters(readFixRequests(writeAskFile(ASK_LINES)));
assert.equal(clusters.length, 2, "two hands, two lanes");
const fixture = clusters.find((c) => c.key === "person-snappy-fixture")!;
assert.equal(fixture.kind, "person");
assert.equal(fixture.skills.length, 1);
assert.equal(fixture.evidence.length, 2, "two notes about one hand are ONE lane, not two");
assert.equal(fixture.mechanism, "and the all-day row should be at the top", "the mechanism is not his NEWEST sentence");
// VERBATIM. A paraphrase would put my sentence where his was.
for (const line of ASK_LINES.filter((l) => l.skill === "snappy-fixture")) {
assert.ok(fixture.evidence.some((e) => e.detail.includes(line.words)), `his words were re-worded: ${JSON.stringify(fixture.evidence)}`);
}
// The hand he wrote about twice ranks above the hand he wrote about once.
assert.equal(clusters[0]!.key, "person-snappy-fixture");
});
/* ── THE ROAD BETWEEN THE TWO MACS ───────────────────────────────────────────
*
* Nothing syncs `fix-requests.jsonl` ⟨measured 2026-09-09, lane menu-bar-13⟩,
* so `both` is how a note typed on the bar here is ranked by a loop run on the
* mini. The reader visits both bodies; the file never travels.
*/
const MINI_LINES = [
{ at: "2026-09-09T06:00:00.000Z", skill: "snappy-elsewhere", words: "it should show the sender before the subject", verb: null, face_kind: null, revision: "rev-b" },
];
test("`both` folds the two bodies into one list, and the same line reached twice counts once", async () => {
const { readAsks, bodiesFor } = await import("./loop.ts");
assert.deepEqual(bodiesFor("both"), ["local", "mini"]);
assert.deepEqual(bodiesFor("mini"), ["mini"]);
assert.deepEqual(bodiesFor("local"), ["local"], "anything unnamed is this Mac, as it was before `both` existed");
const asks = await readAsks(bodiesFor("both"), {
local: () => ASK_LINES,
// The mini answering with one line this Mac already holds is the shape of
// `both` run ON the mini: the same file, once by path and once by door.
mini: async () => [...MINI_LINES, ASK_LINES[0]!],
}, () => {});
assert.equal(asks.length, 4, "the duplicated line was counted twice");
assert.ok(asks.some((a) => a.skill === "snappy-elsewhere"), "the other Mac's note never arrived");
});
test("an unreachable body never throws away the body that answered", async () => {
const { readAsks } = await import("./loop.ts");
const said: string[] = [];
const asks = await readAsks(["local", "mini"], {
local: () => ASK_LINES,
mini: async () => { throw new Error("tailnet is down"); },
}, (line) => said.push(line));
assert.equal(asks.length, 3, "a failed mini threw away every note typed on this Mac");
assert.ok(said.some((line) => line.includes("mini") && line.includes("COULD NOT BE READ")), `the failure was silent: ${said.join(" | ")}`);
assert.ok(said.some((line) => line.includes("absence, not a zero")), "an unread body reads as a zero");
});
test("the ledger puts a person's ask above every lint cluster, however many defects the rule kills", async () => {
const { ledger, personClusters, readFixRequests } = await import("./loop.ts");
const asks = personClusters(readFixRequests(writeAskFile(ASK_LINES)));
// A lint cluster far bigger than either ask — the case where ranking by
// score would silently put the linter above him.
const big = clusterFailures([
...Array.from({ length: 40 }, (_, i) => ({
skill: `snappy-${i}`, pass: 0, fail: 1, defer: 0,
rules: [{ id: 59, rule: "positional order", status: "FAIL", mode: "static", detail: "an optional positional stands before limit" }],
})),
] as never);
assert.ok(big[0]!.skills.length > asks[0]!.evidence.length, "the fixture does not set up the case it claims to");
const text = ledger([...asks, ...big], { skills: 40, pass: 0, fail: 40, defer: 0 });
const rows = text.split("\n").filter((line) => line.startsWith("| ") && line.includes("`"));
assert.match(rows[0]!, /person-snappy-fixture/u, `the top row is not his ask:\n${rows.slice(0, 3).join("\n")}`);
assert.match(rows[1]!, /person-snappy-other/u);
assert.match(rows[2]!, /r59-/u, "the lint cluster did not follow his asks");
assert.match(rows[0]!, /HE ASKED/u, "a person's row is drawn as a rule number");
assert.match(text, /2 row\(s\) at the top are things a PERSON asked for/u);
// A ledger with no asks says nothing about asks — an absence, not a zero row.
assert.doesNotMatch(ledger(big, { skills: 40, pass: 0, fail: 40, defer: 0 }), /PERSON asked/u);
});
test("a person's brief quotes him and asks for a change on his glass, not a lint count", async () => {
const { briefFor, personClusters, readFixRequests } = await import("./loop.ts");
const cluster = personClusters(readFixRequests(writeAskFile(ASK_LINES)))[0]!;
const brief = briefFor(cluster, join(HERE, ".."));
assert.match(brief, /A PERSON ASKED FOR THIS/u);
assert.match(brief, /> .*the week view should start on Monday/u, "his line is not quoted verbatim in the brief");
assert.match(brief, /and the all-day row should be at the top/u, "the older note was dropped from the lane");
// The lint brief's proof is a FAIL count falling. There is no count here, and
// a brief that asked for one would be asking a lane to prove the wrong thing.
assert.doesNotMatch(brief, /FAIL count falling/u);
assert.match(brief, /skills\/snappy-fixture\//u, "the brief does not say where to start");
});
test("his words never reach git: the generated person briefs and the loop ledger are ignored", () => {
const root = join(HERE, "..", "..");
for (const path of ["skills/snappy-tool-design/briefs/person-snappy-calendar.md", "skills/snappy-tool-design/LEDGER.md"]) {
// `check-ignore` exits 1 when a path is NOT ignored, so a throw here is the
// failure: the file that would carry his sentences is committable.
const ignored = execFileSync("git", ["check-ignore", path], { cwd: root, encoding: "utf8" }).trim();
assert.equal(ignored, path);
}
// …and the LINT briefs beside them stay tracked — this must not have gone
// wide and silenced the artifacts the loop is supposed to publish.
assert.equal(existsSync(join(HERE, "briefs", "hosts-no-face-family.md")), true);
assert.throws(() => execFileSync("git", ["check-ignore", "skills/snappy-tool-design/briefs/hosts-no-face-family.md"], { cwd: root, stdio: "pipe" }));
});
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { lintSkill } from "./api.ts";
import {
PROBE_SCHEMA,
RENDER_SCHEMA,
REQUIRED_METRICS,
faceKindsDrawn,
probeFor,
readProbeFile,
readRenderFile,
renderFor,
type ProbeFile,
type RenderFile,
} from "./corpus.ts";
import { clusterFailures, clusterKey, ledger } from "./loop.ts";
import { drawOwner, gradeFamilies } from "./render.ts";
const HERE = dirname(fileURLToPath(import.meta.url));
/** A probe file exactly as `probe --via mini` writes one, small enough to read. */
const FIXTURE: ProbeFile = {
schema: PROBE_SCHEMA,
generated_at: "2026-09-08T22:00:00.000Z",
server: {
published_tools: 5,
tools_list_tokens: 1439,
metrics: REQUIRED_METRICS,
fresh_client: true,
handshake_after_deploy: true,
revision_first: "rev-a",
revision_last: "rev-a",
},
skills: {
"snappy-fixture": {
reads: [
{ verb: "list", limit: 1, chars: 1000, bytes: 1000, duration_ms: 700, request_count: 1, outcome: "answered" },
{ verb: "list", limit: 5, chars: 2000, bytes: 2000, duration_ms: 800, request_count: 1, outcome: "answered" },
],
refusals: [{ verb: "list", outcome: "refused", code: "missing_credential", message: "FIXTURE_KEY is not set", fix: "set FIXTURE_KEY" }],
unmet_requirement: [{ verb: "list", elapsed_ms: 12, child_processes: 0 }],
search_ranks: [1],
},
},
};
test("the probe corpus folds server facts into every skill's slice", () => {
const slice = probeFor(FIXTURE, "snappy-fixture");
assert.equal(slice?.published_tools, 5);
assert.equal(slice?.tools_list_tokens, 1439);
assert.deepEqual(slice?.metrics, REQUIRED_METRICS);
assert.equal(slice?.fresh_client, true);
assert.equal(slice?.reads?.length, 2);
});
test("a skill the probe never reached keeps its DEFER — absence stays absence", () => {
assert.equal(probeFor(FIXTURE, "snappy-never-probed"), null);
assert.equal(probeFor(null, "snappy-fixture"), null);
});
test("a probe file with the wrong schema is not read at all", () => {
assert.equal(readProbeFile(join(HERE, "does-not-exist")), null);
});
test("the render corpus grades a skill on the AND of the families it owns", () => {
const file: RenderFile = {
schema: RENDER_SCHEMA,
generated_at: "2026-09-08T22:00:00.000Z",
host: { ext_apps: "1.7.5", widths: [360, 720], resource_uri: "ui://snappy/faces" },
kinds: [],
families: {
gmail: { auto_fit: true, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true },
linkedin: { auto_fit: false, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true },
},
skill_families: { "snappy-faces": ["gmail", "linkedin"], "snappy-gmail": ["gmail"], "snappy-linkedin": ["linkedin"] },
};
assert.equal(renderFor(file, "snappy-gmail")?.values.auto_fit, true);
assert.equal(renderFor(file, "snappy-linkedin")?.values.auto_fit, false);
// one bad family makes the owner of both bad — a face family is the unit
assert.equal(renderFor(file, "snappy-faces")?.values.auto_fit, false);
// a skill with no family is vacuous, and says so through an empty family list
assert.deepEqual(renderFor(file, "snappy-telegram"), { values: {}, families: [] });
});
test("families are graded from what the host saw, not from a declaration", () => {
const graded = gradeFamilies([
{ kind: "gmail-list", family: "gmail", member: "list", component: "X", skill: "snappy-gmail", buttons: [], press: null, shot: "a.png", threw: null,
widths: { "360": { first_paint_ms: 10, drew: "drew", height: 400, nested_scroll: false, painted: 40 }, "720": { first_paint_ms: 9, drew: "drew", height: 380, nested_scroll: false, painted: 40 } } },
{ kind: "gmail-thread", family: "gmail", member: "thread", component: "Y", skill: "snappy-gmail", buttons: [], press: null, shot: null, threw: null,
widths: { "360": { first_paint_ms: 11, drew: "drew", height: 900, nested_scroll: true, painted: 60 }, "720": { first_paint_ms: 8, drew: "drew", height: 700, nested_scroll: false, painted: 60 } } },
], "snappy-faces");
assert.equal(graded.families.gmail.auto_fit, false, "one nested scroller fails the family");
assert.equal(graded.families.gmail.dated_real_host_screenshot, false, "one kind with no shot fails the family");
assert.deepEqual(graded.skill_families["snappy-gmail"], ["gmail"]);
});
test("the owning hand is READ off the fixture path, never a hand-written map", () => {
// the shape the bundle's own build-report writes since faces moved into
// skills/<hand>/faces/ — the path IS face-homes' answer, already resolved
assert.equal(drawOwner("../snappy-gmail/faces/fixtures/gmail-list.json"), "snappy-gmail");
assert.equal(drawOwner("../snappy-statechange/faces/fixtures/statechange-feed.json"), "snappy-statechange");
// a family the faces library still keeps names no hand, and claims none
assert.equal(drawOwner("fixtures/stripe-charge.json"), null);
});
test("every family the run saw gets an owner — the publisher, plus the hand that hosts it", () => {
const row = (kind: string, family: string, skill: string | null) => ({
kind, family, member: "list", component: "C", skill, buttons: [], press: null, shot: `${kind}.png`, threw: null,
widths: { "360": { first_paint_ms: 5, drew: "drew", height: 100, nested_scroll: false, painted: 20 } },
});
const graded = gradeFamilies([row("gmail-list", "gmail", "snappy-gmail"), row("stripe-charge", "stripe", null)], "snappy-faces");
// the publisher draws every family in its own bundle, so it is graded on every one
assert.deepEqual(graded.skill_families["snappy-faces"], ["gmail", "stripe"]);
assert.deepEqual(graded.skill_families["snappy-gmail"], ["gmail"]);
// no family is left with nobody accountable for it
const owned = new Set(Object.values(graded.skill_families).flat());
assert.deepEqual([...owned].sort(), Object.keys(graded.families).sort());
});
test("a hand is graded on the family its own reads answered with, even when the library keeps it", () => {
const file: RenderFile = {
schema: RENDER_SCHEMA,
generated_at: "2026-09-09T12:00:00.000Z",
host: { ext_apps: "1.7.5", widths: [360, 720], resource_uri: "ui://snappy/faces" },
kinds: [{ kind: "imageset-grid", family: "imageset", member: "grid", component: "G", skill: null, buttons: [], press: null, shot: "g.png", threw: null, widths: {} }],
families: { imageset: { auto_fit: false, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true } },
skill_families: { "snappy-faces": ["imageset"] },
};
// snappy-image hosts no faces, but its probe read came back drawn as imageset-grid
assert.deepEqual(renderFor(file, "snappy-image", ["imageset-grid"])?.families, ["imageset"]);
assert.equal(renderFor(file, "snappy-image", ["imageset-grid"])?.values.auto_fit, false);
// and a hand whose reads named no face still owns nothing
assert.deepEqual(renderFor(file, "snappy-image", []), { values: {}, families: [] });
});
test("faceKindsDrawn reads the kinds off the recorded reads, deduplicated", () => {
assert.deepEqual(faceKindsDrawn({ reads: [
{ verb: "list", face: { kind: "skool-feed" } },
{ verb: "recent", face: { kind: "skool-feed" } },
{ verb: "one", face: { textFallback: true } },
] } as never), ["skool-feed"]);
assert.deepEqual(faceKindsDrawn(null), []);
});
test("a skill that owns no face family DEFERS rules 50-53 — it never PASSES them vacuously", async () => {
// THE DEFECT ⟨2026-09-09⟩: this arm answered PASS "…is vacuous", so 94 of 98
// skills read 98 PASS / 0 FAIL on four rules nobody had measured, and the
// zero floor vouched for it. snappy-ads publishes no face family.
const report = await lintSkill("snappy-ads");
for (const id of [50, 51, 52, 53]) {
const rule = report.rules.find((candidate) => candidate.id === id);
assert.ok(rule, `rule ${id} is evaluated`);
assert.equal(rule.status, "DEFER", `rule ${id} defers instead of passing on nothing (detail: ${rule.detail})`);
assert.match(rule.detail, /never measured|no real-host render probe/);
}
});
test("two skills failing the same way are ONE cluster, not two", () => {
const shared = { group: "B11", rule: "No optional positional precedes a commonly-supplied argument", sources: ["S11"], mode: "static" as const, status: "FAIL" as const, pass: false };
const reports = [
{ skill: "snappy-imessage", pass: 0, fail: 1, defer: 0, rules: [{ ...shared, id: 59, detail: "recent(contact?, limit?) — contact? before limit?" }] },
{ skill: "snappy-youtube", pass: 0, fail: 1, defer: 0, rules: [{ ...shared, id: 59, detail: "recent(channel?, limit?) — channel? before limit?" }] },
];
const clusters = clusterFailures(reports as never);
assert.equal(clusters.length, 1, "one mechanism, one cluster");
assert.equal(clusters[0].key, "r59-optional-positional-before-the-count");
assert.deepEqual(clusters[0].skills, ["snappy-imessage", "snappy-youtube"]);
assert.match(clusters[0].mechanism, /bare count lands in the wrong slot/);
});
test("different mechanisms under one rule stay apart", () => {
const absent = clusterKey({ id: 60, detail: "AGENTS.md carries no generated contract block; run fix-loader" } as never);
const stale = clusterKey({ id: 60, detail: "generated block is stale against HAND_CONTRACT; run fix-loader" } as never);
assert.notEqual(absent.key, stale.key);
});
test("the ledger ranks by defects one fix kills, biggest first", () => {
const clusters = clusterFailures([
{ skill: "a", pass: 0, fail: 1, defer: 0, rules: [{ id: 6, mode: "static", rule: "desc", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "description differs from contract source" }] },
{ skill: "b", pass: 0, fail: 1, defer: 0, rules: [{ id: 6, mode: "static", rule: "desc", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "description differs from contract source" }] },
{ skill: "c", pass: 0, fail: 1, defer: 0, rules: [{ id: 6, mode: "static", rule: "desc", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "description differs from contract source" }] },
{ skill: "d", pass: 0, fail: 1, defer: 0, rules: [{ id: 17, mode: "static", rule: "limit", sources: ["x"], group: "g", status: "FAIL", pass: false, detail: "limit default/maximum missing: list" }] },
] as never);
assert.equal(clusters[0].skills.length, 3);
assert.equal(clusters[1].skills.length, 1);
const text = ledger(clusters, { skills: 4, pass: 0, fail: 4, defer: 0 });
assert.ok(text.indexOf("r6-description-not-projected") < text.indexOf("r17-unbounded-collection-read"), "the bigger kill ranks first");
assert.match(text, /Re-run after every landing/);
});
/**
* THE POINT OF THE WHOLE LANE. These rules could only answer DEFER before a
* probe existed. They are graded here against the corpus the probe actually
* wrote, so this test goes red the moment the evidence stops being produced.
*/
test("rules that could only DEFER are graded once the probe corpus exists", async (t) => {
if (!existsSync(join(HERE, "tool-design-probe.json"))) {
t.skip("no tool-design-probe.json yet — run `api.ts probe --via mini`");
return;
}
const corpus = readProbeFile(HERE);
// Rule 24 compares a limit 1/5/25 series, so the subject must be a skill
// whose read actually takes a limit — otherwise the test grades absence.
const probed = Object.keys(corpus?.skills ?? {}).find((name) => (corpus?.skills[name].reads ?? []).some((read) => read.limit !== undefined));
assert.ok(probed, "the corpus holds at least one skill probed across a limit series");
const report = await lintSkill(probed!);
for (const id of [23, 24, 37, 38, 43, 56, 58]) {
assert.notEqual(report.rules.find((rule) => rule.id === id)?.status, "DEFER", `rule ${id} still defers with a corpus present`);
}
assert.equal(report.rules.find((rule) => rule.id === 37)?.status, "PASS", "five published tools is inside the ten-tool budget");
});
test("the committed probe corpus carries shapes, never a person's values", async () => {
const { readFileSync } = await import("node:fs");
const { scrubPersonal } = await import("./probe.ts");
// String VALUES only: a byte count or a timestamp is a number, not a phone.
const strings: string[] = [];
const walk = (o: unknown): void => {
if (typeof o === "string") strings.push(o);
else if (Array.isArray(o)) o.forEach(walk);
else if (o && typeof o === "object") Object.values(o as Record<string, unknown>).forEach(walk);
};
walk(JSON.parse(readFileSync(new URL("./tool-design-probe.json", import.meta.url), "utf8")));
const hits = strings.flatMap((s) => s.match(/[\w.+-]+@[\w-]+\.[\w.]+|boulos|(?:\+?1[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}/giu) ?? []);
assert.deepEqual(hits, [], `personal values in the corpus: ${hits.slice(0, 5).join(", ")}`);
assert.equal(scrubPersonal("mail mara@quillworks.example at 555-123-4567, Boulos"), "mail [email] at [phone], [name]");
});
/* ── A PERSON'S ASK IS A LANE, AND IT OUTRANKS EVERY RULE ────────────────────
*
* ⟨owner, 2026-09-09 01:4x: "a fix field that files a lane"⟩. The runner's
* `POST /fix-request` door appends one line per note somebody typed on a skill
* card; this loop reads that file and ranks his words above the linter's.
*
* EVERY WORD IN THIS FIXTURE IS INVENTED. The real file is a person's own
* sentences about his own mail, calendar and money — a test that shipped them
* would put personal data in git to prove a feature about personal data. The
* generated `briefs/person-*.md` and the loop's `LEDGER.md` are gitignored for
* the same reason, and the last test here is what keeps that true.
*/
const ASK_LINES = [
{ at: "2026-09-09T04:00:00.000Z", skill: "snappy-fixture", words: "the week view should start on Monday", verb: "week", face_kind: "fixture-week", revision: "rev-a" },
{ at: "2026-09-09T05:00:00.000Z", skill: "snappy-fixture", words: "and the all-day row should be at the top", verb: null, face_kind: null, revision: "rev-a" },
{ at: "2026-09-09T04:30:00.000Z", skill: "snappy-other", words: "it should say who it is from before what it is about", verb: "list", face_kind: null, revision: "rev-a" },
];
function writeAskFile(lines: unknown[], extra = ""): string {
const path = join(mkdtempSync(join(tmpdir(), "fix-requests-")), "fix-requests.jsonl");
writeFileSync(path, `${lines.map((line) => JSON.stringify(line)).join("\n")}\n${extra}`);
return path;
}
test("an appended line round-trips out of the file the runner's door writes", async () => {
const { readFixRequests } = await import("./loop.ts");
const asks = readFixRequests(writeAskFile(ASK_LINES));
assert.equal(asks.length, 3);
assert.equal(asks[0]!.words, "the week view should start on Monday");
assert.equal(asks[0]!.verb, "week");
assert.equal(asks[0]!.face_kind, "fixture-week");
assert.equal(asks[1]!.verb, null, "an absent verb is null, never the empty string a card would draw as a verb named nothing");
});
test("a half-written line loses itself and nothing behind it — the writer is a live appender", async () => {
const { readFixRequests } = await import("./loop.ts");
// A torn last line is what a reader racing an append actually sees.
const asks = readFixRequests(writeAskFile(ASK_LINES, '{"skill":"snappy-fixt'));
assert.equal(asks.length, 3, "one torn line threw away every good line behind it");
// …and a line with no words is not an ask, however well-formed its JSON.
assert.equal(readFixRequests(writeAskFile([{ at: "2026-09-09T04:00:00.000Z", skill: "snappy-fixture", words: " " }])).length, 0);
});
test("one cluster per hand he wrote about, his newest sentence as the mechanism, his words verbatim", async () => {
const { personClusters, readFixRequests } = await import("./loop.ts");
const clusters = personClusters(readFixRequests(writeAskFile(ASK_LINES)));
assert.equal(clusters.length, 2, "two hands, two lanes");
const fixture = clusters.find((c) => c.key === "person-snappy-fixture")!;
assert.equal(fixture.kind, "person");
assert.equal(fixture.skills.length, 1);
assert.equal(fixture.evidence.length, 2, "two notes about one hand are ONE lane, not two");
assert.equal(fixture.mechanism, "and the all-day row should be at the top", "the mechanism is not his NEWEST sentence");
// VERBATIM. A paraphrase would put my sentence where his was.
for (const line of ASK_LINES.filter((l) => l.skill === "snappy-fixture")) {
assert.ok(fixture.evidence.some((e) => e.detail.includes(line.words)), `his words were re-worded: ${JSON.stringify(fixture.evidence)}`);
}
// The hand he wrote about twice ranks above the hand he wrote about once.
assert.equal(clusters[0]!.key, "person-snappy-fixture");
});
/* ── THE ROAD BETWEEN THE TWO MACS ───────────────────────────────────────────
*
* Nothing syncs `fix-requests.jsonl` ⟨measured 2026-09-09, lane menu-bar-13⟩,
* so `both` is how a note typed on the bar here is ranked by a loop run on the
* mini. The reader visits both bodies; the file never travels.
*/
const MINI_LINES = [
{ at: "2026-09-09T06:00:00.000Z", skill: "snappy-elsewhere", words: "it should show the sender before the subject", verb: null, face_kind: null, revision: "rev-b" },
];
test("`both` folds the two bodies into one list, and the same line reached twice counts once", async () => {
const { readAsks, bodiesFor } = await import("./loop.ts");
assert.deepEqual(bodiesFor("both"), ["local", "mini"]);
assert.deepEqual(bodiesFor("mini"), ["mini"]);
assert.deepEqual(bodiesFor("local"), ["local"], "anything unnamed is this Mac, as it was before `both` existed");
const asks = await readAsks(bodiesFor("both"), {
local: () => ASK_LINES,
// The mini answering with one line this Mac already holds is the shape of
// `both` run ON the mini: the same file, once by path and once by door.
mini: async () => [...MINI_LINES, ASK_LINES[0]!],
}, () => {});
assert.equal(asks.length, 4, "the duplicated line was counted twice");
assert.ok(asks.some((a) => a.skill === "snappy-elsewhere"), "the other Mac's note never arrived");
});
test("an unreachable body never throws away the body that answered", async () => {
const { readAsks } = await import("./loop.ts");
const said: string[] = [];
const asks = await readAsks(["local", "mini"], {
local: () => ASK_LINES,
mini: async () => { throw new Error("tailnet is down"); },
}, (line) => said.push(line));
assert.equal(asks.length, 3, "a failed mini threw away every note typed on this Mac");
assert.ok(said.some((line) => line.includes("mini") && line.includes("COULD NOT BE READ")), `the failure was silent: ${said.join(" | ")}`);
assert.ok(said.some((line) => line.includes("absence, not a zero")), "an unread body reads as a zero");
});
test("the ledger puts a person's ask above every lint cluster, however many defects the rule kills", async () => {
const { ledger, personClusters, readFixRequests } = await import("./loop.ts");
const asks = personClusters(readFixRequests(writeAskFile(ASK_LINES)));
// A lint cluster far bigger than either ask — the case where ranking by
// score would silently put the linter above him.
const big = clusterFailures([
...Array.from({ length: 40 }, (_, i) => ({
skill: `snappy-${i}`, pass: 0, fail: 1, defer: 0,
rules: [{ id: 59, rule: "positional order", status: "FAIL", mode: "static", detail: "an optional positional stands before limit" }],
})),
] as never);
assert.ok(big[0]!.skills.length > asks[0]!.evidence.length, "the fixture does not set up the case it claims to");
const text = ledger([...asks, ...big], { skills: 40, pass: 0, fail: 40, defer: 0 });
const rows = text.split("\n").filter((line) => line.startsWith("| ") && line.includes("`"));
assert.match(rows[0]!, /person-snappy-fixture/u, `the top row is not his ask:\n${rows.slice(0, 3).join("\n")}`);
assert.match(rows[1]!, /person-snappy-other/u);
assert.match(rows[2]!, /r59-/u, "the lint cluster did not follow his asks");
assert.match(rows[0]!, /HE ASKED/u, "a person's row is drawn as a rule number");
assert.match(text, /2 row\(s\) at the top are things a PERSON asked for/u);
// A ledger with no asks says nothing about asks — an absence, not a zero row.
assert.doesNotMatch(ledger(big, { skills: 40, pass: 0, fail: 40, defer: 0 }), /PERSON asked/u);
});
test("a person's brief quotes him and asks for a change on his glass, not a lint count", async () => {
const { briefFor, personClusters, readFixRequests } = await import("./loop.ts");
const cluster = personClusters(readFixRequests(writeAskFile(ASK_LINES)))[0]!;
const brief = briefFor(cluster, join(HERE, ".."));
assert.match(brief, /A PERSON ASKED FOR THIS/u);
assert.match(brief, /> .*the week view should start on Monday/u, "his line is not quoted verbatim in the brief");
assert.match(brief, /and the all-day row should be at the top/u, "the older note was dropped from the lane");
// The lint brief's proof is a FAIL count falling. There is no count here, and
// a brief that asked for one would be asking a lane to prove the wrong thing.
assert.doesNotMatch(brief, /FAIL count falling/u);
assert.match(brief, /skills\/snappy-fixture\//u, "the brief does not say where to start");
});
test("his words never reach git: the generated person briefs and the loop ledger are ignored", () => {
const root = join(HERE, "..", "..");
for (const path of ["skills/snappy-tool-design/briefs/person-snappy-calendar.md", "skills/snappy-tool-design/LEDGER.md"]) {
// `check-ignore` exits 1 when a path is NOT ignored, so a throw here is the
// failure: the file that would carry his sentences is committable.
const ignored = execFileSync("git", ["check-ignore", path], { cwd: root, encoding: "utf8" }).trim();
assert.equal(ignored, path);
}
// …and the LINT briefs beside them stay tracked — this must not have gone
// wide and silenced the artifacts the loop is supposed to publish.
assert.equal(existsSync(join(HERE, "briefs", "hosts-no-face-family.md")), true);
assert.throws(() => execFileSync("git", ["check-ignore", "skills/snappy-tool-design/briefs/hosts-no-face-family.md"], { cwd: root, stdio: "pipe" }));
});
/**
* THE LOOP — probe, render, lint, then turn what failed into WORK.
*
* The owner's order on 2026-09-09: "you need a closed feedback loop of using
* every part of this — remember this needs to be exponential." A list of 900
* rule failures is not exponential; it is a list. What compounds is a list of
* MECHANISMS, because one mechanism fixed kills every defect that shares it.
* Rule 59 is the worked example: six skills, one grammar mistake, one lane.
*
* So `loop` clusters by mechanism, not by skill, ranks by how many defects one
* fix kills, writes a lane brief per cluster and a ranked LEDGER.md, and is
* re-run after each landing. The count going down IS the measurement.
*/
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { PROBE_FILE, RENDER_FILE } from "./corpus.ts";
import type { LintResult, RuleResult } from "./api.ts";
export interface Cluster {
key: string;
/** WHERE THIS CLUSTER CAME FROM, and therefore how it is ranked and how its
* brief reads. `"person"` is a line somebody typed on a skill card;
* `"lint"` is a rule the linter found. They are one type because they are
* one queue — a second ledger for the person's asks would rank the same
* work twice and stop agreeing the first time either moved ⟨CLAUDE.md §4⟩. */
kind: "person" | "lint";
/** The lint rule, or 0 for a person's ask — his words answer to no rule. */
rule: number;
mechanism: string;
mode: string;
skills: string[];
evidence: Array<{ skill: string; detail: string }>;
anchors: string[];
}
/* ── THE PERSON'S OWN WORDS OUTRANK EVERY RULE ───────────────────────────────
*
* ⟨owner, 2026-09-09 01:4x: "a fix field that files a lane"⟩. Someone looking
* at a hand's card in the menu bar types one line about what it should do
* better and presses Note it. The bar posts it to the runner's
* `POST /fix-request` door, the runner appends it through the ONE writer in
* `src/store.ts`, and this file reads the file that door writes.
*
* THE PATH IS A CONVENTION, NOT AN IMPORT. This repo is the runner's `bundle/`
* submodule and cannot import from the superproject, so the two ends agree on
* `${SNAPPY_SKILLS_HOME:-~/.snappy-skills}/fix-requests.jsonl` and both say so
* where a reader will look: `store.ts`'s own header, and SKILL.md's
* "THE PERSON'S FIX FIELD" section. A constant copied into two files that
* disagree is a road that goes silently blank, so the DEFAULT and the ENV
* VARIABLE are both matched here, exactly as the runner resolves them.
*
* THE MINI IS READ THROUGH THE ROAD THAT ALREADY EXISTS. `probe --via mini`
* dials `MINI_MCP_URL` with a bearer from `SNAPPY_RUNNER_TOKEN_FILE`
* (`probe.ts:190,192`), and `GET /fix-requests?since=` is a door on that same
* port behind that same bearer — so `--fix-requests-from mini` swaps `/mcp`
* for `/fix-requests` on that URL and presents the same token file. There is no
* second credential and no ssh.
*
* HIS WORDS ARE NEVER RE-WORDED. The line goes into the brief verbatim as the
* mechanism. A summary of a correction is my sentence standing where his was.
*/
export interface PersonAsk {
at: string;
skill: string;
words: string;
verb: string | null;
face_kind: string | null;
revision: string | null;
}
/** The one path both ends resolve, by convention, the same way. */
export function fixRequestsPath(env: NodeJS.ProcessEnv = process.env): string {
const home = env.SNAPPY_SKILLS_HOME !== undefined && env.SNAPPY_SKILLS_HOME !== ""
? env.SNAPPY_SKILLS_HOME
: join(homedir(), ".snappy-skills");
return join(home, "fix-requests.jsonl");
}
/** Whatever the file holds, oldest first, skipping any half-written line — the
* writer is a live appender and a reader that threw on one bad line would
* throw away every good one behind it. */
export function readFixRequests(path = fixRequestsPath()): PersonAsk[] {
let text: string;
try { text = readFileSync(path, "utf8"); } catch { return []; }
const asks: PersonAsk[] = [];
for (const raw of text.split("\n")) {
if (raw.trim() === "") continue;
let line: Partial<PersonAsk>;
try { line = JSON.parse(raw) as Partial<PersonAsk>; } catch { continue; }
if (typeof line.skill !== "string" || line.skill === "") continue;
if (typeof line.words !== "string" || line.words.trim() === "") continue;
asks.push({
at: typeof line.at === "string" ? line.at : "",
skill: line.skill,
words: line.words.trim(),
verb: typeof line.verb === "string" && line.verb !== "" ? line.verb : null,
face_kind: typeof line.face_kind === "string" && line.face_kind !== "" ? line.face_kind : null,
revision: typeof line.revision === "string" && line.revision !== "" ? line.revision : null,
});
}
return asks;
}
/** The mini's own file, over the door that already exists on the probe's road.
* A body that cannot be reached is reported and returns nothing — a silent
* empty list would read as "he has asked for nothing". */
export async function fetchFixRequests(options: { url: string; tokenFile: string; since?: string }): Promise<PersonAsk[]> {
const bearer = readFileSync(options.tokenFile, "utf8").trim();
const door = `${options.url.replace(/\/mcp$/u, "")}/fix-requests?since=${encodeURIComponent(options.since ?? "")}`;
const answer = await fetch(door, { headers: { authorization: `Bearer ${bearer}` } });
if (!answer.ok) {
throw new Error(`${door} answered HTTP ${answer.status}. A 404 means the body on that Mac is older than this loop — it has no fix-request door yet.`);
}
const body = (await answer.json()) as { requests?: unknown };
return Array.isArray(body.requests) ? (body.requests as PersonAsk[]).filter((r) => typeof r?.skill === "string" && typeof r?.words === "string") : [];
}
/** The Macs a note can have been typed on. There is no third: the bar runs on
* one of these two and each one's runner owns its own file. */
export type AskBody = "local" | "mini";
/** What `--fix-requests-from` names, as bodies to visit. */
export function bodiesFor(from: string): AskBody[] {
return from === "both" ? ["local", "mini"] : from === "mini" ? ["mini"] : ["local"];
}
/**
* EVERY NAMED BODY'S NOTES, IN ONE LIST — the one reader, so a loop run on
* either Mac ranks what he typed on the other.
*
* EACH BODY IS READ ON ITS OWN, and that is the whole reason this is a function
* and not a `Promise.all`. A shared try/catch — which is what this replaced —
* lets an unreachable mini throw away every note typed on THIS Mac, and the
* ledger then prints "he has asked for nothing" over a file holding his words:
* a status truer than its artifact, on the one row that outranks all the rest.
* A body that fails is named out loud, by name, and the others still stand.
*
* DUPLICATES FOLD ON `at` + `skill` + `words`, because `both` reached from the
* mini itself reads one file twice — once by path, once through its own door —
* and one sentence counted twice would rank a hand above a hand he wrote about
* more.
*/
export async function readAsks(
bodies: AskBody[],
read: { local: () => PersonAsk[]; mini: () => Promise<PersonAsk[]> },
say: (line: string) => void = console.log,
): Promise<PersonAsk[]> {
const seen = new Set<string>();
const asks: PersonAsk[] = [];
for (const body of bodies) {
try {
const got = body === "local" ? read.local() : await read.mini();
let added = 0;
for (const ask of got) {
const key = `${ask.at} ${ask.skill} ${ask.words}`;
if (seen.has(key)) continue;
seen.add(key);
asks.push(ask);
added += 1;
}
say(`fix-requests (${body}) → ${added} note(s) a person typed on a card`);
} catch (error) {
say(`fix-requests (${body}) → COULD NOT BE READ: ${error instanceof Error ? error.message : String(error)}`);
say(` no ask from ${body} is ranked below — that is an absence, not a zero.`);
}
}
return asks;
}
/**
* ONE CLUSTER PER SKILL HE WROTE ABOUT, newest first inside it. Per SKILL and
* not per LINE, because two notes about the same hand are one lane: whoever
* opens it should read everything he has said about that hand before touching
* it. The mechanism is HIS most recent sentence, verbatim.
*/
export function personClusters(asks: PersonAsk[]): Cluster[] {
const bySkill = new Map<string, PersonAsk[]>();
for (const ask of asks) bySkill.set(ask.skill, [...(bySkill.get(ask.skill) ?? []), ask]);
const clusters: Cluster[] = [];
for (const [skill, filed] of bySkill) {
const newestFirst = [...filed].sort((a, b) => (b.at ?? "").localeCompare(a.at ?? ""));
clusters.push({
key: `person-${skill}`,
kind: "person",
rule: 0,
mechanism: newestFirst[0]!.words,
mode: "a person typed this on the card",
skills: [skill],
evidence: newestFirst.map((ask) => ({
skill,
detail: [ask.at === "" ? null : ask.at, ask.verb === null ? null : `verb ${ask.verb}`, ask.face_kind === null ? null : `face ${ask.face_kind}`, ask.words]
.filter((part) => part !== null).join(" · "),
})),
anchors: [],
});
}
// Most-asked-about hand first, then the one he wrote about most recently.
return clusters.sort((a, b) => b.evidence.length - a.evidence.length || b.evidence[0]!.detail.localeCompare(a.evidence[0]!.detail));
}
/**
* THE MECHANISM NAMES. A cluster key must be the same for two skills that
* share a root cause and different for two that do not, so the key is derived
* from the rule plus a normalized signature of what the detail SAID — never
* from the skill's name, which is what would split one mechanism into ninety.
*/
const SIGNATURES: Array<{ rule: number; test: RegExp; slug: string; mechanism: string }> = [
{ rule: 59, test: /before/, slug: "optional-positional-before-the-count", mechanism: "An optional positional argument stands in front of `limit`, so a bare count lands in the wrong slot and the first call an AI writes answers about the wrong thing." },
{ rule: 60, test: /carries no generated/, slug: "loader-block-absent", mechanism: "AGENTS.md carries no generated contract block, so the prompt half of the MCP projection teaches a first call from prose instead of from the contract." },
{ rule: 60, test: /stale/, slug: "loader-block-stale", mechanism: "The generated loader block has drifted from HAND_CONTRACT, so every reader gets a first call the contract no longer honours." },
{ rule: 6, test: /differs from contract/, slug: "description-not-projected", mechanism: "The SKILL.md description is a hand-written copy rather than a projection of HAND_CONTRACT.description, so the two drift and the MCP publishes the stale one." },
{ rule: 17, test: /limit default\/maximum missing/, slug: "unbounded-collection-read", mechanism: "A collection read declares no limit default and no maximum, so the answer's size is whatever the vendor felt like returning." },
{ rule: 24, test: /over 4x|non-monotone/, slug: "limit-does-not-bound-the-answer", mechanism: "The answer does not shrink when `limit` shrinks — the argument is accepted and ignored, so an AI that asks for one row pays for all of them." },
{ rule: 31, test: /outcome\/code\/message\/fix/, slug: "refusal-envelope-incomplete", mechanism: "A refusal arrives without the full outcome/code/message/fix envelope, so the caller is told it failed but not what to do next." },
{ rule: 22, test: /exceeded 50 ms|spawned/, slug: "no-preflight-on-missing-credential", mechanism: "A missing credential is discovered by a spawned child process instead of by preflight, so a refusal that should cost nothing costs seconds." },
{ rule: 13, test: /missing description/, slug: "argument-without-a-description", mechanism: "Arguments carry no descriptions in inputSchema, so the model guesses their meaning from the name alone." },
{ rule: 18, test: /missing class/, slug: "verb-without-an-effect-class", mechanism: "Verbs declare no effect class, so nothing downstream can derive annotations or decide what must stage." },
{ rule: 19, test: /annotation defaults mismatch/, slug: "annotations-not-derived-from-class", mechanism: "Published MCP annotations were guessed rather than derived from the declared effect class." },
{ rule: 54, test: /ready=true/, slug: "ready-without-evals", mechanism: "A skill declares itself ready with fewer than three eval scenarios — a status truer than the artifact behind it." },
{ rule: 50, test: /auto_fit/, slug: "face-family-does-not-fit", mechanism: "A face family fails to fit at one of the measured widths, or draws a scroller inside itself." },
{ rule: 51, test: /action_item_bounds/, slug: "decision-row-out-of-bounds", mechanism: "A decision face draws more primary actions than the guideline allows." },
{ rule: 53, test: /dated_real_host_screenshot/, slug: "face-family-has-no-screenshot", mechanism: "A published face family has no dated screenshot from the real bundle in a real host." },
];
export function clusterKey(rule: RuleResult): { key: string; mechanism: string } {
for (const signature of SIGNATURES) {
if (signature.rule === rule.id && signature.test.test(rule.detail)) {
return { key: `r${rule.id}-${signature.slug}`, mechanism: signature.mechanism };
}
}
const slug = rule.rule.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
return { key: `r${rule.id}-${slug}`, mechanism: rule.rule };
}
/** One cluster per mechanism, ranked by the defects a single fix would kill. */
export function clusterFailures(reports: LintResult[]): Cluster[] {
const byKey = new Map<string, Cluster>();
for (const report of reports) {
for (const rule of report.rules) {
if (rule.status !== "FAIL") continue;
const { key, mechanism } = clusterKey(rule);
const cluster = byKey.get(key) ?? { key, kind: "lint" as const, rule: rule.id, mechanism, mode: rule.mode, skills: [], evidence: [], anchors: [] };
cluster.skills.push(report.skill);
cluster.evidence.push({ skill: report.skill, detail: rule.detail });
byKey.set(key, cluster);
}
}
return [...byKey.values()].sort((a, b) => b.skills.length - a.skills.length || a.rule - b.rule);
}
/** The file:line a lane opens first. Derived, never guessed: the contract line. */
export function anchorFor(skillsRoot: string, skill: string, detail: string): string {
const path = join(skillsRoot, skill, "api.ts");
if (!existsSync(path)) return `${skill}/api.ts (absent)`;
const verb = detail.match(/^([a-z][a-z0-9_-]*)\(/)?.[1] ?? detail.match(/:\s*([a-z][a-z0-9_-]*)/)?.[1];
const lines = readFileSync(path, "utf8").split("\n");
const wanted = verb ? new RegExp(`(^|\\s|")${verb}(\"|'|\\s|:)`) : /HAND_CONTRACT/;
const index = lines.findIndex((line) => wanted.test(line));
return `${path}:${index >= 0 ? index + 1 : 1}`;
}
const PROHIBITIONS = [
"NEVER install on any Mac, deploy, or push.",
"NEVER send, post, spend or delete through any hand — reads only.",
"NEVER edit a skill outside the list of files named below.",
"`git commit --only <explicit paths>`; never `git add -A`; never `git stash`.",
"nice -n 19 every compile; no `tsc --watch`.",
];
export function briefFor(cluster: Cluster, skillsRoot: string): string {
if (cluster.kind === "person") return personBriefFor(cluster, skillsRoot);
const sample = cluster.evidence.slice(0, 12);
const anchors = sample.map((item) => `- \`${anchorFor(skillsRoot, item.skill, item.detail)}\` — ${item.skill}`);
return `# Lane brief — ${cluster.key}
**Rule ${cluster.rule}** · evidence mode \`${cluster.mode}\` · **${cluster.skills.length} skill(s) share this one mechanism**
## Role and prohibitions
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
${PROHIBITIONS.map((line) => `- ${line}`).join("\n")}
## The mechanism
${cluster.mechanism}
This is ONE root cause across ${cluster.skills.length} skill(s). Fix the mechanism, not the
${cluster.skills.length} symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered ${cluster.skills.length}.
## The measured evidence, verbatim from \`lint --all\`
\`\`\`
${sample.map((item) => `${item.skill}: ${item.detail}`).join("\n")}${cluster.evidence.length > sample.length ? `\n… and ${cluster.evidence.length - sample.length} more` : ""}
\`\`\`
## Anchors
${anchors.join("\n")}
## The anti-fix — what would look like progress and is not
- Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves.
- Repairing the prose in SKILL.md while the contract that generates it stays wrong.
- Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green.
- Fixing the alphabetically first skill and reporting the cluster closed.
## The proof required
1. \`npx tsx skills/snappy-tool-design/api.ts lint --all --summary\` before and after, with rule ${cluster.rule}'s FAIL count falling from ${cluster.skills.length}.
2. The full \`lint <skill>\` line for two skills in the cluster, showing rule ${cluster.rule} PASS.
3. \`SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh\` still passing.
4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
`;
}
/**
* A LANE BRIEF FROM A PERSON'S LINE. It is a different document from the lint
* brief and not a variant of it, because the two say opposite things about what
* "done" means: a lint brief proves itself by a rule's FAIL count falling, and
* there is no count here — the proof is that the thing he asked for is TRUE ON
* HIS GLASS when he next opens the card.
*
* HIS WORDS LEAD AND ARE QUOTED, never paraphrased and never "interpreted as".
* The anti-fix section exists for the same reason it does in the lint brief:
* the failure this catches is a lane that writes a note back to him instead of
* changing the hand.
*/
export function personBriefFor(cluster: Cluster, skillsRoot: string): string {
const skill = cluster.skills[0]!;
const path = join(skillsRoot, skill, "api.ts");
return `# Lane brief — ${cluster.key}
**A PERSON ASKED FOR THIS.** ${cluster.evidence.length} note(s) on the \`${skill}\` card, newest first.
It outranks every rule in this ledger: a linter finds what a rule can see, and he
found what using it feels like.
## What he said, verbatim
${cluster.evidence.map((item) => `> ${item.detail}`).join("\n>\n")}
## Role and prohibitions
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is
\`skills/${skill}/\` and nothing else.
${PROHIBITIONS.map((line) => `- ${line}`).join("\n")}
## Where to start
- \`${existsSync(path) ? path : `${path} (absent — this hand has no api.ts on this body)`}\`
- \`${join(skillsRoot, skill, "SKILL.md")}\` — the words the MCP publishes about it.
## The anti-fix — what would look like progress and is not
- Replying to him. He asked for the hand to change, not for an answer.
- Restating his line in the SKILL.md as a "known limitation".
- Fixing a NEIGHBOURING thing that was easier and reporting his ask closed.
- Deciding his words meant something else. If they are genuinely ambiguous, say
so in the report with the two readings — never pick one silently.
## The proof required
1. The change, on \`${skill}\`, with the file and the line.
2. The verb driven once after it, with its real answer pasted.
3. \`SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh\` still passing.
4. One sentence a person would recognise as an answer to the line above.
`;
}
export function ledger(clusters: Cluster[], summary: Record<string, number>, hostsBlock = ""): string {
// HIS ASKS FIRST, ALWAYS — not by score, by rank. A person's ask sorted
// against defect counts would fall below any rule that happened to fire on
// four hands, which is the ledger quietly deciding a linter outranks him.
const ranked = [...clusters.filter((c) => c.kind === "person"), ...clusters.filter((c) => c.kind !== "person")];
const rows = ranked.map((cluster, index) => cluster.kind === "person"
? `| ${index + 1} | \`${cluster.key}\` | **HE ASKED** | a person typed this on the card | **${cluster.evidence.length}** | ${cluster.skills[0]} — “${cluster.mechanism.replace(/\|/gu, "\\|").slice(0, 90)}${cluster.mechanism.length > 90 ? "…" : ""}” |`
: `| ${index + 1} | \`${cluster.key}\` | R${cluster.rule} | ${cluster.mode} | **${cluster.skills.length}** | ${cluster.skills.slice(0, 4).join(", ")}${cluster.skills.length > 4 ? ", …" : ""} |`);
const asked = ranked.filter((c) => c.kind === "person").length;
return `# tool-design LEDGER — ranked by defects one fix kills
Generated ${new Date().toISOString()} by \`api.ts loop\`.
Collection: ${summary.skills} skills · ${summary.pass} PASS · ${summary.fail} FAIL · ${summary.defer} DEFER across ${summary.skills * 60} gradings.
**Re-run after every landing — that is the exponential part:**
\`\`\`bash
SNAPPY_RUNNER_TOKEN_FILE=<600-mode token file> npx tsx skills/snappy-tool-design/api.ts loop --via mini
\`\`\`
Each landing removes a whole mechanism, so the next \`loop\` ranks a shorter list
whose top row is worth more than the row below it was yesterday.
${asked === 0 ? "" : `**${asked} row(s) at the top are things a PERSON asked for on a card.** They are
ranked above every rule below them because a rule finds what a rule can see and
he found what using it feels like. Take them first.
`}| # | cluster | rule | evidence | defects killed | skills |
|---:|---|---|---|---:|---|
${rows.join("\n")}
Briefs for every row are in \`briefs/<cluster>.md\`, ready to hand a lane.
${hostsBlock}`;
}
export function writeLoopArtifacts(
reports: LintResult[],
outDir: string,
skillsRoot: string,
asks: PersonAsk[] = [],
): { clusters: Cluster[]; ledgerPath: string } {
// THE PERSON'S ASKS ARE PART OF THE SAME LIST, put in front of it. One
// ledger, one briefs directory, one re-run — a second road for his words
// would be the road that goes stale first.
const clusters = [...personClusters(asks), ...clusterFailures(reports)];
mkdirSync(join(outDir, "briefs"), { recursive: true });
for (const cluster of clusters) {
writeFileSync(join(outDir, "briefs", `${cluster.key}.md`), briefFor(cluster, skillsRoot));
}
const summary = {
skills: reports.length,
pass: reports.reduce((sum, r) => sum + r.pass, 0),
fail: reports.reduce((sum, r) => sum + r.fail, 0),
defer: reports.reduce((sum, r) => sum + r.defer, 0),
};
const ledgerPath = join(outDir, "LEDGER.md");
writeFileSync(ledgerPath, ledger(clusters, summary, hostsBlockFor(outDir)));
return { clusters, ledgerPath };
}
/**
* THE HOSTS ROW IN THE LOOP'S LEDGER — the same road, not a second one.
*
* `hosts` is a verb on this loop rather than its own program because the loop
* IS the thing that turns measurement into ranked work, and a second ledger
* beside this one would rank the same defects twice and stop agreeing the
* first time either moved ⟨CLAUDE.md §4⟩. So the battery writes its corpus and
* its block, and `ledger()` folds the block in when one exists.
*/
export async function runHostsCommand(args: string[], deps: { skillsRoot: string }): Promise<void> {
const here = join(deps.skillsRoot, "snappy-tool-design");
const { runHosts } = await import("./hosts-run.ts");
const tokenFile = flag(args, "--token-file") ?? process.env.SNAPPY_RUNNER_TOKEN_FILE;
if (!tokenFile) throw new Error("hosts needs a bearer: --token-file, or SNAPPY_RUNNER_TOKEN_FILE, a 600-mode file holding the runner token");
const only = flag(args, "--only")?.split(",").map((word) => word.trim()).filter(Boolean);
await runHosts({
skillsRoot: deps.skillsRoot,
facesSkillDir: join(deps.skillsRoot, "snappy-faces"),
runnerUrl: flag(args, "--url") ?? "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
runnerFaceTs: flag(args, "--runner-face") ?? "/Users/robertboulos/Projects/snappy-runner/src/face.ts",
tokenFile,
outDir: flag(args, "--out") ?? here,
only: only as never,
codexModel: flag(args, "--codex-model"),
barPort: flag(args, "--bar-port") === undefined ? undefined : Number(flag(args, "--bar-port")),
// `--already <prompt>=<message_id>` — a card a previous run put on a host
// and this one must not duplicate. The date is the run's, so the cell can
// say WHEN the artifact was made rather than implying it was made now.
already: Object.fromEntries((flag(args, "--already") ?? "").split(",").filter(Boolean).map((pair) => {
const [prompt, id] = pair.split("=");
return [prompt.trim(), { message_id: Number(id), when: flag(args, "--already-when") ?? "earlier" }];
})),
});
}
/** The hosts block, when the battery has run. Absent is absent — never a zero. */
export function hostsBlockFor(dir: string): string {
const path = join(dir, "HOSTS-LEDGER.md");
return existsSync(path) ? `\n\n---\n\n${readFileSync(path, "utf8")}` : "";
}
export function flag(args: string[], name: string): string | undefined {
const at = args.indexOf(name);
return at >= 0 ? args[at + 1] : undefined;
}
/** THE CLOSED LOOP. probe → render → lint → cluster → briefs → ledger. */
export async function runLoopCommand(
command: string,
args: string[],
deps: { skillsRoot: string; lintAll: () => Promise<LintResult[]>; printAll: (reports: LintResult[], summary?: boolean) => void },
): Promise<void> {
const { skillsRoot: SKILLS_ROOT, lintAll, printAll } = deps;
const here = join(SKILLS_ROOT, "snappy-tool-design");
const via = (flag(args, "--via") ?? "mini") as "mini" | "public";
const out = flag(args, "--out");
if (command === "probe" || (command === "loop" && !args.includes("--skip-probe"))) {
const { runProbe } = await import("./probe.ts");
const skill = command === "probe" ? args.find((arg, index) => !arg.startsWith("--") && !args[index - 1]?.startsWith("--")) : undefined;
const hands = skill ? undefined : collectionNames(SKILLS_ROOT);
const file = await runProbe({ via, skill, hands, tokenFile: flag(args, "--token-file") });
const path = (command === "probe" && out) || join(here, PROBE_FILE);
writeFileSync(path, JSON.stringify(file, null, 1));
console.log(`probe → ${path}: ${Object.keys(file.skills).length} skill(s), ${JSON.stringify(file.server.run)}`);
}
if (command === "render" || (command === "loop" && !args.includes("--skip-render"))) {
const { runRender } = await import("./render.ts");
const hostDir = flag(args, "--host-dir");
if (!hostDir) throw new Error("render needs --host-dir (the loop directory holding host.html, host-bundle.js, static.mjs)");
// `--kind a,b` draws just those kinds. Reproducing one THREW row cost a
// full eight-minute pass over 97 kinds, which also could not be run beside
// a pass already in flight ⟨lane render-harness, 2026-09-09⟩. A partial run
// writes to `--out`; it must not overwrite the collection's render file,
// because rules 50–53 would then read four families instead of forty.
const kinds = (flag(args, "--kind") ?? "").split(",").map((k) => k.trim()).filter(Boolean);
if (kinds.length && command === "render" && !out) throw new Error("render --kind needs --out; a partial run must not overwrite the collection's render file");
const file = await runRender({ hostDir, shotDir: flag(args, "--shot-dir") ?? join(hostDir, "shots"), ...(kinds.length ? { kinds } : {}) });
const path = (command === "render" && out) || join(here, RENDER_FILE);
writeFileSync(path, JSON.stringify(file, null, 1));
console.log(`render → ${path}: ${file.kinds.length} kind(s), famil(ies) ${Object.keys(file.families).join(", ")}`);
}
if (command !== "loop") return;
// WHAT A PERSON ASKED FOR, BEFORE ANYTHING IS RANKED. Local by default —
// the file the runner on THIS Mac writes. `--fix-requests-from mini` reads
// the mini's own door over the road `probe --via mini` already uses (its URL
// with `/mcp` swapped for `/fix-requests`, the same `SNAPPY_RUNNER_TOKEN_FILE`
// bearer), because his notes are typed wherever the bar is running and the
// loop is often not run there.
//
// `--fix-requests-from both` IS THE ROAD BETWEEN THE TWO MACS, and it is the
// only one there is: nothing syncs `fix-requests.jsonl` ⟨measured 2026-09-09,
// lane menu-bar-13 — the runner's `update` pulls git and this file is runtime
// state, so a note typed on the bar here has never been visible to a loop run
// on the mini⟩. Copying the file would make a second writer of a directory
// `store.ts` owns, which is the duplicate road CLAUDE.md §4 bans by name. So
// the file does not travel — the READER visits both bodies, each through the
// door that body already answers, and folds what comes back.
const from = flag(args, "--fix-requests-from") ?? "local";
const asks = await readAsks(bodiesFor(from), {
local: () => readFixRequests(flag(args, "--fix-requests") ?? fixRequestsPath()),
mini: () => fetchFixRequests({
url: flag(args, "--url") ?? "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
tokenFile: flag(args, "--token-file") ?? process.env.SNAPPY_RUNNER_TOKEN_FILE
?? (() => { throw new Error("--fix-requests-from mini needs a bearer: --token-file, or SNAPPY_RUNNER_TOKEN_FILE"); })(),
}),
});
const reports = await lintAll();
printAll(reports, true);
const { clusters, ledgerPath } = writeLoopArtifacts(reports, out ?? here, SKILLS_ROOT, asks);
console.log(`loop → ${clusters.length} cluster(s); ledger ${ledgerPath}`);
for (const cluster of clusters.slice(0, 10)) {
console.log(cluster.kind === "person"
? ` ASK × ${cluster.key} — ${cluster.mechanism.slice(0, 70)}`
: ` ${String(cluster.skills.length).padStart(3)} × ${cluster.key}`);
}
}
export function collectionNames(SKILLS_ROOT: string): string[] {
return readdirSync(SKILLS_ROOT, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith("snappy-") && existsSync(join(SKILLS_ROOT, entry.name, "api.ts")))
.map((entry) => entry.name).sort();
}
/**
* THE LOOP — probe, render, lint, then turn what failed into WORK.
*
* The owner's order on 2026-09-09: "you need a closed feedback loop of using
* every part of this — remember this needs to be exponential." A list of 900
* rule failures is not exponential; it is a list. What compounds is a list of
* MECHANISMS, because one mechanism fixed kills every defect that shares it.
* Rule 59 is the worked example: six skills, one grammar mistake, one lane.
*
* So `loop` clusters by mechanism, not by skill, ranks by how many defects one
* fix kills, writes a lane brief per cluster and a ranked LEDGER.md, and is
* re-run after each landing. The count going down IS the measurement.
*/
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { PROBE_FILE, RENDER_FILE } from "./corpus.ts";
import type { LintResult, RuleResult } from "./api.ts";
export interface Cluster {
key: string;
/** WHERE THIS CLUSTER CAME FROM, and therefore how it is ranked and how its
* brief reads. `"person"` is a line somebody typed on a skill card;
* `"lint"` is a rule the linter found. They are one type because they are
* one queue — a second ledger for the person's asks would rank the same
* work twice and stop agreeing the first time either moved ⟨CLAUDE.md §4⟩. */
kind: "person" | "lint";
/** The lint rule, or 0 for a person's ask — his words answer to no rule. */
rule: number;
mechanism: string;
mode: string;
skills: string[];
evidence: Array<{ skill: string; detail: string }>;
anchors: string[];
}
/* ── THE PERSON'S OWN WORDS OUTRANK EVERY RULE ───────────────────────────────
*
* ⟨owner, 2026-09-09 01:4x: "a fix field that files a lane"⟩. Someone looking
* at a hand's card in the menu bar types one line about what it should do
* better and presses Note it. The bar posts it to the runner's
* `POST /fix-request` door, the runner appends it through the ONE writer in
* `src/store.ts`, and this file reads the file that door writes.
*
* THE PATH IS A CONVENTION, NOT AN IMPORT. This repo is the runner's `bundle/`
* submodule and cannot import from the superproject, so the two ends agree on
* `${SNAPPY_SKILLS_HOME:-~/.snappy-skills}/fix-requests.jsonl` and both say so
* where a reader will look: `store.ts`'s own header, and SKILL.md's
* "THE PERSON'S FIX FIELD" section. A constant copied into two files that
* disagree is a road that goes silently blank, so the DEFAULT and the ENV
* VARIABLE are both matched here, exactly as the runner resolves them.
*
* THE MINI IS READ THROUGH THE ROAD THAT ALREADY EXISTS. `probe --via mini`
* dials `MINI_MCP_URL` with a bearer from `SNAPPY_RUNNER_TOKEN_FILE`
* (`probe.ts:190,192`), and `GET /fix-requests?since=` is a door on that same
* port behind that same bearer — so `--fix-requests-from mini` swaps `/mcp`
* for `/fix-requests` on that URL and presents the same token file. There is no
* second credential and no ssh.
*
* HIS WORDS ARE NEVER RE-WORDED. The line goes into the brief verbatim as the
* mechanism. A summary of a correction is my sentence standing where his was.
*/
export interface PersonAsk {
at: string;
skill: string;
words: string;
verb: string | null;
face_kind: string | null;
revision: string | null;
}
/** The one path both ends resolve, by convention, the same way. */
export function fixRequestsPath(env: NodeJS.ProcessEnv = process.env): string {
const home = env.SNAPPY_SKILLS_HOME !== undefined && env.SNAPPY_SKILLS_HOME !== ""
? env.SNAPPY_SKILLS_HOME
: join(homedir(), ".snappy-skills");
return join(home, "fix-requests.jsonl");
}
/** Whatever the file holds, oldest first, skipping any half-written line — the
* writer is a live appender and a reader that threw on one bad line would
* throw away every good one behind it. */
export function readFixRequests(path = fixRequestsPath()): PersonAsk[] {
let text: string;
try { text = readFileSync(path, "utf8"); } catch { return []; }
const asks: PersonAsk[] = [];
for (const raw of text.split("\n")) {
if (raw.trim() === "") continue;
let line: Partial<PersonAsk>;
try { line = JSON.parse(raw) as Partial<PersonAsk>; } catch { continue; }
if (typeof line.skill !== "string" || line.skill === "") continue;
if (typeof line.words !== "string" || line.words.trim() === "") continue;
asks.push({
at: typeof line.at === "string" ? line.at : "",
skill: line.skill,
words: line.words.trim(),
verb: typeof line.verb === "string" && line.verb !== "" ? line.verb : null,
face_kind: typeof line.face_kind === "string" && line.face_kind !== "" ? line.face_kind : null,
revision: typeof line.revision === "string" && line.revision !== "" ? line.revision : null,
});
}
return asks;
}
/** The mini's own file, over the door that already exists on the probe's road.
* A body that cannot be reached is reported and returns nothing — a silent
* empty list would read as "he has asked for nothing". */
export async function fetchFixRequests(options: { url: string; tokenFile: string; since?: string }): Promise<PersonAsk[]> {
const bearer = readFileSync(options.tokenFile, "utf8").trim();
const door = `${options.url.replace(/\/mcp$/u, "")}/fix-requests?since=${encodeURIComponent(options.since ?? "")}`;
const answer = await fetch(door, { headers: { authorization: `Bearer ${bearer}` } });
if (!answer.ok) {
throw new Error(`${door} answered HTTP ${answer.status}. A 404 means the body on that Mac is older than this loop — it has no fix-request door yet.`);
}
const body = (await answer.json()) as { requests?: unknown };
return Array.isArray(body.requests) ? (body.requests as PersonAsk[]).filter((r) => typeof r?.skill === "string" && typeof r?.words === "string") : [];
}
/** The Macs a note can have been typed on. There is no third: the bar runs on
* one of these two and each one's runner owns its own file. */
export type AskBody = "local" | "mini";
/** What `--fix-requests-from` names, as bodies to visit. */
export function bodiesFor(from: string): AskBody[] {
return from === "both" ? ["local", "mini"] : from === "mini" ? ["mini"] : ["local"];
}
/**
* EVERY NAMED BODY'S NOTES, IN ONE LIST — the one reader, so a loop run on
* either Mac ranks what he typed on the other.
*
* EACH BODY IS READ ON ITS OWN, and that is the whole reason this is a function
* and not a `Promise.all`. A shared try/catch — which is what this replaced —
* lets an unreachable mini throw away every note typed on THIS Mac, and the
* ledger then prints "he has asked for nothing" over a file holding his words:
* a status truer than its artifact, on the one row that outranks all the rest.
* A body that fails is named out loud, by name, and the others still stand.
*
* DUPLICATES FOLD ON `at` + `skill` + `words`, because `both` reached from the
* mini itself reads one file twice — once by path, once through its own door —
* and one sentence counted twice would rank a hand above a hand he wrote about
* more.
*/
export async function readAsks(
bodies: AskBody[],
read: { local: () => PersonAsk[]; mini: () => Promise<PersonAsk[]> },
say: (line: string) => void = console.log,
): Promise<PersonAsk[]> {
const seen = new Set<string>();
const asks: PersonAsk[] = [];
for (const body of bodies) {
try {
const got = body === "local" ? read.local() : await read.mini();
let added = 0;
for (const ask of got) {
const key = `${ask.at} ${ask.skill} ${ask.words}`;
if (seen.has(key)) continue;
seen.add(key);
asks.push(ask);
added += 1;
}
say(`fix-requests (${body}) → ${added} note(s) a person typed on a card`);
} catch (error) {
say(`fix-requests (${body}) → COULD NOT BE READ: ${error instanceof Error ? error.message : String(error)}`);
say(` no ask from ${body} is ranked below — that is an absence, not a zero.`);
}
}
return asks;
}
/**
* ONE CLUSTER PER SKILL HE WROTE ABOUT, newest first inside it. Per SKILL and
* not per LINE, because two notes about the same hand are one lane: whoever
* opens it should read everything he has said about that hand before touching
* it. The mechanism is HIS most recent sentence, verbatim.
*/
export function personClusters(asks: PersonAsk[]): Cluster[] {
const bySkill = new Map<string, PersonAsk[]>();
for (const ask of asks) bySkill.set(ask.skill, [...(bySkill.get(ask.skill) ?? []), ask]);
const clusters: Cluster[] = [];
for (const [skill, filed] of bySkill) {
const newestFirst = [...filed].sort((a, b) => (b.at ?? "").localeCompare(a.at ?? ""));
clusters.push({
key: `person-${skill}`,
kind: "person",
rule: 0,
mechanism: newestFirst[0]!.words,
mode: "a person typed this on the card",
skills: [skill],
evidence: newestFirst.map((ask) => ({
skill,
detail: [ask.at === "" ? null : ask.at, ask.verb === null ? null : `verb ${ask.verb}`, ask.face_kind === null ? null : `face ${ask.face_kind}`, ask.words]
.filter((part) => part !== null).join(" · "),
})),
anchors: [],
});
}
// Most-asked-about hand first, then the one he wrote about most recently.
return clusters.sort((a, b) => b.evidence.length - a.evidence.length || b.evidence[0]!.detail.localeCompare(a.evidence[0]!.detail));
}
/**
* THE MECHANISM NAMES. A cluster key must be the same for two skills that
* share a root cause and different for two that do not, so the key is derived
* from the rule plus a normalized signature of what the detail SAID — never
* from the skill's name, which is what would split one mechanism into ninety.
*/
const SIGNATURES: Array<{ rule: number; test: RegExp; slug: string; mechanism: string }> = [
{ rule: 59, test: /before/, slug: "optional-positional-before-the-count", mechanism: "An optional positional argument stands in front of `limit`, so a bare count lands in the wrong slot and the first call an AI writes answers about the wrong thing." },
{ rule: 60, test: /carries no generated/, slug: "loader-block-absent", mechanism: "AGENTS.md carries no generated contract block, so the prompt half of the MCP projection teaches a first call from prose instead of from the contract." },
{ rule: 60, test: /stale/, slug: "loader-block-stale", mechanism: "The generated loader block has drifted from HAND_CONTRACT, so every reader gets a first call the contract no longer honours." },
{ rule: 6, test: /differs from contract/, slug: "description-not-projected", mechanism: "The SKILL.md description is a hand-written copy rather than a projection of HAND_CONTRACT.description, so the two drift and the MCP publishes the stale one." },
{ rule: 17, test: /limit default\/maximum missing/, slug: "unbounded-collection-read", mechanism: "A collection read declares no limit default and no maximum, so the answer's size is whatever the vendor felt like returning." },
{ rule: 24, test: /over 4x|non-monotone/, slug: "limit-does-not-bound-the-answer", mechanism: "The answer does not shrink when `limit` shrinks — the argument is accepted and ignored, so an AI that asks for one row pays for all of them." },
{ rule: 31, test: /outcome\/code\/message\/fix/, slug: "refusal-envelope-incomplete", mechanism: "A refusal arrives without the full outcome/code/message/fix envelope, so the caller is told it failed but not what to do next." },
{ rule: 22, test: /exceeded 50 ms|spawned/, slug: "no-preflight-on-missing-credential", mechanism: "A missing credential is discovered by a spawned child process instead of by preflight, so a refusal that should cost nothing costs seconds." },
{ rule: 13, test: /missing description/, slug: "argument-without-a-description", mechanism: "Arguments carry no descriptions in inputSchema, so the model guesses their meaning from the name alone." },
{ rule: 18, test: /missing class/, slug: "verb-without-an-effect-class", mechanism: "Verbs declare no effect class, so nothing downstream can derive annotations or decide what must stage." },
{ rule: 19, test: /annotation defaults mismatch/, slug: "annotations-not-derived-from-class", mechanism: "Published MCP annotations were guessed rather than derived from the declared effect class." },
{ rule: 54, test: /ready=true/, slug: "ready-without-evals", mechanism: "A skill declares itself ready with fewer than three eval scenarios — a status truer than the artifact behind it." },
{ rule: 50, test: /auto_fit/, slug: "face-family-does-not-fit", mechanism: "A face family fails to fit at one of the measured widths, or draws a scroller inside itself." },
{ rule: 51, test: /action_item_bounds/, slug: "decision-row-out-of-bounds", mechanism: "A decision face draws more primary actions than the guideline allows." },
{ rule: 53, test: /dated_real_host_screenshot/, slug: "face-family-has-no-screenshot", mechanism: "A published face family has no dated screenshot from the real bundle in a real host." },
];
export function clusterKey(rule: RuleResult): { key: string; mechanism: string } {
for (const signature of SIGNATURES) {
if (signature.rule === rule.id && signature.test.test(rule.detail)) {
return { key: `r${rule.id}-${signature.slug}`, mechanism: signature.mechanism };
}
}
const slug = rule.rule.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
return { key: `r${rule.id}-${slug}`, mechanism: rule.rule };
}
/** One cluster per mechanism, ranked by the defects a single fix would kill. */
export function clusterFailures(reports: LintResult[]): Cluster[] {
const byKey = new Map<string, Cluster>();
for (const report of reports) {
for (const rule of report.rules) {
if (rule.status !== "FAIL") continue;
const { key, mechanism } = clusterKey(rule);
const cluster = byKey.get(key) ?? { key, kind: "lint" as const, rule: rule.id, mechanism, mode: rule.mode, skills: [], evidence: [], anchors: [] };
cluster.skills.push(report.skill);
cluster.evidence.push({ skill: report.skill, detail: rule.detail });
byKey.set(key, cluster);
}
}
return [...byKey.values()].sort((a, b) => b.skills.length - a.skills.length || a.rule - b.rule);
}
/** The file:line a lane opens first. Derived, never guessed: the contract line. */
export function anchorFor(skillsRoot: string, skill: string, detail: string): string {
const path = join(skillsRoot, skill, "api.ts");
if (!existsSync(path)) return `${skill}/api.ts (absent)`;
const verb = detail.match(/^([a-z][a-z0-9_-]*)\(/)?.[1] ?? detail.match(/:\s*([a-z][a-z0-9_-]*)/)?.[1];
const lines = readFileSync(path, "utf8").split("\n");
const wanted = verb ? new RegExp(`(^|\\s|")${verb}(\"|'|\\s|:)`) : /HAND_CONTRACT/;
const index = lines.findIndex((line) => wanted.test(line));
return `${path}:${index >= 0 ? index + 1 : 1}`;
}
const PROHIBITIONS = [
"NEVER install on any Mac, deploy, or push.",
"NEVER send, post, spend or delete through any hand — reads only.",
"NEVER edit a skill outside the list of files named below.",
"`git commit --only <explicit paths>`; never `git add -A`; never `git stash`.",
"nice -n 19 every compile; no `tsc --watch`.",
];
export function briefFor(cluster: Cluster, skillsRoot: string): string {
if (cluster.kind === "person") return personBriefFor(cluster, skillsRoot);
const sample = cluster.evidence.slice(0, 12);
const anchors = sample.map((item) => `- \`${anchorFor(skillsRoot, item.skill, item.detail)}\` — ${item.skill}`);
return `# Lane brief — ${cluster.key}
**Rule ${cluster.rule}** · evidence mode \`${cluster.mode}\` · **${cluster.skills.length} skill(s) share this one mechanism**
## Role and prohibitions
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is the
skills named under "Anchors" and nothing else.
${PROHIBITIONS.map((line) => `- ${line}`).join("\n")}
## The mechanism
${cluster.mechanism}
This is ONE root cause across ${cluster.skills.length} skill(s). Fix the mechanism, not the
${cluster.skills.length} symptoms: a change that repairs one skill and leaves the shape in place
has killed one defect where the cluster offered ${cluster.skills.length}.
## The measured evidence, verbatim from \`lint --all\`
\`\`\`
${sample.map((item) => `${item.skill}: ${item.detail}`).join("\n")}${cluster.evidence.length > sample.length ? `\n… and ${cluster.evidence.length - sample.length} more` : ""}
\`\`\`
## Anchors
${anchors.join("\n")}
## The anti-fix — what would look like progress and is not
- Editing the lint so the rule stops firing. The rule is sourced; the contract is what moves.
- Repairing the prose in SKILL.md while the contract that generates it stays wrong.
- Pinning the count in a registry so the gate goes quiet. A pinned count is debt with a receipt, never green.
- Fixing the alphabetically first skill and reporting the cluster closed.
## The proof required
1. \`npx tsx skills/snappy-tool-design/api.ts lint --all --summary\` before and after, with rule ${cluster.rule}'s FAIL count falling from ${cluster.skills.length}.
2. The full \`lint <skill>\` line for two skills in the cluster, showing rule ${cluster.rule} PASS.
3. \`SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh\` still passing.
4. No other rule's FAIL count rising — paste the whole rule table, not the one row.
`;
}
/**
* A LANE BRIEF FROM A PERSON'S LINE. It is a different document from the lint
* brief and not a variant of it, because the two say opposite things about what
* "done" means: a lint brief proves itself by a rule's FAIL count falling, and
* there is no count here — the proof is that the thing he asked for is TRUE ON
* HIS GLASS when he next opens the card.
*
* HIS WORDS LEAD AND ARE QUOTED, never paraphrased and never "interpreted as".
* The anti-fix section exists for the same reason it does in the lint brief:
* the failure this catches is a lane that writes a note back to him instead of
* changing the hand.
*/
export function personBriefFor(cluster: Cluster, skillsRoot: string): string {
const skill = cluster.skills[0]!;
const path = join(skillsRoot, skill, "api.ts");
return `# Lane brief — ${cluster.key}
**A PERSON ASKED FOR THIS.** ${cluster.evidence.length} note(s) on the \`${skill}\` card, newest first.
It outranks every rule in this ledger: a linter finds what a rule can see, and he
found what using it feels like.
## What he said, verbatim
${cluster.evidence.map((item) => `> ${item.detail}`).join("\n>\n")}
## Role and prohibitions
You are a BUILD LANE in a worktree of the snappy-kernel. Your territory is
\`skills/${skill}/\` and nothing else.
${PROHIBITIONS.map((line) => `- ${line}`).join("\n")}
## Where to start
- \`${existsSync(path) ? path : `${path} (absent — this hand has no api.ts on this body)`}\`
- \`${join(skillsRoot, skill, "SKILL.md")}\` — the words the MCP publishes about it.
## The anti-fix — what would look like progress and is not
- Replying to him. He asked for the hand to change, not for an answer.
- Restating his line in the SKILL.md as a "known limitation".
- Fixing a NEIGHBOURING thing that was easier and reporting his ask closed.
- Deciding his words meant something else. If they are genuinely ambiguous, say
so in the report with the two readings — never pick one silently.
## The proof required
1. The change, on \`${skill}\`, with the file and the line.
2. The verb driven once after it, with its real answer pasted.
3. \`SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh\` still passing.
4. One sentence a person would recognise as an answer to the line above.
`;
}
export function ledger(clusters: Cluster[], summary: Record<string, number>, hostsBlock = ""): string {
// HIS ASKS FIRST, ALWAYS — not by score, by rank. A person's ask sorted
// against defect counts would fall below any rule that happened to fire on
// four hands, which is the ledger quietly deciding a linter outranks him.
const ranked = [...clusters.filter((c) => c.kind === "person"), ...clusters.filter((c) => c.kind !== "person")];
const rows = ranked.map((cluster, index) => cluster.kind === "person"
? `| ${index + 1} | \`${cluster.key}\` | **HE ASKED** | a person typed this on the card | **${cluster.evidence.length}** | ${cluster.skills[0]} — “${cluster.mechanism.replace(/\|/gu, "\\|").slice(0, 90)}${cluster.mechanism.length > 90 ? "…" : ""}” |`
: `| ${index + 1} | \`${cluster.key}\` | R${cluster.rule} | ${cluster.mode} | **${cluster.skills.length}** | ${cluster.skills.slice(0, 4).join(", ")}${cluster.skills.length > 4 ? ", …" : ""} |`);
const asked = ranked.filter((c) => c.kind === "person").length;
return `# tool-design LEDGER — ranked by defects one fix kills
Generated ${new Date().toISOString()} by \`api.ts loop\`.
Collection: ${summary.skills} skills · ${summary.pass} PASS · ${summary.fail} FAIL · ${summary.defer} DEFER across ${summary.skills * 60} gradings.
**Re-run after every landing — that is the exponential part:**
\`\`\`bash
SNAPPY_RUNNER_TOKEN_FILE=<600-mode token file> npx tsx skills/snappy-tool-design/api.ts loop --via mini
\`\`\`
Each landing removes a whole mechanism, so the next \`loop\` ranks a shorter list
whose top row is worth more than the row below it was yesterday.
${asked === 0 ? "" : `**${asked} row(s) at the top are things a PERSON asked for on a card.** They are
ranked above every rule below them because a rule finds what a rule can see and
he found what using it feels like. Take them first.
`}| # | cluster | rule | evidence | defects killed | skills |
|---:|---|---|---|---:|---|
${rows.join("\n")}
Briefs for every row are in \`briefs/<cluster>.md\`, ready to hand a lane.
${hostsBlock}`;
}
export function writeLoopArtifacts(
reports: LintResult[],
outDir: string,
skillsRoot: string,
asks: PersonAsk[] = [],
): { clusters: Cluster[]; ledgerPath: string } {
// THE PERSON'S ASKS ARE PART OF THE SAME LIST, put in front of it. One
// ledger, one briefs directory, one re-run — a second road for his words
// would be the road that goes stale first.
const clusters = [...personClusters(asks), ...clusterFailures(reports)];
mkdirSync(join(outDir, "briefs"), { recursive: true });
for (const cluster of clusters) {
writeFileSync(join(outDir, "briefs", `${cluster.key}.md`), briefFor(cluster, skillsRoot));
}
const summary = {
skills: reports.length,
pass: reports.reduce((sum, r) => sum + r.pass, 0),
fail: reports.reduce((sum, r) => sum + r.fail, 0),
defer: reports.reduce((sum, r) => sum + r.defer, 0),
};
const ledgerPath = join(outDir, "LEDGER.md");
writeFileSync(ledgerPath, ledger(clusters, summary, hostsBlockFor(outDir)));
return { clusters, ledgerPath };
}
/**
* THE HOSTS ROW IN THE LOOP'S LEDGER — the same road, not a second one.
*
* `hosts` is a verb on this loop rather than its own program because the loop
* IS the thing that turns measurement into ranked work, and a second ledger
* beside this one would rank the same defects twice and stop agreeing the
* first time either moved ⟨CLAUDE.md §4⟩. So the battery writes its corpus and
* its block, and `ledger()` folds the block in when one exists.
*/
export async function runHostsCommand(args: string[], deps: { skillsRoot: string }): Promise<void> {
const here = join(deps.skillsRoot, "snappy-tool-design");
const { runHosts } = await import("./hosts-run.ts");
const tokenFile = flag(args, "--token-file") ?? process.env.SNAPPY_RUNNER_TOKEN_FILE;
if (!tokenFile) throw new Error("hosts needs a bearer: --token-file, or SNAPPY_RUNNER_TOKEN_FILE, a 600-mode file holding the runner token");
const only = flag(args, "--only")?.split(",").map((word) => word.trim()).filter(Boolean);
await runHosts({
skillsRoot: deps.skillsRoot,
facesSkillDir: join(deps.skillsRoot, "snappy-faces"),
runnerUrl: flag(args, "--url") ?? "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
runnerFaceTs: flag(args, "--runner-face") ?? "/Users/robertboulos/Projects/snappy-runner/src/face.ts",
tokenFile,
outDir: flag(args, "--out") ?? here,
only: only as never,
codexModel: flag(args, "--codex-model"),
barPort: flag(args, "--bar-port") === undefined ? undefined : Number(flag(args, "--bar-port")),
// `--already <prompt>=<message_id>` — a card a previous run put on a host
// and this one must not duplicate. The date is the run's, so the cell can
// say WHEN the artifact was made rather than implying it was made now.
already: Object.fromEntries((flag(args, "--already") ?? "").split(",").filter(Boolean).map((pair) => {
const [prompt, id] = pair.split("=");
return [prompt.trim(), { message_id: Number(id), when: flag(args, "--already-when") ?? "earlier" }];
})),
});
}
/** The hosts block, when the battery has run. Absent is absent — never a zero. */
export function hostsBlockFor(dir: string): string {
const path = join(dir, "HOSTS-LEDGER.md");
return existsSync(path) ? `\n\n---\n\n${readFileSync(path, "utf8")}` : "";
}
export function flag(args: string[], name: string): string | undefined {
const at = args.indexOf(name);
return at >= 0 ? args[at + 1] : undefined;
}
/** THE CLOSED LOOP. probe → render → lint → cluster → briefs → ledger. */
export async function runLoopCommand(
command: string,
args: string[],
deps: { skillsRoot: string; lintAll: () => Promise<LintResult[]>; printAll: (reports: LintResult[], summary?: boolean) => void },
): Promise<void> {
const { skillsRoot: SKILLS_ROOT, lintAll, printAll } = deps;
const here = join(SKILLS_ROOT, "snappy-tool-design");
const via = (flag(args, "--via") ?? "mini") as "mini" | "public";
const out = flag(args, "--out");
if (command === "probe" || (command === "loop" && !args.includes("--skip-probe"))) {
const { runProbe } = await import("./probe.ts");
const skill = command === "probe" ? args.find((arg, index) => !arg.startsWith("--") && !args[index - 1]?.startsWith("--")) : undefined;
const hands = skill ? undefined : collectionNames(SKILLS_ROOT);
const file = await runProbe({ via, skill, hands, tokenFile: flag(args, "--token-file") });
const path = (command === "probe" && out) || join(here, PROBE_FILE);
writeFileSync(path, JSON.stringify(file, null, 1));
console.log(`probe → ${path}: ${Object.keys(file.skills).length} skill(s), ${JSON.stringify(file.server.run)}`);
}
if (command === "render" || (command === "loop" && !args.includes("--skip-render"))) {
const { runRender } = await import("./render.ts");
const hostDir = flag(args, "--host-dir");
if (!hostDir) throw new Error("render needs --host-dir (the loop directory holding host.html, host-bundle.js, static.mjs)");
// `--kind a,b` draws just those kinds. Reproducing one THREW row cost a
// full eight-minute pass over 97 kinds, which also could not be run beside
// a pass already in flight ⟨lane render-harness, 2026-09-09⟩. A partial run
// writes to `--out`; it must not overwrite the collection's render file,
// because rules 50–53 would then read four families instead of forty.
const kinds = (flag(args, "--kind") ?? "").split(",").map((k) => k.trim()).filter(Boolean);
if (kinds.length && command === "render" && !out) throw new Error("render --kind needs --out; a partial run must not overwrite the collection's render file");
const file = await runRender({ hostDir, shotDir: flag(args, "--shot-dir") ?? join(hostDir, "shots"), ...(kinds.length ? { kinds } : {}) });
const path = (command === "render" && out) || join(here, RENDER_FILE);
writeFileSync(path, JSON.stringify(file, null, 1));
console.log(`render → ${path}: ${file.kinds.length} kind(s), famil(ies) ${Object.keys(file.families).join(", ")}`);
}
if (command !== "loop") return;
// WHAT A PERSON ASKED FOR, BEFORE ANYTHING IS RANKED. Local by default —
// the file the runner on THIS Mac writes. `--fix-requests-from mini` reads
// the mini's own door over the road `probe --via mini` already uses (its URL
// with `/mcp` swapped for `/fix-requests`, the same `SNAPPY_RUNNER_TOKEN_FILE`
// bearer), because his notes are typed wherever the bar is running and the
// loop is often not run there.
//
// `--fix-requests-from both` IS THE ROAD BETWEEN THE TWO MACS, and it is the
// only one there is: nothing syncs `fix-requests.jsonl` ⟨measured 2026-09-09,
// lane menu-bar-13 — the runner's `update` pulls git and this file is runtime
// state, so a note typed on the bar here has never been visible to a loop run
// on the mini⟩. Copying the file would make a second writer of a directory
// `store.ts` owns, which is the duplicate road CLAUDE.md §4 bans by name. So
// the file does not travel — the READER visits both bodies, each through the
// door that body already answers, and folds what comes back.
const from = flag(args, "--fix-requests-from") ?? "local";
const asks = await readAsks(bodiesFor(from), {
local: () => readFixRequests(flag(args, "--fix-requests") ?? fixRequestsPath()),
mini: () => fetchFixRequests({
url: flag(args, "--url") ?? "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
tokenFile: flag(args, "--token-file") ?? process.env.SNAPPY_RUNNER_TOKEN_FILE
?? (() => { throw new Error("--fix-requests-from mini needs a bearer: --token-file, or SNAPPY_RUNNER_TOKEN_FILE"); })(),
}),
});
const reports = await lintAll();
printAll(reports, true);
const { clusters, ledgerPath } = writeLoopArtifacts(reports, out ?? here, SKILLS_ROOT, asks);
console.log(`loop → ${clusters.length} cluster(s); ledger ${ledgerPath}`);
for (const cluster of clusters.slice(0, 10)) {
console.log(cluster.kind === "person"
? ` ASK × ${cluster.key} — ${cluster.mechanism.slice(0, 70)}`
: ` ${String(cluster.skills.length).padStart(3)} × ${cluster.key}`);
}
}
export function collectionNames(SKILLS_ROOT: string): string[] {
return readdirSync(SKILLS_ROOT, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith("snappy-") && existsSync(join(SKILLS_ROOT, entry.name, "api.ts")))
.map((entry) => entry.name).sort();
}
/**
* THE REFUSAL THE HAND ITSELF PRINTED, LIFTED OUT OF THE RUNNER'S ANSWER
* ⟨lane loop-run-3, 2026-09-09⟩.
*
* `refusal-shape` landed `printRefusal` on eleven hands and R31/R32 did not
* move a single row, and the reason was HERE, not there: `refusalFrom` read
* `code`/`fix` off `structuredContent.result`, which is the runner's own
* `ExecuteResult`. That object has no `code` and no `fix` — its `why` for any
* non-zero exit is the sentence "the hand exited 1". So no hand could ever
* pass rule 31 through this road, however perfect its refusal, because the
* shape it printed was never the shape being read.
*
* WHERE THE HAND'S OWN WORDS ACTUALLY RIDE (read out of the runner's source,
* `src/execute.ts`): `printRefusal` writes the closed table's object to
* STDOUT and exits 1; the runner parses the whole of stdout into
* `result.hand_result` (`parseMaybeJson`, execute.ts:280) and keeps the raw
* text in `result.stdout`. Both survive a non-zero exit. So the envelope
* already carries the refusal — nobody was reading it.
*
* AND THE LINE THAT MUST NOT BE CROSSED. The runner ALSO mints a refusal of
* its own for every not-ok outcome (`refusalForOutcome`, execute.ts:167), and
* `hand_failed` always carries a code, a message and a fix. Lifting THAT for
* a hand that printed nothing would turn every bare `exit 1` into a rule-31
* PASS and hide the exact defect the rule exists to find. So the door's own
* refusal is lifted only when the door refused on its OWN account — before or
* instead of the hand — and never when its whole content is "the hand exited".
*/
import assert from "node:assert/strict";
import test from "node:test";
import { namesMissingCredential, refusalFrom } from "./probe.ts";
/** The shape a real `tools/call` answer takes, with only the parts read here. */
function answer(result: Record<string, unknown>, refusal?: Record<string, unknown>, text = "") {
return {
ms: 12, http: 200, bytes: 100,
body: {
result: {
isError: refusal !== undefined,
content: text ? [{ type: "text", text }] : [],
structuredContent: { ok: refusal === undefined, tool: "execute", result, ...(refusal ? { refusal } : {}) },
},
},
};
}
/** What a hand that imports the closed table prints, verbatim. */
const HAND_REFUSAL = {
outcome: "refused",
code: "missing_credential",
message: "snappy-box list needs BOX_TOKEN; it is not set on this body.",
fix: "Set the named key in the environment cache; the hand names the key and never the value.",
contract_slice: "requires",
};
const EXITED = { skill: "snappy-box", verb: "list", outcome: "failed", why: "the hand exited 1", exit_code: 1, stdout: "", stderr: "" };
test("the hand's own printed refusal is what the corpus records, not the runner's grading of it", () => {
const lifted = refusalFrom(answer(
{ ...EXITED, hand_result: HAND_REFUSAL, stdout: `${JSON.stringify(HAND_REFUSAL)}\n` },
{ code: "hand_failed", message: "snappy-box list failed: the hand exited 1.", fix: "The hand exited non-zero: read result.stderr." },
) as any, "list");
assert.ok(lifted, "a refused call recorded nothing");
assert.equal(lifted!.outcome, "refused", "graded `failed` because the exit code was read instead of the printed shape");
assert.equal(lifted!.code, "missing_credential", "the runner's `hand_failed` stood in front of the hand's own code");
assert.equal(lifted!.fix, HAND_REFUSAL.fix);
assert.ok(lifted!.message?.includes("BOX_TOKEN"), "rule 32 needs the literal the hand named");
});
test("a preamble line does not hide the shape — the object is found in stdout when hand_result is null", () => {
// `parseMaybeJson` returns null unless the WHOLE of stdout parses, so one
// stray line of prose above the refusal drops it out of `hand_result`.
const stdout = `reading the collection…\n${JSON.stringify(HAND_REFUSAL)}\n`;
const lifted = refusalFrom(answer({ ...EXITED, hand_result: null, stdout }) as any, "list");
assert.equal(lifted?.code, "missing_credential");
assert.equal(lifted?.outcome, "refused");
});
test("a bare exit stays a bare exit — the runner's own `hand_failed` never dresses it as a shaped refusal", () => {
const lifted = refusalFrom(answer(
{ ...EXITED, hand_result: null, stderr: "Error: ENOENT\n" },
{ code: "hand_failed", message: "snappy-box list failed: the hand exited 1.", fix: "read result.stderr" },
) as any, "list");
assert.ok(lifted, "the failure was not recorded at all");
assert.equal(lifted!.code, undefined, "rule 31 would have passed on a hand that printed no shape at all");
assert.equal(lifted!.outcome, "failed");
});
test("the DOOR's own refusal is lifted whole — it refused on its own account and the hand never ran", () => {
const lifted = refusalFrom(answer(
{ skill: "snappy-box", verb: "list", outcome: "refused", why: "nothing ran", exit_code: null, stdout: "", stderr: "" },
{ code: "argument_missing", message: 'list requires "path". Its grammar is: path limit?.', fix: "Call info { name, verb } — it names every required argument." },
) as any, "list");
assert.equal(lifted?.code, "argument_missing");
assert.equal(lifted?.outcome, "refused");
assert.ok(lifted!.fix?.includes("info"), "the door's fix was dropped");
});
test("SHAPES, never values: a hand that quotes a person in its refusal is scrubbed before the corpus", () => {
const lifted = refusalFrom(answer({
...EXITED, hand_result: { ...HAND_REFUSAL, message: "no thread for someone@example.com (+1 415 555 0134)" },
}) as any, "list");
assert.ok(!lifted!.message?.includes("example.com"), "an address reached the committed corpus");
assert.ok(lifted!.message?.includes("[email]") && lifted!.message?.includes("[phone]"));
});
test("an answered call records no refusal, and an answer that merely mentions a code is not one", () => {
const ok = answer({ skill: "snappy-box", verb: "list", outcome: "answered", why: "the hand answered", exit_code: 0, stdout: '{"rows":[]}', hand_result: { rows: [], note: "code: missing_credential is what you would see without a token" } });
assert.equal(refusalFrom(ok as any, "list"), null);
});
/* ── RULE 22 ONLY ASKS ABOUT A KEY THAT IS ABSENT ────────────────────────────
*
* The moment the hand's own shape became readable, rule 22 — pinned at zero on
* the floor and deferring on all 98 skills because nothing had ever been
* measured — went red on exactly two rows: snappy-publish and snappy-website,
* answering `credential_scope_denied` after 600 ms with one spawned child.
* That is Vercel's 403 on a token this body DOES hold. A rule that asks "did
* the door refuse before spawning" cannot be asked of an answer that only
* exists because the call was made.
*/
test("a provider's 403 is not an unmet requirement — nothing could refuse it before the call", () => {
const denied = { verb: "deploys", outcome: "refused", code: "credential_scope_denied", message: "[snappy-publish] VERCEL_TOKEN needs authorization (HTTP 403)", fix: "Re-grant the credential with the scope the verb names." };
assert.equal(namesMissingCredential(denied, ["VERCEL_TOKEN"]), false, "a scope the provider denied was graded as a key the door should have caught");
const expired = { verb: "list", outcome: "refused", code: "credential_expired", message: "SLACK_BOT_TOKEN has aged out", fix: "Refresh the named credential." };
assert.equal(namesMissingCredential(expired, ["SLACK_BOT_TOKEN"]), false);
});
test("a key that is not held on this body IS an unmet requirement, by code or by words", () => {
assert.equal(namesMissingCredential({ verb: "list", code: "missing_credential", message: "BOX_TOKEN is not set on this body.", fix: "Add the named key to the environment cache." }, ["BOX_TOKEN"]), true);
// A hand that printed no shape at all still gets read by its words, which is
// the only thing left to read.
assert.equal(namesMissingCredential({ verb: "list", message: "Missing credential BOX_TOKEN" }, ["BOX_TOKEN"]), true);
assert.equal(namesMissingCredential({ verb: "list", message: "BOX_TOKEN is not held on this machine" }, ["BOX_TOKEN"]), true);
// …and a refusal that merely MENTIONS the key while saying something else is
// not one: the old scan said yes to any sentence containing the word.
assert.equal(namesMissingCredential({ verb: "list", message: "BOX_TOKEN reached a folder that is empty" }, ["BOX_TOKEN"]), false);
});
/**
* THE REFUSAL THE HAND ITSELF PRINTED, LIFTED OUT OF THE RUNNER'S ANSWER
* ⟨lane loop-run-3, 2026-09-09⟩.
*
* `refusal-shape` landed `printRefusal` on eleven hands and R31/R32 did not
* move a single row, and the reason was HERE, not there: `refusalFrom` read
* `code`/`fix` off `structuredContent.result`, which is the runner's own
* `ExecuteResult`. That object has no `code` and no `fix` — its `why` for any
* non-zero exit is the sentence "the hand exited 1". So no hand could ever
* pass rule 31 through this road, however perfect its refusal, because the
* shape it printed was never the shape being read.
*
* WHERE THE HAND'S OWN WORDS ACTUALLY RIDE (read out of the runner's source,
* `src/execute.ts`): `printRefusal` writes the closed table's object to
* STDOUT and exits 1; the runner parses the whole of stdout into
* `result.hand_result` (`parseMaybeJson`, execute.ts:280) and keeps the raw
* text in `result.stdout`. Both survive a non-zero exit. So the envelope
* already carries the refusal — nobody was reading it.
*
* AND THE LINE THAT MUST NOT BE CROSSED. The runner ALSO mints a refusal of
* its own for every not-ok outcome (`refusalForOutcome`, execute.ts:167), and
* `hand_failed` always carries a code, a message and a fix. Lifting THAT for
* a hand that printed nothing would turn every bare `exit 1` into a rule-31
* PASS and hide the exact defect the rule exists to find. So the door's own
* refusal is lifted only when the door refused on its OWN account — before or
* instead of the hand — and never when its whole content is "the hand exited".
*/
import assert from "node:assert/strict";
import test from "node:test";
import { namesMissingCredential, refusalFrom } from "./probe.ts";
/** The shape a real `tools/call` answer takes, with only the parts read here. */
function answer(result: Record<string, unknown>, refusal?: Record<string, unknown>, text = "") {
return {
ms: 12, http: 200, bytes: 100,
body: {
result: {
isError: refusal !== undefined,
content: text ? [{ type: "text", text }] : [],
structuredContent: { ok: refusal === undefined, tool: "execute", result, ...(refusal ? { refusal } : {}) },
},
},
};
}
/** What a hand that imports the closed table prints, verbatim. */
const HAND_REFUSAL = {
outcome: "refused",
code: "missing_credential",
message: "snappy-box list needs BOX_TOKEN; it is not set on this body.",
fix: "Set the named key in the environment cache; the hand names the key and never the value.",
contract_slice: "requires",
};
const EXITED = { skill: "snappy-box", verb: "list", outcome: "failed", why: "the hand exited 1", exit_code: 1, stdout: "", stderr: "" };
test("the hand's own printed refusal is what the corpus records, not the runner's grading of it", () => {
const lifted = refusalFrom(answer(
{ ...EXITED, hand_result: HAND_REFUSAL, stdout: `${JSON.stringify(HAND_REFUSAL)}\n` },
{ code: "hand_failed", message: "snappy-box list failed: the hand exited 1.", fix: "The hand exited non-zero: read result.stderr." },
) as any, "list");
assert.ok(lifted, "a refused call recorded nothing");
assert.equal(lifted!.outcome, "refused", "graded `failed` because the exit code was read instead of the printed shape");
assert.equal(lifted!.code, "missing_credential", "the runner's `hand_failed` stood in front of the hand's own code");
assert.equal(lifted!.fix, HAND_REFUSAL.fix);
assert.ok(lifted!.message?.includes("BOX_TOKEN"), "rule 32 needs the literal the hand named");
});
test("a preamble line does not hide the shape — the object is found in stdout when hand_result is null", () => {
// `parseMaybeJson` returns null unless the WHOLE of stdout parses, so one
// stray line of prose above the refusal drops it out of `hand_result`.
const stdout = `reading the collection…\n${JSON.stringify(HAND_REFUSAL)}\n`;
const lifted = refusalFrom(answer({ ...EXITED, hand_result: null, stdout }) as any, "list");
assert.equal(lifted?.code, "missing_credential");
assert.equal(lifted?.outcome, "refused");
});
test("a bare exit stays a bare exit — the runner's own `hand_failed` never dresses it as a shaped refusal", () => {
const lifted = refusalFrom(answer(
{ ...EXITED, hand_result: null, stderr: "Error: ENOENT\n" },
{ code: "hand_failed", message: "snappy-box list failed: the hand exited 1.", fix: "read result.stderr" },
) as any, "list");
assert.ok(lifted, "the failure was not recorded at all");
assert.equal(lifted!.code, undefined, "rule 31 would have passed on a hand that printed no shape at all");
assert.equal(lifted!.outcome, "failed");
});
test("the DOOR's own refusal is lifted whole — it refused on its own account and the hand never ran", () => {
const lifted = refusalFrom(answer(
{ skill: "snappy-box", verb: "list", outcome: "refused", why: "nothing ran", exit_code: null, stdout: "", stderr: "" },
{ code: "argument_missing", message: 'list requires "path". Its grammar is: path limit?.', fix: "Call info { name, verb } — it names every required argument." },
) as any, "list");
assert.equal(lifted?.code, "argument_missing");
assert.equal(lifted?.outcome, "refused");
assert.ok(lifted!.fix?.includes("info"), "the door's fix was dropped");
});
test("SHAPES, never values: a hand that quotes a person in its refusal is scrubbed before the corpus", () => {
const lifted = refusalFrom(answer({
...EXITED, hand_result: { ...HAND_REFUSAL, message: "no thread for someone@example.com (+1 415 555 0134)" },
}) as any, "list");
assert.ok(!lifted!.message?.includes("example.com"), "an address reached the committed corpus");
assert.ok(lifted!.message?.includes("[email]") && lifted!.message?.includes("[phone]"));
});
test("an answered call records no refusal, and an answer that merely mentions a code is not one", () => {
const ok = answer({ skill: "snappy-box", verb: "list", outcome: "answered", why: "the hand answered", exit_code: 0, stdout: '{"rows":[]}', hand_result: { rows: [], note: "code: missing_credential is what you would see without a token" } });
assert.equal(refusalFrom(ok as any, "list"), null);
});
/* ── RULE 22 ONLY ASKS ABOUT A KEY THAT IS ABSENT ────────────────────────────
*
* The moment the hand's own shape became readable, rule 22 — pinned at zero on
* the floor and deferring on all 98 skills because nothing had ever been
* measured — went red on exactly two rows: snappy-publish and snappy-website,
* answering `credential_scope_denied` after 600 ms with one spawned child.
* That is Vercel's 403 on a token this body DOES hold. A rule that asks "did
* the door refuse before spawning" cannot be asked of an answer that only
* exists because the call was made.
*/
test("a provider's 403 is not an unmet requirement — nothing could refuse it before the call", () => {
const denied = { verb: "deploys", outcome: "refused", code: "credential_scope_denied", message: "[snappy-publish] VERCEL_TOKEN needs authorization (HTTP 403)", fix: "Re-grant the credential with the scope the verb names." };
assert.equal(namesMissingCredential(denied, ["VERCEL_TOKEN"]), false, "a scope the provider denied was graded as a key the door should have caught");
const expired = { verb: "list", outcome: "refused", code: "credential_expired", message: "SLACK_BOT_TOKEN has aged out", fix: "Refresh the named credential." };
assert.equal(namesMissingCredential(expired, ["SLACK_BOT_TOKEN"]), false);
});
test("a key that is not held on this body IS an unmet requirement, by code or by words", () => {
assert.equal(namesMissingCredential({ verb: "list", code: "missing_credential", message: "BOX_TOKEN is not set on this body.", fix: "Add the named key to the environment cache." }, ["BOX_TOKEN"]), true);
// A hand that printed no shape at all still gets read by its words, which is
// the only thing left to read.
assert.equal(namesMissingCredential({ verb: "list", message: "Missing credential BOX_TOKEN" }, ["BOX_TOKEN"]), true);
assert.equal(namesMissingCredential({ verb: "list", message: "BOX_TOKEN is not held on this machine" }, ["BOX_TOKEN"]), true);
// …and a refusal that merely MENTIONS the key while saying something else is
// not one: the old scan said yes to any sentence containing the word.
assert.equal(namesMissingCredential({ verb: "list", message: "BOX_TOKEN reached a folder that is empty" }, ["BOX_TOKEN"]), false);
});
/**
* THE PROBE — the half of the loop that goes and MEASURES.
*
* `lint` grades 34 of 60 rules from source. The other 26 ask questions source
* cannot answer: how big is the answer at limit 25, does the refusal carry a
* fix, does a missing credential cost 40 ms or 2,376 ms, is the catalog still
* inside its budget through a fresh client. This module calls the real MCP and
* writes what it saw into `tool-design-probe.json`.
*
* READS ONLY. A verb whose effect is send, post, write, or delete is never
* called — not even to fuzz a key. `SAFE_EFFECTS` is the gate and it fails
* closed: an effect this file has never heard of is not called. `NEVER_CALLED`
* is the second, independent witness, because the effect is the server's own
* claim about the verb and the name is not.
*
* NO SDK. The runner speaks Streamable HTTP and answers a bare POST, so the
* probe uses global `fetch`. A dependency here would be a second MCP client
* road inside a repository that already has too many.
*/
import { readFileSync } from "node:fs";
import { RefusedError } from "../snappy-settings/refusal-codes.ts";
import type { ProbeFile, ProbeRead, ProbeRefusal, ServerProbe } from "./corpus.ts";
import { PROBE_SCHEMA, REQUIRED_METRICS } from "./corpus.ts";
export const MINI_MCP_URL = "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp";
export const PUBLIC_MCP_URL = "https://snappy-skills-mcp.robertjboulos.workers.dev/mcp";
/** The only effects this probe will call. Anything else is left alone. */
const SAFE_EFFECTS = new Set(["read"]);
/**
* BELT AND BRACES BESIDE `SAFE_EFFECTS`, and the reason it is not redundant.
*
* `effect` is the SERVER'S CLAIM about a verb. `SAFE_EFFECTS` trusts it, and a
* verb that reaches a person while declaring `effect: "read"` is exactly the
* failure this repository names in CLAUDE.md §10 — a status truer than the
* artifact behind it, where the artifact here is somebody's phone buzzing. So
* the verb's own NAME is read as a second, independent witness, and a
* disagreement between the two is resolved by NOT CALLING and saying so out
* loud in the log. The words are the ones that actually reach a person or
* spend: a read is never named `send`, `post`, `reply`, `dm`, `publish`,
* `pay`, `charge` or `delete`, so a true read loses nothing.
*/
const NEVER_CALLED = /(^|[-_.])(send|post|reply|dm|publish|pay|charge|delete)([-_.]|$)/iu;
export const isNameSafeToCall = (verb: string) => !NEVER_CALLED.test(verb);
/** The limit series rule 24 compares. */
const LIMIT_SERIES = [1, 5, 25];
export interface ProbeOptions {
skill?: string;
via?: "mini" | "public";
url?: string;
tokenFile?: string;
/** Hand names to probe; defaults to every hand the runner reports. */
hands?: string[];
onLine?: (line: string) => void;
}
interface RpcResult {
ms: number;
http: number;
body: Record<string, any>;
bytes: number;
}
/** One fresh client per run. The handshake postdating the deploy is rule 58. */
export class McpProbeClient {
readonly createdAt = new Date().toISOString();
private id = 0;
private readonly url: string;
private readonly bearer: string;
constructor(url: string, bearer: string) { this.url = url; this.bearer = bearer; }
async rpc(method: string, params: Record<string, unknown> = {}, timeoutMs = 120_000): Promise<RpcResult> {
const started = Date.now();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(this.url, {
method: "POST",
signal: controller.signal,
headers: {
authorization: `Bearer ${this.bearer}`,
"content-type": "application/json",
accept: "application/json, text/event-stream",
"mcp-protocol-version": "2025-11-25",
},
body: JSON.stringify({ jsonrpc: "2.0", id: ++this.id, method, params }),
});
const text = await response.text();
return { ms: Date.now() - started, http: response.status, body: parseSse(text), bytes: Buffer.byteLength(text) };
} catch (error) {
return { ms: Date.now() - started, http: 0, body: { error: { message: String(error) } }, bytes: 0 };
} finally {
clearTimeout(timer);
}
}
call(name: string, args: Record<string, unknown>, timeoutMs?: number) {
return this.rpc("tools/call", { name, arguments: args }, timeoutMs);
}
}
/** The runner answers Streamable HTTP as one SSE frame; a JSON body also works. */
/** A refusal's words may quote a hand's own output. Addresses, phone-shaped
* numbers and the owner's name never reach the committed corpus. */
export function scrubPersonal(text: string): string {
return text
.replace(/[\w.+-]+@[\w-]+\.[\w.]+/gu, "[email]")
.replace(/(?:\+?1[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}/gu, "[phone]")
.replace(/boulos/giu, "[name]");
}
function parseSse(text: string): Record<string, any> {
const marker = text.indexOf("data: ");
const payload = text.startsWith("event:") && marker >= 0 ? text.slice(marker + 6).split("\n")[0] : text;
try {
return JSON.parse(payload);
} catch {
return { parseFailed: payload.slice(0, 400) };
}
}
const approxTokens = (bytes: number) => Math.ceil(bytes / 4);
/** The server's own view of a hand: what `info` reports, never what source claims. */
interface ServerHand {
name: string;
requires: string[];
verbs: Array<{ name: string; effect: string; required: string[]; optional: string[] }>;
}
function parseHand(structured: any): ServerHand | null {
const result = structured?.result;
if (!result || result.kind !== "hand") return null;
return {
name: String(result.name),
requires: Array.isArray(result.requires) ? result.requires.map(String) : [],
verbs: (result.verbs ?? []).map((verb: any) => ({
name: String(verb.name),
effect: String(verb.effect ?? ""),
required: (verb.required ?? []).map((a: any) => String(a?.name ?? a)),
optional: (verb.optional ?? []).map((a: any) => String(a?.name ?? a)),
})),
};
}
/**
* THE ENVELOPE, READ HONESTLY.
*
* A refusal that reports itself as an acceptance is the failure this whole
* repository has been bitten by six times, so the outcome is taken from the
* envelope's own discriminant and never from a falsy check on `ok`.
*/
function readEnvelope(rpc: RpcResult) {
const call = rpc.body?.result;
const env = call?.structuredContent;
const inner = env?.result ?? {};
const isError = call?.isError === true;
const outcome: string = inner.outcome ?? (isError ? "failed" : "unknown");
return {
call,
env,
inner,
isError,
outcome,
faceHint: env?.face_hint ?? null,
revision: env?.revision ? String(env.revision) : undefined,
text: (call?.content ?? []).filter((c: any) => c?.type === "text").map((c: any) => String(c.text ?? "")).join("\n"),
};
}
/**
* THE HAND'S OWN REFUSAL, LIFTED OUT OF THE RUNNER'S ANSWER.
*
* WHAT WAS WRONG, AND WHY NO HAND COULD EVER HAVE PASSED. This read `code` and
* `fix` off `structuredContent.result`, which is the runner's `ExecuteResult`
* — a record of a PROCESS. It has no `code` and no `fix`, and its `why` for
* any non-zero exit is the sentence "the hand exited 1". So `refusal-shape`
* landed the closed table's shape on eleven hands and rules 31 and 32 did not
* move one row: the shape those hands printed was never the shape being read.
*
* WHERE THE WORDS ACTUALLY RIDE ⟨read out of the runner, `src/execute.ts`⟩:
* `printRefusal` writes the closed table's object to STDOUT and exits 1; the
* runner parses the whole of stdout into `result.hand_result`
* ⟨parseMaybeJson, execute.ts:280⟩ and keeps the text in `result.stdout`.
* Both survive the non-zero exit. The refusal was in the envelope all along.
*
* THE LINE THIS FUNCTION WILL NOT CROSS. The runner also mints a refusal of
* its own for every not-ok outcome ⟨refusalForOutcome, execute.ts:167⟩, and
* `hand_failed` always carries a code, a message and a fix. Lifting that for a
* hand that printed NOTHING would turn every bare `exit 1` into a rule-31 PASS
* and hide the exact defect rule 31 exists to find — a status truer than the
* artifact behind it ⟨CLAUDE.md §10⟩. So the door's own refusal is lifted only
* when the door refused on its own account, and `hand_failed`/`hand_refused` —
* whose entire content is "the hand did something" — are left where they are.
*/
export function refusalFrom(rpc: RpcResult, verb: string): ProbeRefusal | null {
const { inner, env, isError, outcome, text } = readEnvelope(rpc);
if (!isError && outcome !== "refused" && outcome !== "failed") return null;
const printed = handPrintedRefusal(inner);
if (printed !== null) {
return {
verb,
outcome: "refused",
code: printed.code,
message: scrubPersonal(printed.message).slice(0, 400),
fix: scrubPersonal(printed.fix).slice(0, 400),
...(printed.contract_slice ? { contract_slice: printed.contract_slice } : {}),
};
}
// The door's own refusal — one it decided without the hand, or instead of it.
const door = env?.refusal;
if (door && typeof door.code === "string" && !GRADES_THE_HAND.has(door.code)) {
return {
verb,
outcome: "refused",
code: door.code,
message: scrubPersonal(String(door.message ?? "")).slice(0, 400) || undefined,
fix: scrubPersonal(String(door.fix ?? "")).slice(0, 400) || undefined,
};
}
// Nothing shaped was printed and nothing shaped was decided. The row says so.
return {
verb,
outcome: outcome === "unknown" ? "failed" : outcome,
code: undefined,
message: scrubPersonal(String(inner.why ?? text ?? "")).slice(0, 400) || undefined,
fix: undefined,
};
}
/** The two runner codes whose whole content is "the hand did something": they
* grade the hand's exit and carry none of the hand's own vocabulary. */
const GRADES_THE_HAND = new Set(["hand_failed", "hand_refused"]);
/** The closed table's own discriminant, never a falsy check and never a word
* scan: `outcome === "refused"` plus a `code`, on an object the HAND printed. */
function isPrintedRefusal(value: unknown): value is { outcome: "refused"; code: string; message: string; fix: string; contract_slice?: string } {
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
const row = value as Record<string, unknown>;
return row.outcome === "refused" && typeof row.code === "string" && row.code.length > 0
&& typeof row.message === "string" && typeof row.fix === "string";
}
/**
* The hand's printed envelope, wherever it landed. `hand_result` first because
* the runner already parsed it; then the raw streams, because one line of
* prose above the object is enough for `parseMaybeJson` to return null and a
* refusal that IS there would otherwise be recorded as absent.
*/
function handPrintedRefusal(inner: any): { code: string; message: string; fix: string; contract_slice?: string } | null {
if (isPrintedRefusal(inner?.hand_result)) return inner.hand_result;
for (const stream of [inner?.stdout, inner?.stderr]) {
if (typeof stream !== "string" || stream.length === 0) continue;
for (const line of stream.split("\n")) {
const trimmed = line.trim();
if (!trimmed.startsWith("{")) continue;
try {
const parsed = JSON.parse(trimmed) as unknown;
if (isPrintedRefusal(parsed)) return parsed;
} catch { /* a torn or non-JSON line is not a refusal; keep looking */ }
}
}
return null;
}
/** Rule 30's mark: a credentialed read whose vendor text arrives labelled as data. */
function evidenceEnvelope(inner: any): boolean {
return Boolean(inner?.hand_result) || typeof inner?.stdout === "string";
}
function faceOf(hint: any, meta: any, textLength: number): ProbeRead["face"] | undefined {
if (!hint || hint.state === "none" || hint.state === undefined) return undefined;
const uri = meta?.ui?.resourceUri ?? meta?.["ui/resourceUri"] ?? hint.resource_uri;
return {
state: String(hint.state),
kind: hint.kind ? String(hint.kind) : undefined,
textFallback: textLength > 0,
resourceUri: uri ? String(uri) : undefined,
mimeType: meta?.ui?.mimeType ?? "text/html;profile=mcp-app",
renditions: hint.renditions ?? ["png", "lang", "text"],
};
}
/** The near-miss pairs rule 15 derives. Only READ verbs are ever fuzzed. */
const NEAR_MISS_PAIRS: Array<[string, string]> = [
["body", "text"], ["text", "body"], ["recipient", "to"], ["to", "recipient"],
["msg", "message"], ["message", "msg"], ["count", "limit"], ["limit", "count"],
];
export async function runProbe(options: ProbeOptions = {}): Promise<ProbeFile> {
const via = options.via ?? "mini";
const url = options.url ?? (via === "public" ? PUBLIC_MCP_URL : MINI_MCP_URL);
const say = options.onLine ?? ((line: string) => console.log(line));
const bearer = readFileSync(options.tokenFile ?? requiredTokenFile(), "utf8").trim();
const client = new McpProbeClient(url, bearer);
const startedAt = Date.now();
let toolCalls = 0;
let errors = 0;
const server: ServerProbe = { runner_url: url, via, client_created_at: client.createdAt };
say(`probe --via ${via} → ${url}`);
// ── the server primitives, once ──────────────────────────────────────────
const tools = await client.rpc("tools/list");
toolCalls++;
const toolList = tools.body?.result?.tools ?? [];
const names = toolList.map((t: any) => String(t.name));
server.published_tools = toolList.length;
server.tool_names = names;
server.tools_list_bytes = tools.bytes;
server.tools_list_tokens = approxTokens(tools.bytes);
server.tools_sorted = names.every((n: string, i: number) => i === 0 || names[i - 1] <= n);
server.ttl_ms_present = JSON.stringify(toolList).includes("ttlMs");
server.cache_scope_present = JSON.stringify(toolList).includes("cacheScope");
say(` tools/list ${tools.ms} ms ${toolList.length} tools ${tools.bytes} B ~${server.tools_list_tokens} tok sorted=${server.tools_sorted} ttlMs=${server.ttl_ms_present} cacheScope=${server.cache_scope_present}`);
const prompts = await client.rpc("prompts/list");
toolCalls++;
server.prompts_count = prompts.body?.result?.prompts?.length ?? 0;
server.prompts_bytes = prompts.bytes;
server.prompts_ms = prompts.ms;
say(` prompts/list ${prompts.ms} ms ${server.prompts_count} prompts ${prompts.bytes} B`);
const resources = await client.rpc("resources/list");
toolCalls++;
const uris = (resources.body?.result?.resources ?? []).map((r: any) => String(r.uri));
server.resources_count = uris.length;
server.resources_bytes = resources.bytes;
server.resources_ms = resources.ms;
server.faces_view_listed = uris.includes("ui://snappy/faces");
say(` resources/list ${resources.ms} ms ${uris.length} resources ui://snappy/faces listed=${server.faces_view_listed}`);
const faces = await client.rpc("resources/read", { uri: "ui://snappy/faces" });
toolCalls++;
const facesContent = faces.body?.result?.contents?.[0];
server.faces_view_bytes = facesContent?.text?.length ?? 0;
server.faces_view_ms = faces.ms;
server.faces_view_mime = facesContent?.mimeType;
say(` resources/read ${faces.ms} ms ui://snappy/faces ${server.faces_view_bytes} B mime=${server.faces_view_mime}`);
// ── which hands ──────────────────────────────────────────────────────────
const wanted = options.skill ? [options.skill] : options.hands ?? (await listHands(client, say));
const skills: ProbeFile["skills"] = {};
for (const name of wanted) {
const info = await client.call("info", { name });
toolCalls++;
const hand = parseHand(info.body?.result?.structuredContent);
if (!hand) {
errors++;
say(` ${name}: info answered no hand — skipped`);
continue;
}
const reads: ProbeRead[] = [];
const refusals: ProbeRefusal[] = [];
const unmet: NonNullable<ProbeFile["skills"][string]["unmet_requirement"]> = [];
const nearMiss: NonNullable<ProbeFile["skills"][string]["near_miss"]> = [];
const nearMissSkipped: Array<{ verb: string; provided: string; expected: string; reason: string }> = [];
const declaredReads = hand.verbs.filter((verb) => SAFE_EFFECTS.has(verb.effect));
const safetySkipped: NonNullable<ProbeFile["skills"][string]["safety_skipped"]> = [];
for (const verb of declaredReads.filter((v) => !isNameSafeToCall(v.name))) {
safetySkipped.push({
verb: verb.name,
effect: verb.effect,
reason: `declared "${verb.effect}" but the name says it reaches a person or spends — never called by the probe`,
});
say(` ${name}.${verb.name}: SAFETY SKIP — declared effect "${verb.effect}" but the name says it reaches a person or spends`);
}
const readVerbs = declaredReads.filter((verb) => isNameSafeToCall(verb.name));
for (const verb of readVerbs) {
const takesLimit = [...verb.required, ...verb.optional].some((arg) => /^limit$/i.test(arg));
const series = takesLimit ? LIMIT_SERIES : [undefined];
if (verb.required.length) {
// A read that demands a value the probe would have to invent is not
// probed: an invented argument measures the invention, not the read.
say(` ${name}.${verb.name}: required args [${verb.required.join(", ")}] — not probed`);
continue;
}
for (const limit of series) {
const rpc = await client.call("execute", {
name,
verb: verb.name,
...(limit === undefined ? {} : { arguments: { limit } }),
}, 180_000);
toolCalls++;
const seen = readEnvelope(rpc);
if (!server.revision_first && seen.revision) server.revision_first = seen.revision;
if (seen.revision) server.revision_last = seen.revision;
const bytes = JSON.stringify(rpc.body?.result ?? {}).length;
const read: ProbeRead = {
verb: verb.name,
limit,
chars: bytes,
bytes,
tokens: approxTokens(bytes),
duration_ms: typeof seen.inner.duration_ms === "number" ? seen.inner.duration_ms : undefined,
round_trip_ms: rpc.ms,
request_count: 1,
truncated: seen.inner.truncated === true,
cursor: seen.inner.next_cursor ?? undefined,
// SHAPE, NEVER VALUES. The corpus is committed; a person's inbox is not.
// (2026-09-09 02:4x: the first corpus carried real addresses and was scrubbed.)
content: (rpc.body?.result?.content ?? []).map((c: any) => ({ type: c?.type, chars: String(c?.text ?? "").length })),
structuredContent: undefined,
outputSchemaValid: seen.env ? typeof seen.env.ok === "boolean" && Boolean(seen.env.result) : false,
evidence_envelope: evidenceEnvelope(seen.inner),
outcome: seen.outcome,
face: faceOf(seen.faceHint, rpc.body?.result?._meta, seen.text.length),
};
if (seen.env?.result !== undefined) read.structuredContent = { present: true };
reads.push(read);
const refusal = refusalFrom(rpc, verb.name);
if (refusal) {
refusals.push(refusal);
errors++;
// A refusal that never spawned is the preflight rule 22 measures.
const spawned = seen.inner.exit_code !== null && seen.inner.exit_code !== undefined;
if (namesMissingCredential(refusal, hand.requires)) {
unmet.push({ verb: verb.name, elapsed_ms: seen.inner.duration_ms ?? rpc.ms, child_processes: spawned ? 1 : 0 });
}
}
say(` ${name}.${verb.name}${limit === undefined ? "" : ` limit=${limit}`} ${rpc.ms} ms ${bytes} B ${seen.outcome}${read.face ? ` face=${read.face.state}/${read.face.kind ?? "-"}` : ""}`);
}
// Near-miss keys, on READ verbs only.
const argNames = new Set([...verb.required, ...verb.optional]);
for (const [provided, expected] of NEAR_MISS_PAIRS) {
if (!argNames.has(expected) || argNames.has(provided)) continue;
const rpc = await client.call("execute", { name, verb: verb.name, arguments: { [provided]: "1" } }, 60_000);
toolCalls++;
const seen = readEnvelope(rpc);
const text = scrubPersonal([seen.inner.why, seen.text, JSON.stringify(seen.inner.stderr ?? "")].filter(Boolean).join(" ")).slice(0, 300);
nearMiss.push({ verb: verb.name, provided, expected, refusal: text });
say(` ${name}.${verb.name}: near-miss ${provided}→${expected} ${seen.outcome} names-key=${text.includes(expected)}`);
}
}
for (const verb of hand.verbs.filter((v) => !readVerbs.includes(v))) {
const argNames = new Set([...verb.required, ...verb.optional]);
for (const [provided, expected] of NEAR_MISS_PAIRS) {
if (!argNames.has(expected) || argNames.has(provided)) continue;
nearMissSkipped.push({
verb: verb.name,
provided,
expected,
reason: SAFE_EFFECTS.has(verb.effect)
? `declared "${verb.effect}" but named like a send — never called by the probe`
: `${verb.effect} effect — never called by the probe`,
});
}
}
// Rule 41: does search rank this hand top-three for its own name and job?
const rank = await searchRank(client, name);
toolCalls += 1;
skills[name] = {
reads,
refusals,
unmet_requirement: unmet,
near_miss: nearMiss,
search_ranks: rank === null ? [] : [rank],
...(nearMissSkipped.length ? { near_miss_skipped: nearMissSkipped } : {}),
...(safetySkipped.length ? { safety_skipped: safetySkipped } : {}),
} as ProbeFile["skills"][string];
say(` ${name}: ${reads.length} read sample(s), ${refusals.length} refusal(s), search rank ${rank ?? "unranked"}`);
}
// Rule 58: the client was created inside this run, and no deploy raced it.
server.fresh_client = true;
server.handshake_after_deploy = Boolean(server.revision_first) && server.revision_first === server.revision_last;
server.metrics = REQUIRED_METRICS;
const answered = Object.values(skills).flatMap((s) => s.reads ?? []).filter((r) => r.outcome === "answered").length;
const totalReads = Object.values(skills).flatMap((s) => s.reads ?? []).length;
server.run = {
accuracy: totalReads ? Number((answered / totalReads).toFixed(4)) : 0,
runtime: Date.now() - startedAt,
tool_call_count: toolCalls,
tokens: approxTokens(Object.values(skills).flatMap((s) => s.reads ?? []).reduce((sum, r) => sum + (r.bytes ?? 0), 0)),
errors,
};
return { schema: PROBE_SCHEMA, generated_at: new Date().toISOString(), server, skills };
}
/**
* AN UNMET REQUIREMENT IS ONE THE DOOR COULD HAVE KNOWN — nothing else.
*
* Rule 22 asks whether a requirement that is NOT MET refuses before a process
* is spawned, in under 50 ms. That question only has an answer for a key that
* is absent from this body: `missing_credential`, the closed table's own words
* for "a key named in `requires` is not held on this machine". A provider
* answering 403 (`credential_scope_denied`) or a token that has aged out
* (`credential_expired`) are BOTH discovered by making the call — grading them
* here would demand the door predict a vendor's answer, and every hand would
* fail a rule nothing could pass.
*
* MEASURED THE MOMENT THE REFUSALS BECAME READABLE ⟨lane loop-run-3⟩: with the
* hand's own shape finally lifted, the only two rows rule 22 could see were
* snappy-publish and snappy-website answering `credential_scope_denied` after
* 600 ms and one spawned child — Vercel's 403 on a token that IS held. Read as
* unmet requirements they turned a rule pinned at zero red for a defect that
* was never a defect. So the CODE decides when the hand printed one, and the
* old text scan survives only for a hand that printed no shape at all.
*/
export function namesMissingCredential(refusal: ProbeRefusal, requires: string[]): boolean {
if (refusal.code) return refusal.code === "missing_credential" || refusal.code === "credential_missing";
const text = `${refusal.message ?? ""} ${refusal.fix ?? ""}`;
return (requires.some((key) => text.includes(key)) && /missing|not set|not held|no .*(token|key|credential)/i.test(text))
|| /missing (?:credential|env|token|key)/i.test(text);
}
async function searchRank(client: McpProbeClient, name: string): Promise<number | null> {
const bare = name.replace(/^snappy-/, "");
const rpc = await client.call("search", { intent: bare, limit: 5 }, 60_000);
const matches = rpc.body?.result?.structuredContent?.result?.matches ?? [];
const index = matches.findIndex((m: any) => String(m?.name) === name);
return index < 0 ? null : index + 1;
}
/** Every hand the runner itself reports, discovered through its own search. */
async function listHands(client: McpProbeClient, say: (line: string) => void): Promise<string[]> {
const found = new Set<string>();
const intents = ["read", "send", "list", "post", "search", "file", "video", "image", "calendar", "mail", "message", "client", "content", "deploy", "data"];
for (const intent of intents) {
const rpc = await client.call("search", { intent, limit: 50 }, 60_000);
for (const match of rpc.body?.result?.structuredContent?.result?.matches ?? []) {
if (match?.kind === "hand" && typeof match.name === "string") found.add(match.name);
}
}
const names = [...found].sort();
say(` discovered ${names.length} hand(s) through the runner's own search`);
return names;
}
function requiredTokenFile(): string {
const fromEnv = process.env.SNAPPY_RUNNER_TOKEN_FILE;
if (!fromEnv) {
throw new RefusedError(
"missing_credential",
"probe needs a bearer: set SNAPPY_RUNNER_TOKEN_FILE to a 600-mode file holding the runner token " +
"(ssh mini cat ~/.snappy-skills/token > <file>), or pass --token-file. The token is never an argument.",
);
}
return fromEnv;
}
/**
* THE PROBE — the half of the loop that goes and MEASURES.
*
* `lint` grades 34 of 60 rules from source. The other 26 ask questions source
* cannot answer: how big is the answer at limit 25, does the refusal carry a
* fix, does a missing credential cost 40 ms or 2,376 ms, is the catalog still
* inside its budget through a fresh client. This module calls the real MCP and
* writes what it saw into `tool-design-probe.json`.
*
* READS ONLY. A verb whose effect is send, post, write, or delete is never
* called — not even to fuzz a key. `SAFE_EFFECTS` is the gate and it fails
* closed: an effect this file has never heard of is not called. `NEVER_CALLED`
* is the second, independent witness, because the effect is the server's own
* claim about the verb and the name is not.
*
* NO SDK. The runner speaks Streamable HTTP and answers a bare POST, so the
* probe uses global `fetch`. A dependency here would be a second MCP client
* road inside a repository that already has too many.
*/
import { readFileSync } from "node:fs";
import { RefusedError } from "../snappy-settings/refusal-codes.ts";
import type { ProbeFile, ProbeRead, ProbeRefusal, ServerProbe } from "./corpus.ts";
import { PROBE_SCHEMA, REQUIRED_METRICS } from "./corpus.ts";
export const MINI_MCP_URL = "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp";
export const PUBLIC_MCP_URL = "https://snappy-skills-mcp.robertjboulos.workers.dev/mcp";
/** The only effects this probe will call. Anything else is left alone. */
const SAFE_EFFECTS = new Set(["read"]);
/**
* BELT AND BRACES BESIDE `SAFE_EFFECTS`, and the reason it is not redundant.
*
* `effect` is the SERVER'S CLAIM about a verb. `SAFE_EFFECTS` trusts it, and a
* verb that reaches a person while declaring `effect: "read"` is exactly the
* failure this repository names in CLAUDE.md §10 — a status truer than the
* artifact behind it, where the artifact here is somebody's phone buzzing. So
* the verb's own NAME is read as a second, independent witness, and a
* disagreement between the two is resolved by NOT CALLING and saying so out
* loud in the log. The words are the ones that actually reach a person or
* spend: a read is never named `send`, `post`, `reply`, `dm`, `publish`,
* `pay`, `charge` or `delete`, so a true read loses nothing.
*/
const NEVER_CALLED = /(^|[-_.])(send|post|reply|dm|publish|pay|charge|delete)([-_.]|$)/iu;
export const isNameSafeToCall = (verb: string) => !NEVER_CALLED.test(verb);
/** The limit series rule 24 compares. */
const LIMIT_SERIES = [1, 5, 25];
export interface ProbeOptions {
skill?: string;
via?: "mini" | "public";
url?: string;
tokenFile?: string;
/** Hand names to probe; defaults to every hand the runner reports. */
hands?: string[];
onLine?: (line: string) => void;
}
interface RpcResult {
ms: number;
http: number;
body: Record<string, any>;
bytes: number;
}
/** One fresh client per run. The handshake postdating the deploy is rule 58. */
export class McpProbeClient {
readonly createdAt = new Date().toISOString();
private id = 0;
private readonly url: string;
private readonly bearer: string;
constructor(url: string, bearer: string) { this.url = url; this.bearer = bearer; }
async rpc(method: string, params: Record<string, unknown> = {}, timeoutMs = 120_000): Promise<RpcResult> {
const started = Date.now();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(this.url, {
method: "POST",
signal: controller.signal,
headers: {
authorization: `Bearer ${this.bearer}`,
"content-type": "application/json",
accept: "application/json, text/event-stream",
"mcp-protocol-version": "2025-11-25",
},
body: JSON.stringify({ jsonrpc: "2.0", id: ++this.id, method, params }),
});
const text = await response.text();
return { ms: Date.now() - started, http: response.status, body: parseSse(text), bytes: Buffer.byteLength(text) };
} catch (error) {
return { ms: Date.now() - started, http: 0, body: { error: { message: String(error) } }, bytes: 0 };
} finally {
clearTimeout(timer);
}
}
call(name: string, args: Record<string, unknown>, timeoutMs?: number) {
return this.rpc("tools/call", { name, arguments: args }, timeoutMs);
}
}
/** The runner answers Streamable HTTP as one SSE frame; a JSON body also works. */
/** A refusal's words may quote a hand's own output. Addresses, phone-shaped
* numbers and the owner's name never reach the committed corpus. */
export function scrubPersonal(text: string): string {
return text
.replace(/[\w.+-]+@[\w-]+\.[\w.]+/gu, "[email]")
.replace(/(?:\+?1[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}/gu, "[phone]")
.replace(/boulos/giu, "[name]");
}
function parseSse(text: string): Record<string, any> {
const marker = text.indexOf("data: ");
const payload = text.startsWith("event:") && marker >= 0 ? text.slice(marker + 6).split("\n")[0] : text;
try {
return JSON.parse(payload);
} catch {
return { parseFailed: payload.slice(0, 400) };
}
}
const approxTokens = (bytes: number) => Math.ceil(bytes / 4);
/** The server's own view of a hand: what `info` reports, never what source claims. */
interface ServerHand {
name: string;
requires: string[];
verbs: Array<{ name: string; effect: string; required: string[]; optional: string[] }>;
}
function parseHand(structured: any): ServerHand | null {
const result = structured?.result;
if (!result || result.kind !== "hand") return null;
return {
name: String(result.name),
requires: Array.isArray(result.requires) ? result.requires.map(String) : [],
verbs: (result.verbs ?? []).map((verb: any) => ({
name: String(verb.name),
effect: String(verb.effect ?? ""),
required: (verb.required ?? []).map((a: any) => String(a?.name ?? a)),
optional: (verb.optional ?? []).map((a: any) => String(a?.name ?? a)),
})),
};
}
/**
* THE ENVELOPE, READ HONESTLY.
*
* A refusal that reports itself as an acceptance is the failure this whole
* repository has been bitten by six times, so the outcome is taken from the
* envelope's own discriminant and never from a falsy check on `ok`.
*/
function readEnvelope(rpc: RpcResult) {
const call = rpc.body?.result;
const env = call?.structuredContent;
const inner = env?.result ?? {};
const isError = call?.isError === true;
const outcome: string = inner.outcome ?? (isError ? "failed" : "unknown");
return {
call,
env,
inner,
isError,
outcome,
faceHint: env?.face_hint ?? null,
revision: env?.revision ? String(env.revision) : undefined,
text: (call?.content ?? []).filter((c: any) => c?.type === "text").map((c: any) => String(c.text ?? "")).join("\n"),
};
}
/**
* THE HAND'S OWN REFUSAL, LIFTED OUT OF THE RUNNER'S ANSWER.
*
* WHAT WAS WRONG, AND WHY NO HAND COULD EVER HAVE PASSED. This read `code` and
* `fix` off `structuredContent.result`, which is the runner's `ExecuteResult`
* — a record of a PROCESS. It has no `code` and no `fix`, and its `why` for
* any non-zero exit is the sentence "the hand exited 1". So `refusal-shape`
* landed the closed table's shape on eleven hands and rules 31 and 32 did not
* move one row: the shape those hands printed was never the shape being read.
*
* WHERE THE WORDS ACTUALLY RIDE ⟨read out of the runner, `src/execute.ts`⟩:
* `printRefusal` writes the closed table's object to STDOUT and exits 1; the
* runner parses the whole of stdout into `result.hand_result`
* ⟨parseMaybeJson, execute.ts:280⟩ and keeps the text in `result.stdout`.
* Both survive the non-zero exit. The refusal was in the envelope all along.
*
* THE LINE THIS FUNCTION WILL NOT CROSS. The runner also mints a refusal of
* its own for every not-ok outcome ⟨refusalForOutcome, execute.ts:167⟩, and
* `hand_failed` always carries a code, a message and a fix. Lifting that for a
* hand that printed NOTHING would turn every bare `exit 1` into a rule-31 PASS
* and hide the exact defect rule 31 exists to find — a status truer than the
* artifact behind it ⟨CLAUDE.md §10⟩. So the door's own refusal is lifted only
* when the door refused on its own account, and `hand_failed`/`hand_refused` —
* whose entire content is "the hand did something" — are left where they are.
*/
export function refusalFrom(rpc: RpcResult, verb: string): ProbeRefusal | null {
const { inner, env, isError, outcome, text } = readEnvelope(rpc);
if (!isError && outcome !== "refused" && outcome !== "failed") return null;
const printed = handPrintedRefusal(inner);
if (printed !== null) {
return {
verb,
outcome: "refused",
code: printed.code,
message: scrubPersonal(printed.message).slice(0, 400),
fix: scrubPersonal(printed.fix).slice(0, 400),
...(printed.contract_slice ? { contract_slice: printed.contract_slice } : {}),
};
}
// The door's own refusal — one it decided without the hand, or instead of it.
const door = env?.refusal;
if (door && typeof door.code === "string" && !GRADES_THE_HAND.has(door.code)) {
return {
verb,
outcome: "refused",
code: door.code,
message: scrubPersonal(String(door.message ?? "")).slice(0, 400) || undefined,
fix: scrubPersonal(String(door.fix ?? "")).slice(0, 400) || undefined,
};
}
// Nothing shaped was printed and nothing shaped was decided. The row says so.
return {
verb,
outcome: outcome === "unknown" ? "failed" : outcome,
code: undefined,
message: scrubPersonal(String(inner.why ?? text ?? "")).slice(0, 400) || undefined,
fix: undefined,
};
}
/** The two runner codes whose whole content is "the hand did something": they
* grade the hand's exit and carry none of the hand's own vocabulary. */
const GRADES_THE_HAND = new Set(["hand_failed", "hand_refused"]);
/** The closed table's own discriminant, never a falsy check and never a word
* scan: `outcome === "refused"` plus a `code`, on an object the HAND printed. */
function isPrintedRefusal(value: unknown): value is { outcome: "refused"; code: string; message: string; fix: string; contract_slice?: string } {
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
const row = value as Record<string, unknown>;
return row.outcome === "refused" && typeof row.code === "string" && row.code.length > 0
&& typeof row.message === "string" && typeof row.fix === "string";
}
/**
* The hand's printed envelope, wherever it landed. `hand_result` first because
* the runner already parsed it; then the raw streams, because one line of
* prose above the object is enough for `parseMaybeJson` to return null and a
* refusal that IS there would otherwise be recorded as absent.
*/
function handPrintedRefusal(inner: any): { code: string; message: string; fix: string; contract_slice?: string } | null {
if (isPrintedRefusal(inner?.hand_result)) return inner.hand_result;
for (const stream of [inner?.stdout, inner?.stderr]) {
if (typeof stream !== "string" || stream.length === 0) continue;
for (const line of stream.split("\n")) {
const trimmed = line.trim();
if (!trimmed.startsWith("{")) continue;
try {
const parsed = JSON.parse(trimmed) as unknown;
if (isPrintedRefusal(parsed)) return parsed;
} catch { /* a torn or non-JSON line is not a refusal; keep looking */ }
}
}
return null;
}
/** Rule 30's mark: a credentialed read whose vendor text arrives labelled as data. */
function evidenceEnvelope(inner: any): boolean {
return Boolean(inner?.hand_result) || typeof inner?.stdout === "string";
}
function faceOf(hint: any, meta: any, textLength: number): ProbeRead["face"] | undefined {
if (!hint || hint.state === "none" || hint.state === undefined) return undefined;
const uri = meta?.ui?.resourceUri ?? meta?.["ui/resourceUri"] ?? hint.resource_uri;
return {
state: String(hint.state),
kind: hint.kind ? String(hint.kind) : undefined,
textFallback: textLength > 0,
resourceUri: uri ? String(uri) : undefined,
mimeType: meta?.ui?.mimeType ?? "text/html;profile=mcp-app",
renditions: hint.renditions ?? ["png", "lang", "text"],
};
}
/** The near-miss pairs rule 15 derives. Only READ verbs are ever fuzzed. */
const NEAR_MISS_PAIRS: Array<[string, string]> = [
["body", "text"], ["text", "body"], ["recipient", "to"], ["to", "recipient"],
["msg", "message"], ["message", "msg"], ["count", "limit"], ["limit", "count"],
];
export async function runProbe(options: ProbeOptions = {}): Promise<ProbeFile> {
const via = options.via ?? "mini";
const url = options.url ?? (via === "public" ? PUBLIC_MCP_URL : MINI_MCP_URL);
const say = options.onLine ?? ((line: string) => console.log(line));
const bearer = readFileSync(options.tokenFile ?? requiredTokenFile(), "utf8").trim();
const client = new McpProbeClient(url, bearer);
const startedAt = Date.now();
let toolCalls = 0;
let errors = 0;
const server: ServerProbe = { runner_url: url, via, client_created_at: client.createdAt };
say(`probe --via ${via} → ${url}`);
// ── the server primitives, once ──────────────────────────────────────────
const tools = await client.rpc("tools/list");
toolCalls++;
const toolList = tools.body?.result?.tools ?? [];
const names = toolList.map((t: any) => String(t.name));
server.published_tools = toolList.length;
server.tool_names = names;
server.tools_list_bytes = tools.bytes;
server.tools_list_tokens = approxTokens(tools.bytes);
server.tools_sorted = names.every((n: string, i: number) => i === 0 || names[i - 1] <= n);
server.ttl_ms_present = JSON.stringify(toolList).includes("ttlMs");
server.cache_scope_present = JSON.stringify(toolList).includes("cacheScope");
say(` tools/list ${tools.ms} ms ${toolList.length} tools ${tools.bytes} B ~${server.tools_list_tokens} tok sorted=${server.tools_sorted} ttlMs=${server.ttl_ms_present} cacheScope=${server.cache_scope_present}`);
const prompts = await client.rpc("prompts/list");
toolCalls++;
server.prompts_count = prompts.body?.result?.prompts?.length ?? 0;
server.prompts_bytes = prompts.bytes;
server.prompts_ms = prompts.ms;
say(` prompts/list ${prompts.ms} ms ${server.prompts_count} prompts ${prompts.bytes} B`);
const resources = await client.rpc("resources/list");
toolCalls++;
const uris = (resources.body?.result?.resources ?? []).map((r: any) => String(r.uri));
server.resources_count = uris.length;
server.resources_bytes = resources.bytes;
server.resources_ms = resources.ms;
server.faces_view_listed = uris.includes("ui://snappy/faces");
say(` resources/list ${resources.ms} ms ${uris.length} resources ui://snappy/faces listed=${server.faces_view_listed}`);
const faces = await client.rpc("resources/read", { uri: "ui://snappy/faces" });
toolCalls++;
const facesContent = faces.body?.result?.contents?.[0];
server.faces_view_bytes = facesContent?.text?.length ?? 0;
server.faces_view_ms = faces.ms;
server.faces_view_mime = facesContent?.mimeType;
say(` resources/read ${faces.ms} ms ui://snappy/faces ${server.faces_view_bytes} B mime=${server.faces_view_mime}`);
// ── which hands ──────────────────────────────────────────────────────────
const wanted = options.skill ? [options.skill] : options.hands ?? (await listHands(client, say));
const skills: ProbeFile["skills"] = {};
for (const name of wanted) {
const info = await client.call("info", { name });
toolCalls++;
const hand = parseHand(info.body?.result?.structuredContent);
if (!hand) {
errors++;
say(` ${name}: info answered no hand — skipped`);
continue;
}
const reads: ProbeRead[] = [];
const refusals: ProbeRefusal[] = [];
const unmet: NonNullable<ProbeFile["skills"][string]["unmet_requirement"]> = [];
const nearMiss: NonNullable<ProbeFile["skills"][string]["near_miss"]> = [];
const nearMissSkipped: Array<{ verb: string; provided: string; expected: string; reason: string }> = [];
const declaredReads = hand.verbs.filter((verb) => SAFE_EFFECTS.has(verb.effect));
const safetySkipped: NonNullable<ProbeFile["skills"][string]["safety_skipped"]> = [];
for (const verb of declaredReads.filter((v) => !isNameSafeToCall(v.name))) {
safetySkipped.push({
verb: verb.name,
effect: verb.effect,
reason: `declared "${verb.effect}" but the name says it reaches a person or spends — never called by the probe`,
});
say(` ${name}.${verb.name}: SAFETY SKIP — declared effect "${verb.effect}" but the name says it reaches a person or spends`);
}
const readVerbs = declaredReads.filter((verb) => isNameSafeToCall(verb.name));
for (const verb of readVerbs) {
const takesLimit = [...verb.required, ...verb.optional].some((arg) => /^limit$/i.test(arg));
const series = takesLimit ? LIMIT_SERIES : [undefined];
if (verb.required.length) {
// A read that demands a value the probe would have to invent is not
// probed: an invented argument measures the invention, not the read.
say(` ${name}.${verb.name}: required args [${verb.required.join(", ")}] — not probed`);
continue;
}
for (const limit of series) {
const rpc = await client.call("execute", {
name,
verb: verb.name,
...(limit === undefined ? {} : { arguments: { limit } }),
}, 180_000);
toolCalls++;
const seen = readEnvelope(rpc);
if (!server.revision_first && seen.revision) server.revision_first = seen.revision;
if (seen.revision) server.revision_last = seen.revision;
const bytes = JSON.stringify(rpc.body?.result ?? {}).length;
const read: ProbeRead = {
verb: verb.name,
limit,
chars: bytes,
bytes,
tokens: approxTokens(bytes),
duration_ms: typeof seen.inner.duration_ms === "number" ? seen.inner.duration_ms : undefined,
round_trip_ms: rpc.ms,
request_count: 1,
truncated: seen.inner.truncated === true,
cursor: seen.inner.next_cursor ?? undefined,
// SHAPE, NEVER VALUES. The corpus is committed; a person's inbox is not.
// (2026-09-09 02:4x: the first corpus carried real addresses and was scrubbed.)
content: (rpc.body?.result?.content ?? []).map((c: any) => ({ type: c?.type, chars: String(c?.text ?? "").length })),
structuredContent: undefined,
outputSchemaValid: seen.env ? typeof seen.env.ok === "boolean" && Boolean(seen.env.result) : false,
evidence_envelope: evidenceEnvelope(seen.inner),
outcome: seen.outcome,
face: faceOf(seen.faceHint, rpc.body?.result?._meta, seen.text.length),
};
if (seen.env?.result !== undefined) read.structuredContent = { present: true };
reads.push(read);
const refusal = refusalFrom(rpc, verb.name);
if (refusal) {
refusals.push(refusal);
errors++;
// A refusal that never spawned is the preflight rule 22 measures.
const spawned = seen.inner.exit_code !== null && seen.inner.exit_code !== undefined;
if (namesMissingCredential(refusal, hand.requires)) {
unmet.push({ verb: verb.name, elapsed_ms: seen.inner.duration_ms ?? rpc.ms, child_processes: spawned ? 1 : 0 });
}
}
say(` ${name}.${verb.name}${limit === undefined ? "" : ` limit=${limit}`} ${rpc.ms} ms ${bytes} B ${seen.outcome}${read.face ? ` face=${read.face.state}/${read.face.kind ?? "-"}` : ""}`);
}
// Near-miss keys, on READ verbs only.
const argNames = new Set([...verb.required, ...verb.optional]);
for (const [provided, expected] of NEAR_MISS_PAIRS) {
if (!argNames.has(expected) || argNames.has(provided)) continue;
const rpc = await client.call("execute", { name, verb: verb.name, arguments: { [provided]: "1" } }, 60_000);
toolCalls++;
const seen = readEnvelope(rpc);
const text = scrubPersonal([seen.inner.why, seen.text, JSON.stringify(seen.inner.stderr ?? "")].filter(Boolean).join(" ")).slice(0, 300);
nearMiss.push({ verb: verb.name, provided, expected, refusal: text });
say(` ${name}.${verb.name}: near-miss ${provided}→${expected} ${seen.outcome} names-key=${text.includes(expected)}`);
}
}
for (const verb of hand.verbs.filter((v) => !readVerbs.includes(v))) {
const argNames = new Set([...verb.required, ...verb.optional]);
for (const [provided, expected] of NEAR_MISS_PAIRS) {
if (!argNames.has(expected) || argNames.has(provided)) continue;
nearMissSkipped.push({
verb: verb.name,
provided,
expected,
reason: SAFE_EFFECTS.has(verb.effect)
? `declared "${verb.effect}" but named like a send — never called by the probe`
: `${verb.effect} effect — never called by the probe`,
});
}
}
// Rule 41: does search rank this hand top-three for its own name and job?
const rank = await searchRank(client, name);
toolCalls += 1;
skills[name] = {
reads,
refusals,
unmet_requirement: unmet,
near_miss: nearMiss,
search_ranks: rank === null ? [] : [rank],
...(nearMissSkipped.length ? { near_miss_skipped: nearMissSkipped } : {}),
...(safetySkipped.length ? { safety_skipped: safetySkipped } : {}),
} as ProbeFile["skills"][string];
say(` ${name}: ${reads.length} read sample(s), ${refusals.length} refusal(s), search rank ${rank ?? "unranked"}`);
}
// Rule 58: the client was created inside this run, and no deploy raced it.
server.fresh_client = true;
server.handshake_after_deploy = Boolean(server.revision_first) && server.revision_first === server.revision_last;
server.metrics = REQUIRED_METRICS;
const answered = Object.values(skills).flatMap((s) => s.reads ?? []).filter((r) => r.outcome === "answered").length;
const totalReads = Object.values(skills).flatMap((s) => s.reads ?? []).length;
server.run = {
accuracy: totalReads ? Number((answered / totalReads).toFixed(4)) : 0,
runtime: Date.now() - startedAt,
tool_call_count: toolCalls,
tokens: approxTokens(Object.values(skills).flatMap((s) => s.reads ?? []).reduce((sum, r) => sum + (r.bytes ?? 0), 0)),
errors,
};
return { schema: PROBE_SCHEMA, generated_at: new Date().toISOString(), server, skills };
}
/**
* AN UNMET REQUIREMENT IS ONE THE DOOR COULD HAVE KNOWN — nothing else.
*
* Rule 22 asks whether a requirement that is NOT MET refuses before a process
* is spawned, in under 50 ms. That question only has an answer for a key that
* is absent from this body: `missing_credential`, the closed table's own words
* for "a key named in `requires` is not held on this machine". A provider
* answering 403 (`credential_scope_denied`) or a token that has aged out
* (`credential_expired`) are BOTH discovered by making the call — grading them
* here would demand the door predict a vendor's answer, and every hand would
* fail a rule nothing could pass.
*
* MEASURED THE MOMENT THE REFUSALS BECAME READABLE ⟨lane loop-run-3⟩: with the
* hand's own shape finally lifted, the only two rows rule 22 could see were
* snappy-publish and snappy-website answering `credential_scope_denied` after
* 600 ms and one spawned child — Vercel's 403 on a token that IS held. Read as
* unmet requirements they turned a rule pinned at zero red for a defect that
* was never a defect. So the CODE decides when the hand printed one, and the
* old text scan survives only for a hand that printed no shape at all.
*/
export function namesMissingCredential(refusal: ProbeRefusal, requires: string[]): boolean {
if (refusal.code) return refusal.code === "missing_credential" || refusal.code === "credential_missing";
const text = `${refusal.message ?? ""} ${refusal.fix ?? ""}`;
return (requires.some((key) => text.includes(key)) && /missing|not set|not held|no .*(token|key|credential)/i.test(text))
|| /missing (?:credential|env|token|key)/i.test(text);
}
async function searchRank(client: McpProbeClient, name: string): Promise<number | null> {
const bare = name.replace(/^snappy-/, "");
const rpc = await client.call("search", { intent: bare, limit: 5 }, 60_000);
const matches = rpc.body?.result?.structuredContent?.result?.matches ?? [];
const index = matches.findIndex((m: any) => String(m?.name) === name);
return index < 0 ? null : index + 1;
}
/** Every hand the runner itself reports, discovered through its own search. */
async function listHands(client: McpProbeClient, say: (line: string) => void): Promise<string[]> {
const found = new Set<string>();
const intents = ["read", "send", "list", "post", "search", "file", "video", "image", "calendar", "mail", "message", "client", "content", "deploy", "data"];
for (const intent of intents) {
const rpc = await client.call("search", { intent, limit: 50 }, 60_000);
for (const match of rpc.body?.result?.structuredContent?.result?.matches ?? []) {
if (match?.kind === "hand" && typeof match.name === "string") found.add(match.name);
}
}
const names = [...found].sort();
say(` discovered ${names.length} hand(s) through the runner's own search`);
return names;
}
function requiredTokenFile(): string {
const fromEnv = process.env.SNAPPY_RUNNER_TOKEN_FILE;
if (!fromEnv) {
throw new RefusedError(
"missing_credential",
"probe needs a bearer: set SNAPPY_RUNNER_TOKEN_FILE to a 600-mode file holding the runner token " +
"(ssh mini cat ~/.snappy-skills/token > <file>), or pass --token-file. The token is never an argument.",
);
}
return fromEnv;
}
/**
* R30 PASSED BY INHERITANCE ⟨lane loose-ends, 2026-09-09⟩.
*
* WHAT WAS MEASURED. Rule 30 — "third-party text is wrapped as data, not
* instructions" — grepped `m.source`, and `m.source` is `collectLocalSource`,
* which follows EVERY local import across the whole collection. A hand whose
* api.ts says `export { read } from "../snappy-marked/api.ts"` therefore had
* snappy-marked's entire file folded into the text the rule read, and answered
* PASS with nothing of its own. snappy-update did exactly that until 177e684
* gave it words of its own; the rule never noticed the difference.
*
* WHY A BORROWED DECLARATION IS NOT A DECLARATION. The envelope is a sentence
* on the WIRE: it tells the model reading this hand's answer that the words in
* it were written outside the operator's session. The reader of this hand's
* answer never opens the other hand's file. A mark that lives only there
* protects nobody, and a rule that accepts it teaches the collection that the
* work is done.
*
* The fixture below is the whole proof, and it carries its own red: the SAME
* function, given the collection-wide boundary the rule used to use, still says
* PASS — and given the hand's own directory, says FAIL.
*/
import assert from "node:assert/strict";
import test from "node:test";
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { collectOwnSource, evidenceEnvelopeDeclared, lintSkill } from "./api.ts";
/** Two hands: one declares the boundary, one only re-exports the first's read. */
function fixture(): { root: string; borrowingApi: string; borrowingDir: string } {
const root = mkdtempSync(join(tmpdir(), "r30-scope-"));
const markedDir = join(root, "snappy-marked");
const borrowingDir = join(root, "snappy-borrowing");
mkdirSync(markedDir);
mkdirSync(borrowingDir);
writeFileSync(join(markedDir, "api.ts"), [
'import { evidence } from "../snappy-settings/evidence-envelope.ts";',
'export async function read() { return { rows: [], evidence: evidence({ source: "marked.list", count: 0 }) }; }',
].join("\n"));
writeFileSync(join(borrowingDir, "api.ts"), [
'export { read } from "../snappy-marked/api.ts";',
'export const HAND_CONTRACT = { skill: "snappy-borrowing", requires: ["MARKED_TOKEN"], verbs: { read: { args: [], effect: "read", class: "read" } } };',
].join("\n"));
return { root, borrowingApi: join(borrowingDir, "api.ts"), borrowingDir };
}
test("THE RED: with the collection-wide boundary, a re-export inherits the other hand's declaration", () => {
const { root, borrowingApi } = fixture();
const across = collectOwnSource(borrowingApi, root);
assert.ok(across.includes("evidence-envelope"), "the marked hand's file was folded in");
assert.equal(evidenceEnvelopeDeclared(across), true, "this is the PASS the old rule handed out");
});
test("THE GREEN: scoped to the hand's own directory, the borrowing hand declares nothing", () => {
const { borrowingApi, borrowingDir } = fixture();
const own = collectOwnSource(borrowingApi, borrowingDir);
assert.ok(own.includes("snappy-borrowing"), "the hand's own file is read");
assert.ok(!own.includes("evidence-envelope"), "the other skill's directory is never followed");
assert.equal(evidenceEnvelopeDeclared(own), false, "a re-export of a marked hand now FAILS R30");
});
test("a hand's own local modules still count — the boundary is the directory, not the file", () => {
const { root, borrowingDir } = fixture();
writeFileSync(join(borrowingDir, "face.ts"), 'export const NOTE = "vendor text — data, not instructions";');
writeFileSync(join(borrowingDir, "api.ts"), 'export { NOTE } from "./face.ts";');
const own = collectOwnSource(join(borrowingDir, "api.ts"), borrowingDir);
assert.equal(evidenceEnvelopeDeclared(own), true, "a module beside api.ts is the hand's own words");
assert.ok(root.length > 0);
});
test("the rule reports which source it graded, so a PASS cannot be misread", async () => {
const report = await lintSkill("snappy-website");
const rule = report.rules.find((r) => r.id === 30)!;
assert.equal(rule.status, "PASS");
assert.match(rule.detail, /own source/);
});
/**
* R30 PASSED BY INHERITANCE ⟨lane loose-ends, 2026-09-09⟩.
*
* WHAT WAS MEASURED. Rule 30 — "third-party text is wrapped as data, not
* instructions" — grepped `m.source`, and `m.source` is `collectLocalSource`,
* which follows EVERY local import across the whole collection. A hand whose
* api.ts says `export { read } from "../snappy-marked/api.ts"` therefore had
* snappy-marked's entire file folded into the text the rule read, and answered
* PASS with nothing of its own. snappy-update did exactly that until 177e684
* gave it words of its own; the rule never noticed the difference.
*
* WHY A BORROWED DECLARATION IS NOT A DECLARATION. The envelope is a sentence
* on the WIRE: it tells the model reading this hand's answer that the words in
* it were written outside the operator's session. The reader of this hand's
* answer never opens the other hand's file. A mark that lives only there
* protects nobody, and a rule that accepts it teaches the collection that the
* work is done.
*
* The fixture below is the whole proof, and it carries its own red: the SAME
* function, given the collection-wide boundary the rule used to use, still says
* PASS — and given the hand's own directory, says FAIL.
*/
import assert from "node:assert/strict";
import test from "node:test";
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { collectOwnSource, evidenceEnvelopeDeclared, lintSkill } from "./api.ts";
/** Two hands: one declares the boundary, one only re-exports the first's read. */
function fixture(): { root: string; borrowingApi: string; borrowingDir: string } {
const root = mkdtempSync(join(tmpdir(), "r30-scope-"));
const markedDir = join(root, "snappy-marked");
const borrowingDir = join(root, "snappy-borrowing");
mkdirSync(markedDir);
mkdirSync(borrowingDir);
writeFileSync(join(markedDir, "api.ts"), [
'import { evidence } from "../snappy-settings/evidence-envelope.ts";',
'export async function read() { return { rows: [], evidence: evidence({ source: "marked.list", count: 0 }) }; }',
].join("\n"));
writeFileSync(join(borrowingDir, "api.ts"), [
'export { read } from "../snappy-marked/api.ts";',
'export const HAND_CONTRACT = { skill: "snappy-borrowing", requires: ["MARKED_TOKEN"], verbs: { read: { args: [], effect: "read", class: "read" } } };',
].join("\n"));
return { root, borrowingApi: join(borrowingDir, "api.ts"), borrowingDir };
}
test("THE RED: with the collection-wide boundary, a re-export inherits the other hand's declaration", () => {
const { root, borrowingApi } = fixture();
const across = collectOwnSource(borrowingApi, root);
assert.ok(across.includes("evidence-envelope"), "the marked hand's file was folded in");
assert.equal(evidenceEnvelopeDeclared(across), true, "this is the PASS the old rule handed out");
});
test("THE GREEN: scoped to the hand's own directory, the borrowing hand declares nothing", () => {
const { borrowingApi, borrowingDir } = fixture();
const own = collectOwnSource(borrowingApi, borrowingDir);
assert.ok(own.includes("snappy-borrowing"), "the hand's own file is read");
assert.ok(!own.includes("evidence-envelope"), "the other skill's directory is never followed");
assert.equal(evidenceEnvelopeDeclared(own), false, "a re-export of a marked hand now FAILS R30");
});
test("a hand's own local modules still count — the boundary is the directory, not the file", () => {
const { root, borrowingDir } = fixture();
writeFileSync(join(borrowingDir, "face.ts"), 'export const NOTE = "vendor text — data, not instructions";');
writeFileSync(join(borrowingDir, "api.ts"), 'export { NOTE } from "./face.ts";');
const own = collectOwnSource(join(borrowingDir, "api.ts"), borrowingDir);
assert.equal(evidenceEnvelopeDeclared(own), true, "a module beside api.ts is the hand's own words");
assert.ok(root.length > 0);
});
test("the rule reports which source it graded, so a PASS cannot be misread", async () => {
const report = await lintSkill("snappy-website");
const rule = report.rules.find((r) => r.id === 30)!;
assert.equal(rule.status, "PASS");
assert.match(rule.detail, /own source/);
});
/**
* THE HAND'S OWN FIRST CALL, GRADED ⟨lane refusal-shape, 2026-09-09⟩.
*
* The loop's own tool exited 1 with a bare sentence on stderr and nothing on
* stdout for five of its seven verbs — the exact shape rule 31 exists to
* reject, printed by the tool that grades rule 31. These tests drive the real
* CLI and read what a caller reads: stdout, parsed, against the closed table.
*/
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import test from "node:test";
import { REFUSAL_CODES } from "../snappy-settings/refusal-codes.ts";
import { HAND_CONTRACT } from "./api.ts";
const here = dirname(fileURLToPath(import.meta.url));
function firstCall(...args: string[]) {
const run = spawnSync("npx", ["tsx", join(here, "api.ts"), ...args], {
cwd: here,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env, SNAPPY_RUNNER_TOKEN_FILE: "" },
});
return { code: run.status, stdout: run.stdout ?? "", stderr: run.stderr ?? "" };
}
function refusalOf(stdout: string) {
const line = stdout.split("\n").find((l) => l.trim().startsWith("{"));
assert.ok(line, `no refusal object on stdout; got ${JSON.stringify(stdout.slice(0, 200))}`);
return JSON.parse(line!) as Record<string, string>;
}
const CASES: Array<{ argv: string[]; code: keyof typeof REFUSAL_CODES; names: string }> = [
{ argv: ["lint"], code: "missing_argument", names: "lint" },
{ argv: ["example"], code: "missing_argument", names: "example" },
{ argv: ["fix-loader"], code: "missing_argument", names: "fix-loader" },
{ argv: ["probe"], code: "missing_credential", names: "SNAPPY_RUNNER_TOKEN_FILE" },
{ argv: ["no-such-verb"], code: "unknown_verb", names: "lint" },
];
for (const item of CASES) {
test(`${item.argv.join(" ")} refuses with the closed table's shape`, () => {
const run = firstCall(...item.argv);
// A refusal is never exit 0: an acceptance reported over a refusal is
// worse than an error, because an error ends the wait.
assert.equal(run.code, 1, `exit code for ${item.argv.join(" ")}`);
const refusal = refusalOf(run.stdout);
assert.equal(refusal.outcome, "refused");
assert.equal(refusal.code, item.code);
assert.ok(refusal.message && refusal.message.length > 0, "rule 31 wants a message");
assert.equal(refusal.fix, REFUSAL_CODES[item.code].fix, "fix comes from the table, never the hand");
assert.equal(refusal.contract_slice, REFUSAL_CODES[item.code].contract_slice);
// Rule 32: the refusal names a literal from the valid domain.
assert.ok(
`${refusal.message} ${refusal.fix}`.includes(item.names),
`refusal should name ${item.names}`,
);
});
}
test("the declared refusals are a projection of the one closed table", () => {
for (const [code, row] of Object.entries(HAND_CONTRACT.refusals)) {
assert.deepEqual(row, REFUSAL_CODES[code as keyof typeof REFUSAL_CODES]);
}
});
test("no refusal carries a secret value", () => {
const run = firstCall("probe");
assert.ok(!/[A-Za-z0-9_-]{40,}/.test(refusalOf(run.stdout).message ?? ""), "names the key, never the value");
});
/**
* THE HAND'S OWN FIRST CALL, GRADED ⟨lane refusal-shape, 2026-09-09⟩.
*
* The loop's own tool exited 1 with a bare sentence on stderr and nothing on
* stdout for five of its seven verbs — the exact shape rule 31 exists to
* reject, printed by the tool that grades rule 31. These tests drive the real
* CLI and read what a caller reads: stdout, parsed, against the closed table.
*/
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import test from "node:test";
import { REFUSAL_CODES } from "../snappy-settings/refusal-codes.ts";
import { HAND_CONTRACT } from "./api.ts";
const here = dirname(fileURLToPath(import.meta.url));
function firstCall(...args: string[]) {
const run = spawnSync("npx", ["tsx", join(here, "api.ts"), ...args], {
cwd: here,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env, SNAPPY_RUNNER_TOKEN_FILE: "" },
});
return { code: run.status, stdout: run.stdout ?? "", stderr: run.stderr ?? "" };
}
function refusalOf(stdout: string) {
const line = stdout.split("\n").find((l) => l.trim().startsWith("{"));
assert.ok(line, `no refusal object on stdout; got ${JSON.stringify(stdout.slice(0, 200))}`);
return JSON.parse(line!) as Record<string, string>;
}
const CASES: Array<{ argv: string[]; code: keyof typeof REFUSAL_CODES; names: string }> = [
{ argv: ["lint"], code: "missing_argument", names: "lint" },
{ argv: ["example"], code: "missing_argument", names: "example" },
{ argv: ["fix-loader"], code: "missing_argument", names: "fix-loader" },
{ argv: ["probe"], code: "missing_credential", names: "SNAPPY_RUNNER_TOKEN_FILE" },
{ argv: ["no-such-verb"], code: "unknown_verb", names: "lint" },
];
for (const item of CASES) {
test(`${item.argv.join(" ")} refuses with the closed table's shape`, () => {
const run = firstCall(...item.argv);
// A refusal is never exit 0: an acceptance reported over a refusal is
// worse than an error, because an error ends the wait.
assert.equal(run.code, 1, `exit code for ${item.argv.join(" ")}`);
const refusal = refusalOf(run.stdout);
assert.equal(refusal.outcome, "refused");
assert.equal(refusal.code, item.code);
assert.ok(refusal.message && refusal.message.length > 0, "rule 31 wants a message");
assert.equal(refusal.fix, REFUSAL_CODES[item.code].fix, "fix comes from the table, never the hand");
assert.equal(refusal.contract_slice, REFUSAL_CODES[item.code].contract_slice);
// Rule 32: the refusal names a literal from the valid domain.
assert.ok(
`${refusal.message} ${refusal.fix}`.includes(item.names),
`refusal should name ${item.names}`,
);
});
}
test("the declared refusals are a projection of the one closed table", () => {
for (const [code, row] of Object.entries(HAND_CONTRACT.refusals)) {
assert.deepEqual(row, REFUSAL_CODES[code as keyof typeof REFUSAL_CODES]);
}
});
test("no refusal carries a secret value", () => {
const run = firstCall("probe");
assert.ok(!/[A-Za-z0-9_-]{40,}/.test(refusalOf(run.stdout).message ?? ""), "names the key, never the value");
});
/**
* THE RENDER PROBE — the half of the loop that LOOKS.
*
* Rules 50–53 grade what a face does in a real host: whether it fits at 360
* and at 720 without inventing a second scroller, whether the decision row it
* drew is inside its bounds, whether the host was listening before the view
* spoke, and whether a dated screenshot from the REAL bundle exists. None of
* that can be read off source, and `renderable: true` in a JSON file is a
* claim, not an artifact — so this module drives an actual ext-apps AppBridge
* host in an actual browser and writes down what it saw.
*
* WHERE EACH PIECE COMES FROM, and why the mixture is honest:
* view.html the RUNNER'S OWN widget, read live over MCP (`ui://snappy/faces`)
* kinds the BUNDLE'S OWN `dist/build-report.json` `draws`
* data the FACES SKILL'S OWN committed fixtures (Mara Quill, invented)
* envelope assembled here — see below
*
* The envelope is assembled locally for one measured reason: `execute` cannot
* pass `snappy-faces lang`'s `--json` flag, so the server answers that verb as
* prose and `face_hint.state` comes back "none" with an empty `hand_result`.
* Measured 2026-09-08 through the mini runner with `{"--json":true}` and
* `{"json":true}` — both ignored. That gap is recorded as a finding by `loop`
* rather than papered over; until it closes, the drawn DATA and the drawn VIEW
* are the server's and only the wrapper is ours.
*/
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, join } from "node:path";
import { McpProbeClient, MINI_MCP_URL } from "./probe.ts";
import { decodeBrowserEval } from "./browser-eval.ts";
import { RENDER_SCHEMA, type RenderFile, type RenderKind } from "./corpus.ts";
export const DEFAULT_WIDTHS = [360, 720];
const HOST_PORT = 18823;
export interface RenderOptions {
/** The loop directory holding host.html, host-bundle.js and static.mjs. */
hostDir: string;
/** Where screenshots land. */
shotDir: string;
bundleDir?: string;
tokenFile?: string;
url?: string;
widths?: number[];
/** Render only these kinds. The whole corpus is 97 kinds and eight minutes;
* one kind is how a THREW row is reproduced without spending that, and
* without colliding with a full run already in flight ⟨2026-09-09⟩. */
kinds?: string[];
onLine?: (line: string) => void;
}
interface Draw {
kind: string;
family: string;
member: string;
label: string;
component: string;
fixture: string;
}
/**
* WHICH HAND DREW THIS, read off the artifact instead of a list.
*
* ⟨2026-09-09, lane render-vacuous⟩ This used to be `FAMILY_OWNERS`, a
* hand-written two-entry map naming gmail and linkedin. It was wrong for 29 of
* the 31 published families, and being wrong here is silent: `renderFor` finds
* no families for a skill, and rules 50–53 stop measuring it. A second
* hand-kept copy of an answer the bundle already states is the duplicate road
* ⟨CLAUDE.md §4⟩.
*
* The bundle already states it. Since faces moved into `skills/<hand>/faces/`
* ⟨the owner's decision 2026-09-09 09:3x⟩, every draw's `fixture` is the path
* `snappy-faces/face-homes.ts` resolved for it — `../snappy-gmail/faces/
* fixtures/gmail-list.json`. The hand in that path IS the family's home, so
* the home is READ, never remembered, and a family that moves to a new hand
* moves here with no edit. A family the faces library still keeps has no hand
* in its path and answers `null`; it is owned by the publisher alone.
*/
export function drawOwner(fixture: string): string | null {
return /(?:^|\/)(snappy-[a-z0-9-]+)\/faces\//.exec(fixture)?.[1] ?? null;
}
export function bundleRoot(explicit?: string): string {
const candidates = [
explicit,
"/Users/robertboulos/Projects/snappy-runner/bundle/skills/snappy-faces",
join(dirname(dirname(new URL(import.meta.url).pathname)), "snappy-faces"),
].filter(Boolean) as string[];
for (const candidate of candidates) {
if (existsSync(join(candidate, "dist", "build-report.json"))) return candidate;
}
throw new Error(`no snappy-faces bundle with dist/build-report.json in [${candidates.join(", ")}]`);
}
export function readDraws(root: string): Draw[] {
return JSON.parse(readFileSync(join(root, "dist", "build-report.json"), "utf8")).draws as Draw[];
}
/** The kinds a run will actually draw. An unknown name is a refusal, not a
* silently empty run — an empty render file grades every rule 50–53 DEFER. */
export function selectDraws(draws: Draw[], kinds?: string[]): Draw[] {
if (!kinds?.length) return draws;
const missing = kinds.filter((kind) => !draws.some((draw) => draw.kind === kind));
if (missing.length) throw new Error(`no such kind in the bundle's build-report: ${missing.join(", ")}`);
return draws.filter((draw) => kinds.includes(draw.kind));
}
/** The tool result the host delivers, in the shape the widget's shell reads. */
export function buildPayload(root: string, draw: Draw) {
const data = JSON.parse(readFileSync(join(root, draw.fixture), "utf8"));
const handResult = { kind: draw.kind, data, resource_uri: "ui://snappy/faces" };
return {
arguments: { name: "snappy-faces", verb: "lang", arguments: { kind: draw.kind } },
content: [{ type: "text", text: `snappy-faces lang ${draw.kind} → answered` }],
structuredContent: {
ok: true,
tool: "execute",
result: {
skill: "snappy-faces", verb: "lang", effect: "read", effect_class: "runs-in-the-call",
argv: ["lang", draw.kind], outcome: "answered", exit_code: 0, signal: null,
duration_ms: 0, timed_out: false, stdout: "", stderr: "", stdout_bytes: 0, stderr_bytes: 0,
truncated: false, call_id: `render-${draw.kind}`, cursor: 0, next_cursor: null,
hand_result: handResult, why: "the hand answered and named no failure",
},
face_hint: {
state: "named", resource_uri: "ui://snappy/faces", family: draw.family,
slot: draw.member, kind: draw.kind, data_path: "result.hand_result.data",
why: "the bundle's own build-report names this kind drawable",
},
},
_meta: {
ui: { resourceUri: "ui://snappy/faces" },
"ui/resourceUri": "ui://snappy/faces",
"openai/outputTemplate": "ui://snappy/faces",
},
};
}
function browser(args: string[], timeoutMs = 90_000): string {
return execFileSync("agent-browser", args, { encoding: "utf8", timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 });
}
/** WHAT THE PAGE SAW, READ THE ONLY WAY IT CAN BE READ.
* `agent-browser eval` prints its return value json-encoded, so the answer is
* two layers of JSON and `JSON.parse` is the only thing allowed to undo
* `JSON.stringify` — see browser-eval.ts for the kind this cost us. */
function readLoopState(out: string): LoopState {
const state = decodeBrowserEval<LoopState>(out);
if (!state) throw new Error(`window.__loopState did not decode from ${out.slice(0, 160)}`);
return state;
}
interface LoopState {
state: string;
detail?: {
firstPaintMs?: number | null; height?: number | null; nested_scroll?: boolean | null;
painted?: number; buttons?: string[]; pressed?: RenderKind["press"];
} | null;
}
export async function runRender(options: RenderOptions): Promise<RenderFile> {
const say = options.onLine ?? ((line: string) => console.log(line));
const widths = options.widths ?? DEFAULT_WIDTHS;
const root = bundleRoot(options.bundleDir);
const draws = selectDraws(readDraws(root), options.kinds);
mkdirSync(options.shotDir, { recursive: true });
mkdirSync(join(options.hostDir, "payloads"), { recursive: true });
say(`render: ${draws.length} kind(s) from ${root}/dist/build-report.json at ${widths.join(" and ")} px`);
// THE VIEW IS THE SERVER'S. Read live so a stale local copy can never be
// what got measured.
const tokenFile = options.tokenFile ?? process.env.SNAPPY_RUNNER_TOKEN_FILE;
if (!tokenFile) throw new Error("render needs SNAPPY_RUNNER_TOKEN_FILE (600-mode file holding the runner bearer)");
const client = new McpProbeClient(options.url ?? MINI_MCP_URL, readFileSync(tokenFile, "utf8").trim());
const view = await client.rpc("resources/read", { uri: "ui://snappy/faces" });
const html = view.body?.result?.contents?.[0]?.text;
if (!html) throw new Error("resources/read ui://snappy/faces returned no text");
writeFileSync(join(options.hostDir, "view.html"), html);
say(` view.html ${html.length} B read live from the runner (${view.ms} ms)`);
for (const draw of draws) {
writeFileSync(join(options.hostDir, "payloads", `${draw.kind}.json`), JSON.stringify(buildPayload(root, draw), null, 1));
}
const server = spawn("node", [join(options.hostDir, "static.mjs"), String(HOST_PORT), options.hostDir, options.url ?? MINI_MCP_URL, tokenFile], { stdio: "ignore", detached: false });
await new Promise((resolve) => setTimeout(resolve, 900));
const kinds: RenderKind[] = [];
try {
for (const draw of draws) {
const row: RenderKind = { kind: draw.kind, family: draw.family, member: draw.member, component: draw.component, skill: drawOwner(draw.fixture), widths: {}, buttons: [], press: null, shot: null, threw: null };
for (const width of widths) {
const url = `http://127.0.0.1:${HOST_PORT}/host.html?payload=/payloads/${draw.kind}.json&width=${width}`;
try {
browser(["open", url]);
browser(["wait", "html[data-face-loop]"], 60_000);
const state = readLoopState(browser(["eval", "JSON.stringify(window.__loopState)"], 30_000));
const detail = state.detail ?? {};
row.widths[String(width)] = {
first_paint_ms: detail.firstPaintMs ?? null,
drew: state.state,
height: detail.height ?? null,
nested_scroll: detail.nested_scroll ?? null,
painted: detail.painted ?? 0,
};
if (detail.buttons?.length) row.buttons = detail.buttons;
if (width === Math.max(...widths)) {
const shot = join(options.shotDir, `${draw.kind}-${width}.png`);
browser(["screenshot", shot], 60_000);
row.shot = shot;
}
say(` ${draw.kind} @${width} ${state.state} paint=${detail.firstPaintMs ?? "-"}ms h=${detail.height ?? "-"} nested=${detail.nested_scroll} buttons=${(detail.buttons ?? []).length}`);
} catch (error) {
row.threw = String(error).slice(0, 200);
row.widths[String(width)] = { first_paint_ms: null, drew: "threw", height: null, nested_scroll: null, painted: 0 };
say(` ${draw.kind} @${width} THREW ${row.threw}`);
}
}
// A decision row is only proven when a press reaches `execute`.
if (row.buttons.length) {
const label = row.buttons[0];
const url = `http://127.0.0.1:${HOST_PORT}/host.html?payload=/payloads/${draw.kind}.json&width=720&press=${encodeURIComponent(label)}`;
try {
browser(["open", url]);
browser(["wait", "html[data-face-loop]"], 90_000);
const state = readLoopState(browser(["eval", "JSON.stringify(window.__loopState)"], 30_000));
row.press = state.detail?.pressed ?? null;
say(` ${draw.kind} press "${label}" → reached execute=${row.press?.reached_execute}`);
} catch (error) {
row.press = { word: label, reached_execute: false, outcome: `press threw: ${String(error).slice(0, 120)}` };
}
}
kinds.push(row);
}
} finally {
server.kill("SIGTERM");
try { browser(["close", "--all"], 20_000); } catch { /* the browser may already be gone */ }
}
return { schema: RENDER_SCHEMA, generated_at: new Date().toISOString(), host: { ext_apps: "1.7.5", widths, resource_uri: "ui://snappy/faces" }, kinds, ...gradeFamilies(kinds, basename(root)) };
}
/** The four booleans rules 50–53 read, derived per FAMILY from what was seen.
*
* `publisher` is the skill whose bundle was measured — `basename(bundleRoot())`,
* read off the directory rather than typed, so the one skill that draws every
* family is graded on every family. A hand named by a row's `skill` owns the
* family whose faces live in its folder; nothing else claims a family here. */
export function gradeFamilies(kinds: RenderKind[], publisher: string): Pick<RenderFile, "families" | "skill_families"> {
const families: RenderFile["families"] = {};
for (const row of kinds) {
const seen = Object.values(row.widths);
const drewEverywhere = seen.length > 0 && seen.every((w) => w.drew === "drew");
const fits = seen.every((w) => w.nested_scroll === false);
const bounded = row.buttons.length <= 2 || row.member !== "decision";
const shot = Boolean(row.shot);
const current = families[row.family] ?? { auto_fit: true, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true };
families[row.family] = {
auto_fit: current.auto_fit && drewEverywhere && fits,
action_item_bounds: current.action_item_bounds && bounded,
handshake_race_free: current.handshake_race_free && seen.every((w) => w.drew !== "no-init"),
dated_real_host_screenshot: current.dated_real_host_screenshot && shot,
};
}
const owners = new Map<string, Set<string>>();
const claim = (skill: string, family: string) => {
owners.set(skill, (owners.get(skill) ?? new Set()).add(family));
};
for (const family of Object.keys(families)) claim(publisher, family);
for (const row of kinds) if (row.skill) claim(row.skill, row.family);
const skill_families: RenderFile["skill_families"] = {};
for (const [skill, set] of [...owners].sort(([a], [b]) => a.localeCompare(b))) {
skill_families[skill] = [...set];
}
return { families, skill_families };
}
/**
* THE RENDER PROBE — the half of the loop that LOOKS.
*
* Rules 50–53 grade what a face does in a real host: whether it fits at 360
* and at 720 without inventing a second scroller, whether the decision row it
* drew is inside its bounds, whether the host was listening before the view
* spoke, and whether a dated screenshot from the REAL bundle exists. None of
* that can be read off source, and `renderable: true` in a JSON file is a
* claim, not an artifact — so this module drives an actual ext-apps AppBridge
* host in an actual browser and writes down what it saw.
*
* WHERE EACH PIECE COMES FROM, and why the mixture is honest:
* view.html the RUNNER'S OWN widget, read live over MCP (`ui://snappy/faces`)
* kinds the BUNDLE'S OWN `dist/build-report.json` `draws`
* data the FACES SKILL'S OWN committed fixtures (Mara Quill, invented)
* envelope assembled here — see below
*
* The envelope is assembled locally for one measured reason: `execute` cannot
* pass `snappy-faces lang`'s `--json` flag, so the server answers that verb as
* prose and `face_hint.state` comes back "none" with an empty `hand_result`.
* Measured 2026-09-08 through the mini runner with `{"--json":true}` and
* `{"json":true}` — both ignored. That gap is recorded as a finding by `loop`
* rather than papered over; until it closes, the drawn DATA and the drawn VIEW
* are the server's and only the wrapper is ours.
*/
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, join } from "node:path";
import { McpProbeClient, MINI_MCP_URL } from "./probe.ts";
import { decodeBrowserEval } from "./browser-eval.ts";
import { RENDER_SCHEMA, type RenderFile, type RenderKind } from "./corpus.ts";
export const DEFAULT_WIDTHS = [360, 720];
const HOST_PORT = 18823;
export interface RenderOptions {
/** The loop directory holding host.html, host-bundle.js and static.mjs. */
hostDir: string;
/** Where screenshots land. */
shotDir: string;
bundleDir?: string;
tokenFile?: string;
url?: string;
widths?: number[];
/** Render only these kinds. The whole corpus is 97 kinds and eight minutes;
* one kind is how a THREW row is reproduced without spending that, and
* without colliding with a full run already in flight ⟨2026-09-09⟩. */
kinds?: string[];
onLine?: (line: string) => void;
}
interface Draw {
kind: string;
family: string;
member: string;
label: string;
component: string;
fixture: string;
}
/**
* WHICH HAND DREW THIS, read off the artifact instead of a list.
*
* ⟨2026-09-09, lane render-vacuous⟩ This used to be `FAMILY_OWNERS`, a
* hand-written two-entry map naming gmail and linkedin. It was wrong for 29 of
* the 31 published families, and being wrong here is silent: `renderFor` finds
* no families for a skill, and rules 50–53 stop measuring it. A second
* hand-kept copy of an answer the bundle already states is the duplicate road
* ⟨CLAUDE.md §4⟩.
*
* The bundle already states it. Since faces moved into `skills/<hand>/faces/`
* ⟨the owner's decision 2026-09-09 09:3x⟩, every draw's `fixture` is the path
* `snappy-faces/face-homes.ts` resolved for it — `../snappy-gmail/faces/
* fixtures/gmail-list.json`. The hand in that path IS the family's home, so
* the home is READ, never remembered, and a family that moves to a new hand
* moves here with no edit. A family the faces library still keeps has no hand
* in its path and answers `null`; it is owned by the publisher alone.
*/
export function drawOwner(fixture: string): string | null {
return /(?:^|\/)(snappy-[a-z0-9-]+)\/faces\//.exec(fixture)?.[1] ?? null;
}
export function bundleRoot(explicit?: string): string {
const candidates = [
explicit,
"/Users/robertboulos/Projects/snappy-runner/bundle/skills/snappy-faces",
join(dirname(dirname(new URL(import.meta.url).pathname)), "snappy-faces"),
].filter(Boolean) as string[];
for (const candidate of candidates) {
if (existsSync(join(candidate, "dist", "build-report.json"))) return candidate;
}
throw new Error(`no snappy-faces bundle with dist/build-report.json in [${candidates.join(", ")}]`);
}
export function readDraws(root: string): Draw[] {
return JSON.parse(readFileSync(join(root, "dist", "build-report.json"), "utf8")).draws as Draw[];
}
/** The kinds a run will actually draw. An unknown name is a refusal, not a
* silently empty run — an empty render file grades every rule 50–53 DEFER. */
export function selectDraws(draws: Draw[], kinds?: string[]): Draw[] {
if (!kinds?.length) return draws;
const missing = kinds.filter((kind) => !draws.some((draw) => draw.kind === kind));
if (missing.length) throw new Error(`no such kind in the bundle's build-report: ${missing.join(", ")}`);
return draws.filter((draw) => kinds.includes(draw.kind));
}
/** The tool result the host delivers, in the shape the widget's shell reads. */
export function buildPayload(root: string, draw: Draw) {
const data = JSON.parse(readFileSync(join(root, draw.fixture), "utf8"));
const handResult = { kind: draw.kind, data, resource_uri: "ui://snappy/faces" };
return {
arguments: { name: "snappy-faces", verb: "lang", arguments: { kind: draw.kind } },
content: [{ type: "text", text: `snappy-faces lang ${draw.kind} → answered` }],
structuredContent: {
ok: true,
tool: "execute",
result: {
skill: "snappy-faces", verb: "lang", effect: "read", effect_class: "runs-in-the-call",
argv: ["lang", draw.kind], outcome: "answered", exit_code: 0, signal: null,
duration_ms: 0, timed_out: false, stdout: "", stderr: "", stdout_bytes: 0, stderr_bytes: 0,
truncated: false, call_id: `render-${draw.kind}`, cursor: 0, next_cursor: null,
hand_result: handResult, why: "the hand answered and named no failure",
},
face_hint: {
state: "named", resource_uri: "ui://snappy/faces", family: draw.family,
slot: draw.member, kind: draw.kind, data_path: "result.hand_result.data",
why: "the bundle's own build-report names this kind drawable",
},
},
_meta: {
ui: { resourceUri: "ui://snappy/faces" },
"ui/resourceUri": "ui://snappy/faces",
"openai/outputTemplate": "ui://snappy/faces",
},
};
}
function browser(args: string[], timeoutMs = 90_000): string {
return execFileSync("agent-browser", args, { encoding: "utf8", timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 });
}
/** WHAT THE PAGE SAW, READ THE ONLY WAY IT CAN BE READ.
* `agent-browser eval` prints its return value json-encoded, so the answer is
* two layers of JSON and `JSON.parse` is the only thing allowed to undo
* `JSON.stringify` — see browser-eval.ts for the kind this cost us. */
function readLoopState(out: string): LoopState {
const state = decodeBrowserEval<LoopState>(out);
if (!state) throw new Error(`window.__loopState did not decode from ${out.slice(0, 160)}`);
return state;
}
interface LoopState {
state: string;
detail?: {
firstPaintMs?: number | null; height?: number | null; nested_scroll?: boolean | null;
painted?: number; buttons?: string[]; pressed?: RenderKind["press"];
} | null;
}
export async function runRender(options: RenderOptions): Promise<RenderFile> {
const say = options.onLine ?? ((line: string) => console.log(line));
const widths = options.widths ?? DEFAULT_WIDTHS;
const root = bundleRoot(options.bundleDir);
const draws = selectDraws(readDraws(root), options.kinds);
mkdirSync(options.shotDir, { recursive: true });
mkdirSync(join(options.hostDir, "payloads"), { recursive: true });
say(`render: ${draws.length} kind(s) from ${root}/dist/build-report.json at ${widths.join(" and ")} px`);
// THE VIEW IS THE SERVER'S. Read live so a stale local copy can never be
// what got measured.
const tokenFile = options.tokenFile ?? process.env.SNAPPY_RUNNER_TOKEN_FILE;
if (!tokenFile) throw new Error("render needs SNAPPY_RUNNER_TOKEN_FILE (600-mode file holding the runner bearer)");
const client = new McpProbeClient(options.url ?? MINI_MCP_URL, readFileSync(tokenFile, "utf8").trim());
const view = await client.rpc("resources/read", { uri: "ui://snappy/faces" });
const html = view.body?.result?.contents?.[0]?.text;
if (!html) throw new Error("resources/read ui://snappy/faces returned no text");
writeFileSync(join(options.hostDir, "view.html"), html);
say(` view.html ${html.length} B read live from the runner (${view.ms} ms)`);
for (const draw of draws) {
writeFileSync(join(options.hostDir, "payloads", `${draw.kind}.json`), JSON.stringify(buildPayload(root, draw), null, 1));
}
const server = spawn("node", [join(options.hostDir, "static.mjs"), String(HOST_PORT), options.hostDir, options.url ?? MINI_MCP_URL, tokenFile], { stdio: "ignore", detached: false });
await new Promise((resolve) => setTimeout(resolve, 900));
const kinds: RenderKind[] = [];
try {
for (const draw of draws) {
const row: RenderKind = { kind: draw.kind, family: draw.family, member: draw.member, component: draw.component, skill: drawOwner(draw.fixture), widths: {}, buttons: [], press: null, shot: null, threw: null };
for (const width of widths) {
const url = `http://127.0.0.1:${HOST_PORT}/host.html?payload=/payloads/${draw.kind}.json&width=${width}`;
try {
browser(["open", url]);
browser(["wait", "html[data-face-loop]"], 60_000);
const state = readLoopState(browser(["eval", "JSON.stringify(window.__loopState)"], 30_000));
const detail = state.detail ?? {};
row.widths[String(width)] = {
first_paint_ms: detail.firstPaintMs ?? null,
drew: state.state,
height: detail.height ?? null,
nested_scroll: detail.nested_scroll ?? null,
painted: detail.painted ?? 0,
};
if (detail.buttons?.length) row.buttons = detail.buttons;
if (width === Math.max(...widths)) {
const shot = join(options.shotDir, `${draw.kind}-${width}.png`);
browser(["screenshot", shot], 60_000);
row.shot = shot;
}
say(` ${draw.kind} @${width} ${state.state} paint=${detail.firstPaintMs ?? "-"}ms h=${detail.height ?? "-"} nested=${detail.nested_scroll} buttons=${(detail.buttons ?? []).length}`);
} catch (error) {
row.threw = String(error).slice(0, 200);
row.widths[String(width)] = { first_paint_ms: null, drew: "threw", height: null, nested_scroll: null, painted: 0 };
say(` ${draw.kind} @${width} THREW ${row.threw}`);
}
}
// A decision row is only proven when a press reaches `execute`.
if (row.buttons.length) {
const label = row.buttons[0];
const url = `http://127.0.0.1:${HOST_PORT}/host.html?payload=/payloads/${draw.kind}.json&width=720&press=${encodeURIComponent(label)}`;
try {
browser(["open", url]);
browser(["wait", "html[data-face-loop]"], 90_000);
const state = readLoopState(browser(["eval", "JSON.stringify(window.__loopState)"], 30_000));
row.press = state.detail?.pressed ?? null;
say(` ${draw.kind} press "${label}" → reached execute=${row.press?.reached_execute}`);
} catch (error) {
row.press = { word: label, reached_execute: false, outcome: `press threw: ${String(error).slice(0, 120)}` };
}
}
kinds.push(row);
}
} finally {
server.kill("SIGTERM");
try { browser(["close", "--all"], 20_000); } catch { /* the browser may already be gone */ }
}
return { schema: RENDER_SCHEMA, generated_at: new Date().toISOString(), host: { ext_apps: "1.7.5", widths, resource_uri: "ui://snappy/faces" }, kinds, ...gradeFamilies(kinds, basename(root)) };
}
/** The four booleans rules 50–53 read, derived per FAMILY from what was seen.
*
* `publisher` is the skill whose bundle was measured — `basename(bundleRoot())`,
* read off the directory rather than typed, so the one skill that draws every
* family is graded on every family. A hand named by a row's `skill` owns the
* family whose faces live in its folder; nothing else claims a family here. */
export function gradeFamilies(kinds: RenderKind[], publisher: string): Pick<RenderFile, "families" | "skill_families"> {
const families: RenderFile["families"] = {};
for (const row of kinds) {
const seen = Object.values(row.widths);
const drewEverywhere = seen.length > 0 && seen.every((w) => w.drew === "drew");
const fits = seen.every((w) => w.nested_scroll === false);
const bounded = row.buttons.length <= 2 || row.member !== "decision";
const shot = Boolean(row.shot);
const current = families[row.family] ?? { auto_fit: true, action_item_bounds: true, handshake_race_free: true, dated_real_host_screenshot: true };
families[row.family] = {
auto_fit: current.auto_fit && drewEverywhere && fits,
action_item_bounds: current.action_item_bounds && bounded,
handshake_race_free: current.handshake_race_free && seen.every((w) => w.drew !== "no-init"),
dated_real_host_screenshot: current.dated_real_host_screenshot && shot,
};
}
const owners = new Map<string, Set<string>>();
const claim = (skill: string, family: string) => {
owners.set(skill, (owners.get(skill) ?? new Set()).add(family));
};
for (const family of Object.keys(families)) claim(publisher, family);
for (const row of kinds) if (row.skill) claim(row.skill, row.family);
const skill_families: RenderFile["skill_families"] = {};
for (const [skill, set] of [...owners].sort(([a], [b]) => a.localeCompare(b))) {
skill_families[skill] = [...set];
}
return { families, skill_families };
}
/**
* THE PRINTERS. Lifted out of api.ts on 2026-09-08 for one reason: the file
* was 893 lines and the loop's three new verbs would have carried it past the
* 900-line cap. Printing is a job of its own; grading is the file's job.
*/
import type { LintResult, RuleDefinition } from "./api.ts";
import { RULES } from "./api.ts";
export function printOne(report: LintResult, summary = false): void {
if (summary) {
console.log(`${report.skill}: ${report.pass} pass, ${report.fail} fail, ${report.defer} defer`);
return;
}
console.log(`${report.skill}: ${report.pass} pass, ${report.fail} fail, ${report.defer} defer`);
for (const rule of report.rules) {
console.log(`${rule.status} R${rule.id} [${rule.sources.join(", ")}] ${rule.rule}: ${rule.detail}`);
}
}
export function printAll(reports: LintResult[], summary = false, limit = 100): void {
if (!summary) {
console.log("| skill | pass | fail | defer |");
console.log("|---|---:|---:|---:|");
for (const report of reports.slice(0, limit)) console.log(`| ${report.skill} | ${report.pass} | ${report.fail} | ${report.defer} |`);
}
console.log("\n| rule | source | pass | fail | defer |");
console.log("|---|---|---:|---:|---:|");
for (const definition of RULES) {
const statuses = reports.map((report) => report.rules.find((rule) => rule.id === definition.id)?.status);
console.log(`| R${definition.id} ${definition.rule} | ${definition.sources.join(", ")} | ${statuses.filter((status) => status === "PASS").length} | ${statuses.filter((status) => status === "FAIL").length} | ${statuses.filter((status) => status === "DEFER").length} |`);
}
}
/**
* THE PRINTERS. Lifted out of api.ts on 2026-09-08 for one reason: the file
* was 893 lines and the loop's three new verbs would have carried it past the
* 900-line cap. Printing is a job of its own; grading is the file's job.
*/
import type { LintResult, RuleDefinition } from "./api.ts";
import { RULES } from "./api.ts";
export function printOne(report: LintResult, summary = false): void {
if (summary) {
console.log(`${report.skill}: ${report.pass} pass, ${report.fail} fail, ${report.defer} defer`);
return;
}
console.log(`${report.skill}: ${report.pass} pass, ${report.fail} fail, ${report.defer} defer`);
for (const rule of report.rules) {
console.log(`${rule.status} R${rule.id} [${rule.sources.join(", ")}] ${rule.rule}: ${rule.detail}`);
}
}
export function printAll(reports: LintResult[], summary = false, limit = 100): void {
if (!summary) {
console.log("| skill | pass | fail | defer |");
console.log("|---|---:|---:|---:|");
for (const report of reports.slice(0, limit)) console.log(`| ${report.skill} | ${report.pass} | ${report.fail} | ${report.defer} |`);
}
console.log("\n| rule | source | pass | fail | defer |");
console.log("|---|---|---:|---:|---:|");
for (const definition of RULES) {
const statuses = reports.map((report) => report.rules.find((rule) => rule.id === definition.id)?.status);
console.log(`| R${definition.id} ${definition.rule} | ${definition.sources.join(", ")} | ${statuses.filter((status) => status === "PASS").length} | ${statuses.filter((status) => status === "FAIL").length} | ${statuses.filter((status) => status === "DEFER").length} |`);
}
}
Level-3 reference for SKILL.md. Read it when wiring the lint into a gate or
when you need the dated census rather than the rules.
snappy-settings/scripts/skill-check.sh runs this lint LAST, after its own
F/A/S/I/X rules and after recipe-lint, and it can never turn the gate red.
Exactly what it does:
TOOL_DESIGN="$SKILLS_DIR/snappy-tool-design/api.ts", whereSKILLS_DIR is $SNAPPY_SKILLS_DIR or ~/.claude/skills. If that file is
absent — a checkout without this skill — the rung is skipped in silence,
which is why an older tree keeps its old result.
tool-design lint (TD1 advisory)....lint --all --summary: one processthat imports every HAND_CONTRACT and prints the sixty-row rule table.
With exactly one target it runs lint <skill> --summary. With none it says
no target skills.
could not complete (non-blocking). The rung contributes nothing to FAIL`,
nothing to FAILED_SKILLS, and nothing to the exit code. skill-check
exits on its own count alone.
The reason it is advisory in its first lane: the collection's rule-6 and
rule-60 debt is ~97 skills wide. A rung that turned that red on day one would
be switched off by the first person who met it, and a switched-off gate
measures nothing.
skill-check runs against $SKILLS_DIR, and ~/.claude/skills/snappy-* are
symlinks into the MAIN kernel checkout — not a worktree. To lint the tree you
are editing, pass the root:
bashSNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh --quiet
snappy-tool-design's own lint needs no such flag: it resolves its root as
the parent of its own directory, so it always grades the collection it ships in.
Rule 59 flags six verbs. Three were the loaders that taught a refusal that
night; the lint then found three more nobody had reported:
| Skill | Verb | Grammar |
|---|---|---|
snappy-corpus |
read |
path, offset?, limit? |
snappy-imessage |
recent |
contact?, limit? |
snappy-krisp |
fetch-meetings |
after-date?, limit? |
snappy-thumbnails |
audit |
channel?, limit? |
snappy-transcripts |
read |
path, offset?, limit? |
snappy-youtube |
recent |
channel?, limit? |
Rule 60 passes on five: snappy-gmail, snappy-imessage, snappy-krisp,
snappy-telegram, snappy-tool-design. The other 93 loaders carry no
generated block, so their first call still comes from prose.
Rule 54 is where "a status is only as true as the artifact it implies" is
mechanical here: a skill that declares itself ready is graded against the eval
scenarios that readiness implies, and 55 of 98 declare a readiness no manifest
supports.
# snappy-tool-design — the skill-check rung and the measured collection Level-3 reference for `SKILL.md`. Read it when wiring the lint into a gate or when you need the dated census rather than the rules. ## The warning rung inside skill-check `snappy-settings/scripts/skill-check.sh` runs this lint LAST, after its own F/A/S/I/X rules and after `recipe-lint`, and it can never turn the gate red. Exactly what it does: 1. It resolves `TOOL_DESIGN="$SKILLS_DIR/snappy-tool-design/api.ts"`, where `SKILLS_DIR` is `$SNAPPY_SKILLS_DIR` or `~/.claude/skills`. If that file is absent — a checkout without this skill — the rung is skipped in silence, which is why an older tree keeps its old result. 2. It prints `tool-design lint (TD1 advisory)...`. 3. With more than one target skill it runs `lint --all --summary`: one process that imports every `HAND_CONTRACT` and prints the sixty-row rule table. With exactly one target it runs `lint <skill> --summary`. With none it says `no target skills`. 4. Every failure mode is swallowed into one line — `warning: tool-design lint could not complete (non-blocking)`. The rung contributes nothing to `FAIL`, nothing to `FAILED_SKILLS`, and nothing to the exit code. `skill-check` exits on its own count alone. The reason it is advisory in its first lane: the collection's rule-6 and rule-60 debt is ~97 skills wide. A rung that turned that red on day one would be switched off by the first person who met it, and a switched-off gate measures nothing. `skill-check` runs against `$SKILLS_DIR`, and `~/.claude/skills/snappy-*` are symlinks into the MAIN kernel checkout — not a worktree. To lint the tree you are editing, pass the root: ```bash SNAPPY_SKILLS_DIR=$PWD/skills bash skills/snappy-settings/scripts/skill-check.sh --quiet ``` `snappy-tool-design`'s own `lint` needs no such flag: it resolves its root as the parent of its own directory, so it always grades the collection it ships in. ## The collection as measured (2026-09-08, 98 skills) Rule 59 flags six verbs. Three were the loaders that taught a refusal that night; the lint then found three more nobody had reported: | Skill | Verb | Grammar | |---|---|---| | `snappy-corpus` | `read` | `path, offset?, limit?` | | `snappy-imessage` | `recent` | `contact?, limit?` | | `snappy-krisp` | `fetch-meetings` | `after-date?, limit?` | | `snappy-thumbnails` | `audit` | `channel?, limit?` | | `snappy-transcripts` | `read` | `path, offset?, limit?` | | `snappy-youtube` | `recent` | `channel?, limit?` | Rule 60 passes on five: `snappy-gmail`, `snappy-imessage`, `snappy-krisp`, `snappy-telegram`, `snappy-tool-design`. The other 93 loaders carry no generated block, so their first call still comes from prose. Rule 54 is where "a status is only as true as the artifact it implies" is mechanical here: a skill that declares itself ready is graded against the eval scenarios that readiness implies, and 55 of 98 declare a readiness no manifest supports.
import assert from "node:assert/strict";
import test from "node:test";
import { byFamilyAffinity, driftOf, driftSentence, namesValue, vendorOf } from "./spec-corpus.ts";
import type { SpecProjection } from "../snappy-settings/spec-read.ts";
const projection: SpecProjection = {
skill: "snappy-image",
kind: "openapi",
url: "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.json",
family: "openapi",
version: "2.3.0",
sha256: "a".repeat(64),
fetched_at: "2026-09-09T20:44:11Z",
operations: { generate: { id: "createImage", present: true }, retired: { id: "createImageEdit", present: false } },
operations_total: 289,
enums: { model: ["dall-e-2", "dall-e-3", "gpt-image-1", "gpt-image-2.5-flare", "gpt-image-2.5-sunburst"], quality: ["auto", "high", "max", "xhigh"] },
};
test("a distinctive value is named bare; a plain word must be a literal", () => {
assert.equal(namesValue("we use gpt-image-1 for text", "gpt-image-1"), true);
assert.equal(namesValue("--size 1024x1536 is the default", "1024x1536"), true);
// `gpt-image-1` must NOT match inside `gpt-image-1-mini`, or every hand that
// names one member would be credited with the whole family.
assert.equal(namesValue("we use gpt-image-1-mini", "gpt-image-1"), false);
// The false positive this split exists for: "full" is in almost every
// SKILL.md ever written.
assert.equal(namesValue("returns the full message body", "full"), false);
assert.equal(namesValue('format: "full"', "full"), true);
assert.equal(namesValue("pass `raw` to get the source", "raw"), true);
});
test("a hand that names none of an enum is not drifting against it", () => {
const drift = driftOf({ kind: "openapi", url: projection.url }, projection, "this hand mentions no model at all");
assert.equal(drift.enums.length, 0, "no position taken means the vendor default applies; that is not a stale choice");
assert.deepEqual(drift.gone, [{ verb: "retired", id: "createImageEdit" }]);
});
test("a hand that named some members is told exactly which it is missing", () => {
const drift = driftOf({ kind: "openapi", url: projection.url }, projection, "we call gpt-image-1 and dall-e-3");
const models = drift.enums.find((row) => row.param === "model");
assert.deepEqual(models?.offers, ["dall-e-3", "gpt-image-1"]);
assert.deepEqual(models?.missing, ["gpt-image-2.5-sunburst", "gpt-image-2.5-flare", "dall-e-2"], "the family the hand already uses leads, newest first");
const sentence = driftSentence(vendorOf(projection.url), drift);
// A REMOVED OPERATION LEADS: a verb that cannot work at all outranks a
// capability the hand merely has not caught up with.
assert.match(sentence, /^OpenAI no longer has createImageEdit/u);
assert.match(sentence, /OpenAI added model gpt-image-2\.5-sunburst, gpt-image-2\.5-flare, dall-e-2 — this hand still offers dall-e-3, gpt-image-1/u);
});
test("the moved signal is the vendor's own version, never the digest", () => {
const pinned = { sha256: "b".repeat(64), checked_at: "2026-09-01T00:00:00Z", version: "2.3.0" };
// Measured 2026-09-09: Gmail's Discovery bytes differ on every fetch while
// `revision` holds. A digest comparison would fire on every run.
assert.equal(driftOf({ kind: "openapi", url: projection.url, pinned }, projection, "").moved, null);
assert.deepEqual(driftOf({ kind: "openapi", url: projection.url, pinned: { ...pinned, version: "2.2.0" } }, projection, "").moved, { from: "2.2.0", to: "2.3.0" });
});
test("the vendor is named by who publishes the spec, not by where it is hosted", () => {
assert.equal(vendorOf(projection.url), "OpenAI");
assert.equal(vendorOf("https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/x.json"), "Slack");
assert.equal(vendorOf("https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"), "Google");
});
test("affinity ranks the next generation of a family the hand already has", () => {
assert.deepEqual(
byFamilyAffinity(["dall-e-2", "gpt-image-1.5", "gpt-image-2.5-flare"], ["gpt-image-1", "dall-e-3"]),
["gpt-image-2.5-flare", "gpt-image-1.5", "dall-e-2"],
);
});
import assert from "node:assert/strict";
import test from "node:test";
import { byFamilyAffinity, driftOf, driftSentence, namesValue, vendorOf } from "./spec-corpus.ts";
import type { SpecProjection } from "../snappy-settings/spec-read.ts";
const projection: SpecProjection = {
skill: "snappy-image",
kind: "openapi",
url: "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.json",
family: "openapi",
version: "2.3.0",
sha256: "a".repeat(64),
fetched_at: "2026-09-09T20:44:11Z",
operations: { generate: { id: "createImage", present: true }, retired: { id: "createImageEdit", present: false } },
operations_total: 289,
enums: { model: ["dall-e-2", "dall-e-3", "gpt-image-1", "gpt-image-2.5-flare", "gpt-image-2.5-sunburst"], quality: ["auto", "high", "max", "xhigh"] },
};
test("a distinctive value is named bare; a plain word must be a literal", () => {
assert.equal(namesValue("we use gpt-image-1 for text", "gpt-image-1"), true);
assert.equal(namesValue("--size 1024x1536 is the default", "1024x1536"), true);
// `gpt-image-1` must NOT match inside `gpt-image-1-mini`, or every hand that
// names one member would be credited with the whole family.
assert.equal(namesValue("we use gpt-image-1-mini", "gpt-image-1"), false);
// The false positive this split exists for: "full" is in almost every
// SKILL.md ever written.
assert.equal(namesValue("returns the full message body", "full"), false);
assert.equal(namesValue('format: "full"', "full"), true);
assert.equal(namesValue("pass `raw` to get the source", "raw"), true);
});
test("a hand that names none of an enum is not drifting against it", () => {
const drift = driftOf({ kind: "openapi", url: projection.url }, projection, "this hand mentions no model at all");
assert.equal(drift.enums.length, 0, "no position taken means the vendor default applies; that is not a stale choice");
assert.deepEqual(drift.gone, [{ verb: "retired", id: "createImageEdit" }]);
});
test("a hand that named some members is told exactly which it is missing", () => {
const drift = driftOf({ kind: "openapi", url: projection.url }, projection, "we call gpt-image-1 and dall-e-3");
const models = drift.enums.find((row) => row.param === "model");
assert.deepEqual(models?.offers, ["dall-e-3", "gpt-image-1"]);
assert.deepEqual(models?.missing, ["gpt-image-2.5-sunburst", "gpt-image-2.5-flare", "dall-e-2"], "the family the hand already uses leads, newest first");
const sentence = driftSentence(vendorOf(projection.url), drift);
// A REMOVED OPERATION LEADS: a verb that cannot work at all outranks a
// capability the hand merely has not caught up with.
assert.match(sentence, /^OpenAI no longer has createImageEdit/u);
assert.match(sentence, /OpenAI added model gpt-image-2\.5-sunburst, gpt-image-2\.5-flare, dall-e-2 — this hand still offers dall-e-3, gpt-image-1/u);
});
test("the moved signal is the vendor's own version, never the digest", () => {
const pinned = { sha256: "b".repeat(64), checked_at: "2026-09-01T00:00:00Z", version: "2.3.0" };
// Measured 2026-09-09: Gmail's Discovery bytes differ on every fetch while
// `revision` holds. A digest comparison would fire on every run.
assert.equal(driftOf({ kind: "openapi", url: projection.url, pinned }, projection, "").moved, null);
assert.deepEqual(driftOf({ kind: "openapi", url: projection.url, pinned: { ...pinned, version: "2.2.0" } }, projection, "").moved, { from: "2.2.0", to: "2.3.0" });
});
test("the vendor is named by who publishes the spec, not by where it is hosted", () => {
assert.equal(vendorOf(projection.url), "OpenAI");
assert.equal(vendorOf("https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/x.json"), "Slack");
assert.equal(vendorOf("https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"), "Google");
});
test("affinity ranks the next generation of a family the hand already has", () => {
assert.deepEqual(
byFamilyAffinity(["dall-e-2", "gpt-image-1.5", "gpt-image-2.5-flare"], ["gpt-image-1", "dall-e-3"]),
["gpt-image-2.5-flare", "gpt-image-1.5", "dall-e-2"],
);
});
/**
* THE VENDOR CORPUS — the third evidence file, beside the probe and the render.
*
* `corpus.ts` states the shape both of those follow and this one keeps it: ONE
* file for the whole collection, keyed by skill, evidence and never
* configuration. The difference is that this one is COMMITTED, and the reason
* is the reason it exists — a spec projection holds nothing of his (every byte
* came from a public vendor document), so it can be reviewed in a diff, it
* makes rule 61 deterministic on a fresh clone instead of DEFERring for
* everyone, and the diff between two kernel commits IS the per-skill vendor
* changelog. The probe and render corpora hold his mail and his calendar and
* are gitignored for exactly the opposite reason.
*
* Written by `snappy-specwatch check` (and by `snappy-tool-design specs`,
* which is the same call — one road). Read by rule 61.
*/
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { HandSpec, SpecProjection } from "../snappy-settings/spec-read.ts";
export const SPEC_FILE = "tool-design-specs.json";
export interface SpecCorpus {
/** When the whole file was last written, and by which verb. */
written_at: string;
skills: Record<string, SpecProjection>;
}
export function readSpecFile(dir: string): SpecCorpus | null {
const path = join(dir, SPEC_FILE);
if (!existsSync(path)) return null;
try {
const parsed = JSON.parse(readFileSync(path, "utf8")) as SpecCorpus;
return parsed && typeof parsed === "object" && parsed.skills ? parsed : null;
} catch {
// A half-written corpus is an ABSENT corpus, never a corpus of nothing:
// rule 61 DEFERs on absence and would have PASSed on `{}` ⟨CLAUDE.md §10⟩.
return null;
}
}
export function specFor(corpus: SpecCorpus | null, skill: string): SpecProjection | null {
return corpus?.skills?.[skill] ?? null;
}
/** MERGE, never replace. A one-hand run must not delete the other hands'
* evidence — that is how `specs snappy-image` would silently turn 97 PASSing
* rules into 97 DEFERs. */
export function writeSpecFile(dir: string, projections: SpecProjection[]): { path: string; skills: number } {
const path = join(dir, SPEC_FILE);
const held = readSpecFile(dir);
const skills = { ...(held?.skills ?? {}) };
for (const projection of projections) skills[projection.skill] = projection;
const ordered = Object.fromEntries(Object.keys(skills).sort().map((key) => [key, skills[key]]));
writeFileSync(path, `${JSON.stringify({ written_at: new Date().toISOString(), skills: ordered }, null, 2)}\n`);
return { path, skills: Object.keys(ordered).length };
}
export interface EnumDrift {
/** The argument name — `model`, `quality`, `size`. */
param: string;
/** The members this hand names in its own files. */
offers: string[];
/** The members the vendor has and this hand names nowhere. */
missing: string[];
}
export interface SpecDrift {
/** Verbs whose declared vendor operation is not in the document any more. */
gone: Array<{ verb: string; id: string }>;
enums: EnumDrift[];
/**
* THE VENDOR'S OWN VERSION MOVED since the hand pinned it. NEWS, never a
* defect on its own — a version can move with nothing this hand uses in it.
*
* IT IS NOT THE DIGEST, and that is measured rather than assumed. Three
* fetches of Google's Gmail Discovery document seconds apart on 2026-09-09
* returned THREE different sha256 digests while `revision` held at
* 20260907: the bytes are assembled per request. A digest comparison would
* therefore have printed "the document moved" on every single run, and a
* signal that fires every time is a signal nobody reads. The digest is kept
* in the pin as a tie-break for a vendor that publishes no version at all.
*/
moved: null | { from: string; to: string };
version: string;
checkedAt: string;
}
/**
* WHICH MISSING VALUE IS SAID FIRST.
*
* Sorted plainly, OpenAI's model drift opened with `dall-e-2` and truncated
* `gpt-image-2.5-sunburst` and `gpt-image-2.5-flare` into "and 4 more" — the
* exact two names the owner typed the word "image" for ⟨measured on the first
* red run of R61, 2026-09-09⟩. A sentence that hides its own headline is a
* sentence that will be ignored.
*
* So a missing value ranks by how much of its name it shares with something
* this hand ALREADY offers: `gpt-image-2.5-flare` shares `gpt-image-` with the
* hand's `gpt-image-1`, and `dall-e-2` shares only `dall-e-` with `dall-e-3`.
* The longer the shared prefix, the more certainly it is the NEXT GENERATION of
* a capability this hand is already in the business of — and within one family
* the later name sorts first, so 2.5 leads 1.5.
*/
export function byFamilyAffinity(missing: string[], offers: string[]): string[] {
// THE SHARED PREFIX IS CUT BACK TO A SEPARATOR, because a raw character
// count ranks a hand's OWN generation above the next one: `gpt-image-1.5`
// shares eleven characters with the offered `gpt-image-1` while
// `gpt-image-2.5-flare` shares ten, so 1.5 led and the headline was buried
// one layer deeper ⟨measured writing this test, 2026-09-09⟩. Cutting at the
// last `-`, `_` or `.` makes both "the gpt-image family", which is the fact
// that was meant.
const family = (value: string, offer: string) => {
let shared = 0;
while (shared < value.length && shared < offer.length && value[shared] === offer[shared]) shared += 1;
const cut = Math.max(value.lastIndexOf("-", shared - 1), value.lastIndexOf("_", shared - 1), value.lastIndexOf(".", shared - 1));
return cut >= 0 ? cut + 1 : shared;
};
const affinity = (value: string) => Math.max(0, ...offers.map((offer) => family(value, offer)));
return [...missing].sort((a, b) => {
const byFamily = affinity(b) - affinity(a);
if (byFamily !== 0) return byFamily;
// An ALIAS BEFORE ITS OWN DATED SNAPSHOT: `gpt-image-2.5-flare` is the name
// a person says and `gpt-image-2.5-flare-2026-09-08` is the pin of it.
if (b.startsWith(a)) return -1;
if (a.startsWith(b)) return 1;
return b.localeCompare(a, "en", { numeric: true });
});
}
/**
* DOES THIS HAND NAME THIS VALUE?
*
* The naive test — the value appears anywhere in the hand's files — turns
* every short English word in an enum into a false positive: Gmail's `format`
* enum holds `full`, and the word "full" is in almost every SKILL.md ever
* written. So a value counts as NAMED under one of two conditions, and the
* split is by the value's own shape:
*
* - it carries a digit, a hyphen or a dot (`gpt-image-2.5-flare`,
* `1024x1536`, `chat_postMessage`) — distinctive enough that a word-bounded
* match cannot be prose; or
* - it is a plain word (`full`, `raw`, `max`) — then it must appear as a
* LITERAL: inside quotes or backticks, which is how a value reaches code or
* a documented flag.
*/
export function namesValue(haystack: string, value: string): boolean {
const escaped = value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
if (/[\d.\-_]/u.test(value) && value.length >= 3) return new RegExp(`(^|[^\\w.-])${escaped}([^\\w.-]|$)`, "u").test(haystack);
return new RegExp(`(["'\`])${escaped}\\1`, "u").test(haystack);
}
/**
* THE DRIFT, computed from the hand's own declared surface and the vendor's
* projection. `surface` is every byte a person or an AI could learn this
* hand's choices from: its own api.ts and local modules, its SKILL.md, its
* AGENTS.md, and its contract serialised (which carries `inputSchema` enums).
*/
export function driftOf(spec: HandSpec, projection: SpecProjection, surface: string): SpecDrift {
const gone = Object.entries(projection.operations)
.filter(([, op]) => !op.present)
.map(([verb, op]) => ({ verb, id: op.id }));
const enums: EnumDrift[] = [];
for (const [param, values] of Object.entries(projection.enums)) {
const offers = values.filter((value) => namesValue(surface, value));
const missing = values.filter((value) => !namesValue(surface, value));
// A hand that names NONE of an enum's members is not drifting against that
// enum — it never took a position on it, and the vendor's default applies.
// A hand that names SOME has chosen, and the choice has gone stale.
if (offers.length && missing.length) enums.push({ param, offers, missing: byFamilyAffinity(missing, offers) });
}
enums.sort((a, b) => b.missing.length - a.missing.length || a.param.localeCompare(b.param));
return {
gone,
enums,
moved: movedFrom(spec, projection),
version: projection.version,
checkedAt: spec.pinned?.checked_at ?? projection.fetched_at,
};
}
function movedFrom(spec: HandSpec, projection: SpecProjection): null | { from: string; to: string } {
const pinned = spec.pinned;
if (!pinned) return null;
if (pinned.version && projection.version) return pinned.version === projection.version ? null : { from: pinned.version, to: projection.version };
return pinned.sha256 === projection.sha256 ? null : { from: pinned.sha256.slice(0, 12), to: projection.sha256.slice(0, 12) };
}
/**
* THE SENTENCE. It is written for the person reading a card in his menu bar,
* because that is where it ends up: the runner's `check-words.ts` turns a
* failing rule id into a phrase, and `fix-runs.ts` puts the SAME words into
* the prompt of the agent that fixes it. A rule whose only sentence was "R61
* FAIL: enum drift" would brief an agent on nothing.
*
* `vendor` is the host of the spec URL, so the sentence can open with a name a
* person recognises rather than with a rule id.
*/
export function driftSentence(vendor: string, drift: SpecDrift): string {
const said: string[] = [];
for (const { verb, id } of drift.gone) said.push(`${vendor} no longer has ${id}, which this hand's \`${verb}\` stands on`);
for (const { param, offers, missing } of drift.enums.slice(0, 3)) {
const shown = missing.slice(0, 4).join(", ");
const rest = missing.length > 4 ? ` and ${missing.length - 4} more` : "";
said.push(`${vendor} added ${param} ${shown}${rest} — this hand still offers ${offers.slice(0, 3).join(", ")}`);
}
if (drift.enums.length > 3) said.push(`${drift.enums.length - 3} more argument(s) have grown`);
return said.join("; ");
}
export function vendorOf(url: string): string {
try {
const host = new URL(url).hostname.replace(/^(raw\.githubusercontent|www|api)\./u, "");
// `openai/openai-openapi` on raw.githubusercontent is OpenAI's spec, and
// "githubusercontent.com" would name the wrong party on the card.
const owner = /^https:\/\/raw\.githubusercontent\.com\/([^/]+)\//u.exec(url)?.[1];
return owner ? owner.replace(/^slackapi$/u, "Slack").replace(/^openai$/u, "OpenAI") : host.split(".")[0].replace(/^googleapis$/u, "Google");
} catch {
return "the vendor";
}
}
/**
* THE VENDOR CORPUS — the third evidence file, beside the probe and the render.
*
* `corpus.ts` states the shape both of those follow and this one keeps it: ONE
* file for the whole collection, keyed by skill, evidence and never
* configuration. The difference is that this one is COMMITTED, and the reason
* is the reason it exists — a spec projection holds nothing of his (every byte
* came from a public vendor document), so it can be reviewed in a diff, it
* makes rule 61 deterministic on a fresh clone instead of DEFERring for
* everyone, and the diff between two kernel commits IS the per-skill vendor
* changelog. The probe and render corpora hold his mail and his calendar and
* are gitignored for exactly the opposite reason.
*
* Written by `snappy-specwatch check` (and by `snappy-tool-design specs`,
* which is the same call — one road). Read by rule 61.
*/
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { HandSpec, SpecProjection } from "../snappy-settings/spec-read.ts";
export const SPEC_FILE = "tool-design-specs.json";
export interface SpecCorpus {
/** When the whole file was last written, and by which verb. */
written_at: string;
skills: Record<string, SpecProjection>;
}
export function readSpecFile(dir: string): SpecCorpus | null {
const path = join(dir, SPEC_FILE);
if (!existsSync(path)) return null;
try {
const parsed = JSON.parse(readFileSync(path, "utf8")) as SpecCorpus;
return parsed && typeof parsed === "object" && parsed.skills ? parsed : null;
} catch {
// A half-written corpus is an ABSENT corpus, never a corpus of nothing:
// rule 61 DEFERs on absence and would have PASSed on `{}` ⟨CLAUDE.md §10⟩.
return null;
}
}
export function specFor(corpus: SpecCorpus | null, skill: string): SpecProjection | null {
return corpus?.skills?.[skill] ?? null;
}
/** MERGE, never replace. A one-hand run must not delete the other hands'
* evidence — that is how `specs snappy-image` would silently turn 97 PASSing
* rules into 97 DEFERs. */
export function writeSpecFile(dir: string, projections: SpecProjection[]): { path: string; skills: number } {
const path = join(dir, SPEC_FILE);
const held = readSpecFile(dir);
const skills = { ...(held?.skills ?? {}) };
for (const projection of projections) skills[projection.skill] = projection;
const ordered = Object.fromEntries(Object.keys(skills).sort().map((key) => [key, skills[key]]));
writeFileSync(path, `${JSON.stringify({ written_at: new Date().toISOString(), skills: ordered }, null, 2)}\n`);
return { path, skills: Object.keys(ordered).length };
}
export interface EnumDrift {
/** The argument name — `model`, `quality`, `size`. */
param: string;
/** The members this hand names in its own files. */
offers: string[];
/** The members the vendor has and this hand names nowhere. */
missing: string[];
}
export interface SpecDrift {
/** Verbs whose declared vendor operation is not in the document any more. */
gone: Array<{ verb: string; id: string }>;
enums: EnumDrift[];
/**
* THE VENDOR'S OWN VERSION MOVED since the hand pinned it. NEWS, never a
* defect on its own — a version can move with nothing this hand uses in it.
*
* IT IS NOT THE DIGEST, and that is measured rather than assumed. Three
* fetches of Google's Gmail Discovery document seconds apart on 2026-09-09
* returned THREE different sha256 digests while `revision` held at
* 20260907: the bytes are assembled per request. A digest comparison would
* therefore have printed "the document moved" on every single run, and a
* signal that fires every time is a signal nobody reads. The digest is kept
* in the pin as a tie-break for a vendor that publishes no version at all.
*/
moved: null | { from: string; to: string };
version: string;
checkedAt: string;
}
/**
* WHICH MISSING VALUE IS SAID FIRST.
*
* Sorted plainly, OpenAI's model drift opened with `dall-e-2` and truncated
* `gpt-image-2.5-sunburst` and `gpt-image-2.5-flare` into "and 4 more" — the
* exact two names the owner typed the word "image" for ⟨measured on the first
* red run of R61, 2026-09-09⟩. A sentence that hides its own headline is a
* sentence that will be ignored.
*
* So a missing value ranks by how much of its name it shares with something
* this hand ALREADY offers: `gpt-image-2.5-flare` shares `gpt-image-` with the
* hand's `gpt-image-1`, and `dall-e-2` shares only `dall-e-` with `dall-e-3`.
* The longer the shared prefix, the more certainly it is the NEXT GENERATION of
* a capability this hand is already in the business of — and within one family
* the later name sorts first, so 2.5 leads 1.5.
*/
export function byFamilyAffinity(missing: string[], offers: string[]): string[] {
// THE SHARED PREFIX IS CUT BACK TO A SEPARATOR, because a raw character
// count ranks a hand's OWN generation above the next one: `gpt-image-1.5`
// shares eleven characters with the offered `gpt-image-1` while
// `gpt-image-2.5-flare` shares ten, so 1.5 led and the headline was buried
// one layer deeper ⟨measured writing this test, 2026-09-09⟩. Cutting at the
// last `-`, `_` or `.` makes both "the gpt-image family", which is the fact
// that was meant.
const family = (value: string, offer: string) => {
let shared = 0;
while (shared < value.length && shared < offer.length && value[shared] === offer[shared]) shared += 1;
const cut = Math.max(value.lastIndexOf("-", shared - 1), value.lastIndexOf("_", shared - 1), value.lastIndexOf(".", shared - 1));
return cut >= 0 ? cut + 1 : shared;
};
const affinity = (value: string) => Math.max(0, ...offers.map((offer) => family(value, offer)));
return [...missing].sort((a, b) => {
const byFamily = affinity(b) - affinity(a);
if (byFamily !== 0) return byFamily;
// An ALIAS BEFORE ITS OWN DATED SNAPSHOT: `gpt-image-2.5-flare` is the name
// a person says and `gpt-image-2.5-flare-2026-09-08` is the pin of it.
if (b.startsWith(a)) return -1;
if (a.startsWith(b)) return 1;
return b.localeCompare(a, "en", { numeric: true });
});
}
/**
* DOES THIS HAND NAME THIS VALUE?
*
* The naive test — the value appears anywhere in the hand's files — turns
* every short English word in an enum into a false positive: Gmail's `format`
* enum holds `full`, and the word "full" is in almost every SKILL.md ever
* written. So a value counts as NAMED under one of two conditions, and the
* split is by the value's own shape:
*
* - it carries a digit, a hyphen or a dot (`gpt-image-2.5-flare`,
* `1024x1536`, `chat_postMessage`) — distinctive enough that a word-bounded
* match cannot be prose; or
* - it is a plain word (`full`, `raw`, `max`) — then it must appear as a
* LITERAL: inside quotes or backticks, which is how a value reaches code or
* a documented flag.
*/
export function namesValue(haystack: string, value: string): boolean {
const escaped = value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
if (/[\d.\-_]/u.test(value) && value.length >= 3) return new RegExp(`(^|[^\\w.-])${escaped}([^\\w.-]|$)`, "u").test(haystack);
return new RegExp(`(["'\`])${escaped}\\1`, "u").test(haystack);
}
/**
* THE DRIFT, computed from the hand's own declared surface and the vendor's
* projection. `surface` is every byte a person or an AI could learn this
* hand's choices from: its own api.ts and local modules, its SKILL.md, its
* AGENTS.md, and its contract serialised (which carries `inputSchema` enums).
*/
export function driftOf(spec: HandSpec, projection: SpecProjection, surface: string): SpecDrift {
const gone = Object.entries(projection.operations)
.filter(([, op]) => !op.present)
.map(([verb, op]) => ({ verb, id: op.id }));
const enums: EnumDrift[] = [];
for (const [param, values] of Object.entries(projection.enums)) {
const offers = values.filter((value) => namesValue(surface, value));
const missing = values.filter((value) => !namesValue(surface, value));
// A hand that names NONE of an enum's members is not drifting against that
// enum — it never took a position on it, and the vendor's default applies.
// A hand that names SOME has chosen, and the choice has gone stale.
if (offers.length && missing.length) enums.push({ param, offers, missing: byFamilyAffinity(missing, offers) });
}
enums.sort((a, b) => b.missing.length - a.missing.length || a.param.localeCompare(b.param));
return {
gone,
enums,
moved: movedFrom(spec, projection),
version: projection.version,
checkedAt: spec.pinned?.checked_at ?? projection.fetched_at,
};
}
function movedFrom(spec: HandSpec, projection: SpecProjection): null | { from: string; to: string } {
const pinned = spec.pinned;
if (!pinned) return null;
if (pinned.version && projection.version) return pinned.version === projection.version ? null : { from: pinned.version, to: projection.version };
return pinned.sha256 === projection.sha256 ? null : { from: pinned.sha256.slice(0, 12), to: projection.sha256.slice(0, 12) };
}
/**
* THE SENTENCE. It is written for the person reading a card in his menu bar,
* because that is where it ends up: the runner's `check-words.ts` turns a
* failing rule id into a phrase, and `fix-runs.ts` puts the SAME words into
* the prompt of the agent that fixes it. A rule whose only sentence was "R61
* FAIL: enum drift" would brief an agent on nothing.
*
* `vendor` is the host of the spec URL, so the sentence can open with a name a
* person recognises rather than with a rule id.
*/
export function driftSentence(vendor: string, drift: SpecDrift): string {
const said: string[] = [];
for (const { verb, id } of drift.gone) said.push(`${vendor} no longer has ${id}, which this hand's \`${verb}\` stands on`);
for (const { param, offers, missing } of drift.enums.slice(0, 3)) {
const shown = missing.slice(0, 4).join(", ");
const rest = missing.length > 4 ? ` and ${missing.length - 4} more` : "";
said.push(`${vendor} added ${param} ${shown}${rest} — this hand still offers ${offers.slice(0, 3).join(", ")}`);
}
if (drift.enums.length > 3) said.push(`${drift.enums.length - 3} more argument(s) have grown`);
return said.join("; ");
}
export function vendorOf(url: string): string {
try {
const host = new URL(url).hostname.replace(/^(raw\.githubusercontent|www|api)\./u, "");
// `openai/openai-openapi` on raw.githubusercontent is OpenAI's spec, and
// "githubusercontent.com" would name the wrong party on the card.
const owner = /^https:\/\/raw\.githubusercontent\.com\/([^/]+)\//u.exec(url)?.[1];
return owner ? owner.replace(/^slackapi$/u, "Slack").replace(/^openai$/u, "OpenAI") : host.split(".")[0].replace(/^googleapis$/u, "Google");
} catch {
return "the vendor";
}
}
{
"schema": "snappy-tool-design/hosts@1",
"generated_at": "2026-09-09T11:04:42.350Z",
"runner": {
"mcp_door": "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
"health_door": "https://roberts-mac-mini.tailca7f42.ts.net/runner/health",
"revision": "1da5a73711-41156a7220a9-1879d420a2",
"hands": 98,
"kernel_head": "1da5a73711be",
"attributed": true,
"proved_at": "2026-09-09T11:03:41.310Z"
},
"prompts": [
{
"id": "gmail-inbox",
"text": "Using Snappy Skills, show me my Gmail inbox, three messages",
"hand": "snappy-gmail",
"family": "gmail",
"press": null
},
{
"id": "skool-feed",
"text": "Using Snappy Skills, show me my Skool feed",
"hand": "snappy-skool",
"family": "skool",
"press": null
},
{
"id": "linkedin-profile",
"text": "Using Snappy Skills, show me my LinkedIn profile",
"hand": "snappy-linkedin",
"family": "linkedin",
"press": null
},
{
"id": "linkedin-decide",
"text": "Using Snappy Skills, draft a LinkedIn post about launch checklists and let me decide",
"hand": "snappy-linkedin",
"family": "linkedin",
"press": "Later"
}
],
"drivers": [
{
"host": "chatgpt",
"label": "ChatGPT web app",
"road": "agent-browser --state chatgpt → https://chatgpt.com",
"surface": "widget",
"driven": false,
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"ownerCommand": "agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt"
},
{
"host": "claude",
"label": "Claude.ai web app",
"road": "agent-browser --state claude → https://claude.ai",
"surface": "widget",
"driven": false,
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"ownerCommand": "agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude"
},
{
"host": "codex",
"label": "Codex CLI",
"road": "codex exec --json (mcp_servers.snappy-skills)",
"surface": "none",
"driven": false,
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)",
"ownerCommand": "open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>"
},
{
"host": "menubar",
"label": "Snappy menu bar",
"road": "SnappyBar popover WKWebView over its local runner",
"surface": "webview",
"driven": true,
"reason": "the bar's own page mounted a face in its stage: 120 element(s) under #snappy-faces-root inside the stage <iframe>, 2393 character(s) of drawn text, history row \"Gmail · list\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves.",
"ownerCommand": "cd ~/Projects/snappy-runner/apps/snappy-bar && ./scripts/make-app.sh && open dist/Snappy.app # then: npx tsx api.ts hosts --only menubar --token-file <file>"
},
{
"host": "telegram",
"label": "Telegram on the phone",
"road": "snappy-telegram show (PNG) + decisions",
"surface": "image",
"driven": true,
"reason": "the card is already on the owner's own chat as photo message_id 8387, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself",
"ownerCommand": null
}
],
"cells": [
{
"host": "chatgpt",
"prompt": "gmail-inbox",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "chatgpt",
"prompt": "skool-feed",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "chatgpt",
"prompt": "linkedin-profile",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "chatgpt",
"prompt": "linkedin-decide",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "gmail-inbox",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "skool-feed",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "linkedin-profile",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "linkedin-decide",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "codex",
"prompt": "gmail-inbox",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)"
},
{
"host": "codex",
"prompt": "skool-feed",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
},
{
"host": "codex",
"prompt": "linkedin-profile",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
},
{
"host": "codex",
"prompt": "linkedin-decide",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
},
{
"host": "menubar",
"prompt": "gmail-inbox",
"ms": 6953,
"kind": "gmail-list",
"shot": "/tmp/hosts-battery/menubar-gmail-inbox.png",
"press": null,
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 120 element(s) under #snappy-faces-root inside the stage <iframe>, 2393 character(s) of drawn text, history row \"Gmail · list\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "menubar",
"prompt": "skool-feed",
"ms": 4944,
"kind": "skool-feed",
"shot": "/tmp/hosts-battery/menubar-skool-feed.png",
"press": null,
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 418 element(s) under #snappy-faces-root inside the stage <iframe>, 10117 character(s) of drawn text, history row \"Skool · list\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "menubar",
"prompt": "linkedin-profile",
"ms": 4930,
"kind": "linkedin-profile",
"shot": "/tmp/hosts-battery/menubar-linkedin-profile.png",
"press": null,
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 17 element(s) under #snappy-faces-root inside the stage <iframe>, 29 character(s) of drawn text, history row \"Linkedin · profile\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "menubar",
"prompt": "linkedin-decide",
"ms": 4569,
"kind": "linkedin-decision",
"shot": "/tmp/hosts-battery/menubar-linkedin-decide.png",
"press": {
"word": "Later",
"reached_runner": false,
"within_ms": null,
"how": "the doors are drawn and pressable in the face's own frame; this rig presses none — a press is the owner's, and pressing Later here would be answering for him."
},
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 71 element(s) under #snappy-faces-root inside the stage <iframe>, 1139 character(s) of drawn text, history row \"Linkedin · decision\", drawn as the faces skill's shipped example rather than a staged draft — the bar's `show <kind>` road, so nothing was staged to reach it. Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "telegram",
"prompt": "gmail-inbox",
"ms": null,
"kind": "gmail-list",
"shot": null,
"press": null,
"standing": 8387,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8387, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself"
},
{
"host": "telegram",
"prompt": "skool-feed",
"ms": null,
"kind": "skool-feed",
"shot": null,
"press": null,
"standing": 8388,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8388, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself"
},
{
"host": "telegram",
"prompt": "linkedin-profile",
"ms": null,
"kind": "linkedin-profile",
"shot": null,
"press": null,
"standing": 8389,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8389, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself"
},
{
"host": "telegram",
"prompt": "linkedin-decide",
"ms": null,
"kind": "linkedin-decision",
"shot": null,
"press": {
"word": "Later",
"reached_runner": false,
"within_ms": null,
"how": "snappy-telegram decisions, matched on the ref the send packed (no ref to match — a press could only be read if the send had named one). Nothing here presses: the doors are on the owner's own phone and pressing one would be answering for him."
},
"standing": 8386,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8386, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself — its doors are still unpressed; shown, press pending"
}
],
"road": [
{
"prompt": "gmail-inbox",
"hand": "snappy-gmail",
"verb": "list",
"outcome": "answered",
"kind": "gmail-list",
"family": "gmail",
"resource_uri": "ui://snappy/faces",
"ms": 1745,
"hops": 1,
"note": "the runner's own search ranked snappy-gmail list first for the person's sentence; one call"
},
{
"prompt": "skool-feed",
"hand": "snappy-skool",
"verb": "posts",
"outcome": "answered",
"kind": "skool-feed",
"family": "skool",
"resource_uri": "ui://snappy/faces",
"ms": 1053,
"hops": 1,
"note": "the runner's own search ranked snappy-skool posts first for the person's sentence; one call"
},
{
"prompt": "linkedin-profile",
"hand": "snappy-linkedin",
"verb": "profile",
"outcome": "answered",
"kind": "linkedin-profile",
"family": "linkedin",
"resource_uri": "ui://snappy/faces",
"ms": 902,
"hops": 1,
"note": "the runner's own search ranked snappy-linkedin profile first for the person's sentence; one call"
},
{
"prompt": "linkedin-decide",
"hand": "snappy-linkedin",
"verb": "post",
"outcome": "not driven",
"kind": null,
"family": null,
"resource_uri": null,
"ms": null,
"hops": 0,
"note": "`post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it."
}
],
"face_families": {
"published": [
"gmail",
"linkedin",
"slack",
"telegram",
"whatsapp",
"imessage",
"x",
"instagram",
"youtube",
"skool",
"notion",
"github",
"freshbooks",
"calendar",
"sheets",
"stripe",
"media",
"blocks",
"statechange",
"data",
"code",
"agent",
"thumbnails",
"deploy",
"schedule",
"krisp",
"knowledge",
"transcripts",
"imageset",
"walkthrough",
"doc"
],
"missing": [
{
"skill": "snappy-api-sniffer",
"family": "api-sniffer",
"has_family": false,
"reason": "snappy-faces declares no \"api-sniffer\" family, so every host draws snappy-api-sniffer's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-blog",
"family": "blog",
"has_family": false,
"reason": "snappy-faces declares no \"blog\" family, so every host draws snappy-blog's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-calendar",
"family": "calendar",
"has_family": true,
"reason": "snappy-faces declares the \"calendar\" family"
},
{
"skill": "snappy-channel-contract",
"family": "channel-contract",
"has_family": false,
"reason": "snappy-faces declares no \"channel-contract\" family, so every host draws snappy-channel-contract's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-orbiter",
"family": "client-orbiter",
"has_family": false,
"reason": "snappy-faces declares no \"client-orbiter\" family, so every host draws snappy-client-orbiter's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-ray",
"family": "client-ray",
"has_family": false,
"reason": "snappy-faces declares no \"client-ray\" family, so every host draws snappy-client-ray's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-scott",
"family": "client-scott",
"has_family": false,
"reason": "snappy-faces declares no \"client-scott\" family, so every host draws snappy-client-scott's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-total",
"family": "client-total",
"has_family": false,
"reason": "snappy-faces declares no \"client-total\" family, so every host draws snappy-client-total's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-clients",
"family": "clients",
"has_family": false,
"reason": "snappy-faces declares no \"clients\" family, so every host draws snappy-clients's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-corpus",
"family": "corpus",
"has_family": false,
"reason": "snappy-faces declares no \"corpus\" family, so every host draws snappy-corpus's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-docs",
"family": "docs",
"has_family": false,
"reason": "snappy-faces declares no \"docs\" family, so every host draws snappy-docs's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-email",
"family": "email",
"has_family": false,
"reason": "snappy-faces declares no \"email\" family, so every host draws snappy-email's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-freshbooks",
"family": "freshbooks",
"has_family": true,
"reason": "snappy-faces declares the \"freshbooks\" family"
},
{
"skill": "snappy-gateway",
"family": "gateway",
"has_family": false,
"reason": "snappy-faces declares no \"gateway\" family, so every host draws snappy-gateway's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-github",
"family": "github",
"has_family": true,
"reason": "snappy-faces declares the \"github\" family"
},
{
"skill": "snappy-gmail",
"family": "gmail",
"has_family": true,
"reason": "snappy-faces declares the \"gmail\" family"
},
{
"skill": "snappy-hooks",
"family": "hooks",
"has_family": false,
"reason": "snappy-faces declares no \"hooks\" family, so every host draws snappy-hooks's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-imessage",
"family": "imessage",
"has_family": true,
"reason": "snappy-faces declares the \"imessage\" family"
},
{
"skill": "snappy-knowledge",
"family": "knowledge",
"has_family": true,
"reason": "snappy-faces declares the \"knowledge\" family"
},
{
"skill": "snappy-linkedin",
"family": "linkedin",
"has_family": true,
"reason": "snappy-faces declares the \"linkedin\" family"
},
{
"skill": "snappy-mastermind-model",
"family": "mastermind-model",
"has_family": false,
"reason": "snappy-faces declares no \"mastermind-model\" family, so every host draws snappy-mastermind-model's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-notion",
"family": "notion",
"has_family": true,
"reason": "snappy-faces declares the \"notion\" family"
},
{
"skill": "snappy-outbound",
"family": "outbound",
"has_family": false,
"reason": "snappy-faces declares no \"outbound\" family, so every host draws snappy-outbound's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-remotion",
"family": "remotion",
"has_family": false,
"reason": "snappy-faces declares no \"remotion\" family, so every host draws snappy-remotion's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-settings",
"family": "settings",
"has_family": false,
"reason": "snappy-faces declares no \"settings\" family, so every host draws snappy-settings's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-skool",
"family": "skool",
"has_family": true,
"reason": "snappy-faces declares the \"skool\" family"
},
{
"skill": "snappy-slack",
"family": "slack",
"has_family": true,
"reason": "snappy-faces declares the \"slack\" family"
},
{
"skill": "snappy-statechange",
"family": "statechange",
"has_family": true,
"reason": "snappy-faces declares the \"statechange\" family"
},
{
"skill": "snappy-telegram",
"family": "telegram",
"has_family": true,
"reason": "snappy-faces declares the \"telegram\" family"
},
{
"skill": "snappy-testimonials",
"family": "testimonials",
"has_family": false,
"reason": "snappy-faces declares no \"testimonials\" family, so every host draws snappy-testimonials's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-transcripts",
"family": "transcripts",
"has_family": true,
"reason": "snappy-faces declares the \"transcripts\" family"
},
{
"skill": "snappy-whatsapp",
"family": "whatsapp",
"has_family": true,
"reason": "snappy-faces declares the \"whatsapp\" family"
},
{
"skill": "snappy-youtube",
"family": "youtube",
"has_family": true,
"reason": "snappy-faces declares the \"youtube\" family"
}
],
"unreachable": [
{
"family": "agent",
"read": "snappy-agent-host status → agent-run",
"reachable": true,
"reason": "snappy-agent-host's contract declares \"status\" draws agent-run, which the build report files under \"agent\" — the hand named the face and nothing was derived over it"
},
{
"family": "blocks",
"read": null,
"reachable": false,
"reason": "no hand named snappy-blocks exists and no hand's contract declares a \"blocks\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"blocks\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "calendar",
"read": "snappy-calendar list → list",
"reachable": true,
"reason": "snappy-calendar list folds onto list, so a person's read reaches the \"calendar\" family"
},
{
"family": "code",
"read": null,
"reachable": false,
"reason": "no hand named snappy-code exists and no hand's contract declares a \"code\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"code\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "data",
"read": "snappy-database query → data-table",
"reachable": true,
"reason": "snappy-database's contract declares \"query\" draws data-table, which the build report files under \"data\" — the hand named the face and nothing was derived over it"
},
{
"family": "deploy",
"read": "snappy-deploy status → deploy-receipt",
"reachable": true,
"reason": "snappy-deploy's contract declares \"status\" draws deploy-receipt, which the build report files under \"deploy\" — the hand named the face and nothing was derived over it"
},
{
"family": "doc",
"read": "snappy-docs page → doc-card",
"reachable": true,
"reason": "snappy-docs's contract declares \"page\" draws doc-card, which the build report files under \"doc\" — the hand named the face and nothing was derived over it"
},
{
"family": "freshbooks",
"read": "snappy-freshbooks list → list",
"reachable": true,
"reason": "snappy-freshbooks list folds onto list, so a person's read reaches the \"freshbooks\" family"
},
{
"family": "github",
"read": "snappy-github comments → list",
"reachable": true,
"reason": "snappy-github comments folds onto list, so a person's read reaches the \"github\" family"
},
{
"family": "gmail",
"read": "snappy-gmail list → list",
"reachable": true,
"reason": "snappy-gmail list folds onto list, so a person's read reaches the \"gmail\" family"
},
{
"family": "imageset",
"read": "snappy-image logos → imageset-grid",
"reachable": true,
"reason": "snappy-image's contract declares \"logos\" draws imageset-grid, which the build report files under \"imageset\" — the hand named the face and nothing was derived over it"
},
{
"family": "imessage",
"read": "snappy-imessage recent → list",
"reachable": true,
"reason": "snappy-imessage recent folds onto list, so a person's read reaches the \"imessage\" family"
},
{
"family": "instagram",
"read": null,
"reachable": false,
"reason": "no hand named snappy-instagram exists and no hand's contract declares a \"instagram\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"instagram\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "knowledge",
"read": "snappy-knowledge get → one",
"reachable": true,
"reason": "snappy-knowledge get folds onto one, so a person's read reaches the \"knowledge\" family"
},
{
"family": "krisp",
"read": "snappy-krisp fetch-action-items → krisp-actions",
"reachable": true,
"reason": "snappy-krisp's contract declares \"fetch-action-items\" draws krisp-actions, which the build report files under \"krisp\" — the hand named the face and nothing was derived over it"
},
{
"family": "linkedin",
"read": "snappy-linkedin profile → profile",
"reachable": true,
"reason": "snappy-linkedin profile folds onto profile, so a person's read reaches the \"linkedin\" family"
},
{
"family": "media",
"read": "snappy-ffmpeg probe → media-video",
"reachable": true,
"reason": "snappy-ffmpeg's contract declares \"probe\" draws media-video, which the build report files under \"media\" — the hand named the face and nothing was derived over it"
},
{
"family": "notion",
"read": "snappy-notion me → profile",
"reachable": true,
"reason": "snappy-notion me folds onto profile, so a person's read reaches the \"notion\" family"
},
{
"family": "schedule",
"read": "snappy-scheduling propose → schedule-pick",
"reachable": true,
"reason": "snappy-scheduling's contract declares \"propose\" draws schedule-pick, which the build report files under \"schedule\" — the hand named the face and nothing was derived over it"
},
{
"family": "sheets",
"read": null,
"reachable": false,
"reason": "no hand named snappy-sheets exists and no hand's contract declares a \"sheets\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"sheets\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "skool",
"read": "snappy-skool me → profile",
"reachable": true,
"reason": "snappy-skool me folds onto profile, so a person's read reaches the \"skool\" family"
},
{
"family": "slack",
"read": "snappy-slack messages → list",
"reachable": true,
"reason": "snappy-slack messages folds onto list, so a person's read reaches the \"slack\" family"
},
{
"family": "statechange",
"read": "snappy-statechange posts → list",
"reachable": true,
"reason": "snappy-statechange posts folds onto list, so a person's read reaches the \"statechange\" family"
},
{
"family": "stripe",
"read": null,
"reachable": false,
"reason": "no hand named snappy-stripe exists and no hand's contract declares a \"stripe\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"stripe\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "telegram",
"read": "snappy-telegram read → one",
"reachable": true,
"reason": "snappy-telegram read folds onto one, so a person's read reaches the \"telegram\" family"
},
{
"family": "thumbnails",
"read": "snappy-thumbnails variants → thumbnails-set",
"reachable": true,
"reason": "snappy-thumbnails's contract declares \"variants\" draws thumbnails-set, which the build report files under \"thumbnails\" — the hand named the face and nothing was derived over it"
},
{
"family": "transcripts",
"read": "snappy-transcripts list → list",
"reachable": true,
"reason": "snappy-transcripts list folds onto list, so a person's read reaches the \"transcripts\" family"
},
{
"family": "walkthrough",
"read": "snappy-walkthrough steps → walkthrough-steps",
"reachable": true,
"reason": "snappy-walkthrough's contract declares \"steps\" draws walkthrough-steps, which the build report files under \"walkthrough\" — the hand named the face and nothing was derived over it"
},
{
"family": "whatsapp",
"read": "snappy-whatsapp read → one",
"reachable": true,
"reason": "snappy-whatsapp read folds onto one, so a person's read reaches the \"whatsapp\" family"
},
{
"family": "x",
"read": null,
"reachable": false,
"reason": "no hand named snappy-x exists and no hand's contract declares a \"x\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"x\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "youtube",
"read": "snappy-youtube comments → list",
"reachable": true,
"reason": "snappy-youtube comments folds onto list, so a person's read reaches the \"youtube\" family"
}
]
},
"no_shape": [
{
"skill": "snappy-ads",
"verbs": [
"metrics",
"targets"
]
},
{
"skill": "snappy-agent-host",
"verbs": [
"start",
"prompt",
"status",
"cancel",
"sessions",
"models"
]
},
{
"skill": "snappy-ai-models",
"verbs": [
"chat",
"embed",
"image"
]
},
{
"skill": "snappy-analytics",
"verbs": [
"scorecard"
]
},
{
"skill": "snappy-artifact-loop",
"verbs": [
"channels",
"choose",
"envelope",
"kit",
"parse"
]
},
{
"skill": "snappy-ax",
"verbs": [
"at-point",
"find",
"focused",
"macos-use",
"permissions",
"press",
"raw",
"set-value",
"text",
"tree"
]
},
{
"skill": "snappy-box",
"verbs": [
"call",
"routes",
"sql"
]
},
{
"skill": "snappy-browse",
"verbs": [
"clear-requests",
"click",
"close",
"extract",
"metrics",
"navigate",
"requests",
"snapshot"
]
},
{
"skill": "snappy-cleanshot",
"verbs": [
"area",
"ax",
"crop",
"fullscreen",
"grab",
"grab-app",
"history",
"icons",
"last",
"ocr",
"ocr-region",
"previous",
"probe",
"record",
"screens",
"scroll",
"space",
"spaces",
"sweep",
"tab",
"tabs",
"vlm",
"win",
"window",
"windows"
]
},
{
"skill": "snappy-content",
"verbs": [
"atoms",
"draft-new",
"metrics",
"status",
"tick",
"tick-all"
]
},
{
"skill": "snappy-course",
"verbs": [
"module",
"modules"
]
},
{
"skill": "snappy-data-hygiene",
"verbs": [
"classify",
"prompt",
"review"
]
},
{
"skill": "snappy-database",
"verbs": [
"describe",
"query",
"tables"
]
},
{
"skill": "snappy-deploy",
"verbs": [
"status",
"vercel"
]
},
{
"skill": "snappy-desktop",
"verbs": [
"run",
"screenshot"
]
},
{
"skill": "snappy-dom-cartographer",
"verbs": [
"eval",
"log",
"patch",
"snap",
"start",
"verify"
]
},
{
"skill": "snappy-faces",
"verbs": [
"draw",
"lang",
"faces",
"grammar"
]
},
{
"skill": "snappy-ffmpeg",
"verbs": [
"burn-subs",
"compress",
"concat",
"extract-audio",
"loudnorm",
"overlay",
"probe",
"resize",
"thumbnail",
"trim"
]
},
{
"skill": "snappy-gemini",
"verbs": [
"describe",
"generate",
"image"
]
},
{
"skill": "snappy-hands",
"verbs": [
"census"
]
},
{
"skill": "snappy-image",
"verbs": [
"canva",
"course-folder",
"generate",
"logo",
"logos",
"metrics",
"reframe",
"upload",
"url"
]
},
{
"skill": "snappy-inbound",
"verbs": [
"classify",
"slack-joins"
]
},
{
"skill": "snappy-inbox-sweep",
"verbs": [
"gmail-personal",
"gmail-work",
"inbox-zero",
"send-skool",
"send-slack",
"slack",
"sweep"
]
},
{
"skill": "snappy-infra",
"verbs": [
"ssh",
"vercel",
"xano"
]
},
{
"skill": "snappy-krisp",
"verbs": [
"fetch-meetings",
"fetch-document",
"fetch-action-items",
"metrics"
]
},
{
"skill": "snappy-libretto",
"verbs": [
"record",
"lessons",
"lesson",
"replay",
"promote"
]
},
{
"skill": "snappy-maintenance",
"verbs": [
"all",
"check",
"ssl"
]
},
{
"skill": "snappy-mine",
"verbs": [
"files",
"manifest",
"meetings-by",
"metrics",
"pending",
"persist",
"sources"
]
},
{
"skill": "snappy-nightshift",
"verbs": [
"bar",
"blocked",
"board",
"log",
"preflight",
"reap",
"report",
"status"
]
},
{
"skill": "snappy-offer",
"verbs": [
"icp",
"offer",
"pricing"
]
},
{
"skill": "snappy-openrouter",
"verbs": [
"chat",
"fallback"
]
},
{
"skill": "snappy-ops",
"verbs": [
"ab",
"actions",
"catchup",
"dashboard",
"diff-machine",
"dispatch",
"dry",
"fingerprint",
"ladder",
"loops",
"menu",
"recipes",
"remind",
"replay",
"run",
"settle",
"share",
"snapshot",
"status",
"story",
"sweep",
"trace",
"trend"
]
},
{
"skill": "snappy-os-operator",
"verbs": [
"approvals",
"approve",
"deny",
"deploy-truth",
"grant-create",
"grant-update",
"health",
"run",
"runs",
"script-create",
"script-run",
"script-version",
"session",
"skill-create",
"stage"
]
},
{
"skill": "snappy-pipeline",
"verbs": [
"avatars",
"batch-overview",
"completeness",
"crash-patterns",
"data-landing",
"duplicates",
"edge-gaps",
"history",
"queue-health",
"stuck-analysis"
]
},
{
"skill": "snappy-playbook",
"verbs": [
"week",
"weeks"
]
},
{
"skill": "snappy-positioning",
"verbs": [
"banned",
"check",
"cite",
"flow",
"tone"
]
},
{
"skill": "snappy-post",
"verbs": [
"linkedin",
"slack",
"telegram"
]
},
{
"skill": "snappy-publish",
"verbs": [
"deploys",
"status"
]
},
{
"skill": "snappy-report-publish",
"verbs": [
"gen-batch",
"publish",
"style",
"update-index",
"verify"
]
},
{
"skill": "snappy-review-pages",
"verbs": [
"kit",
"provenance"
]
},
{
"skill": "snappy-sales",
"verbs": [
"clients",
"leads",
"log"
]
},
{
"skill": "snappy-scheduling",
"verbs": [
"available",
"propose"
]
},
{
"skill": "snappy-sensors",
"verbs": [
"dump",
"section",
"stale"
]
},
{
"skill": "snappy-session-close",
"verbs": [
"handoff",
"reconcile"
]
},
{
"skill": "snappy-skill",
"verbs": [
"scaffold"
]
},
{
"skill": "snappy-sync",
"verbs": [
"share"
]
},
{
"skill": "snappy-thumbnails",
"verbs": [
"audit",
"brief",
"generate",
"set",
"variants"
]
},
{
"skill": "snappy-tool-design",
"verbs": [
"lint",
"example",
"probe",
"render",
"loop",
"hosts",
"fix-loader"
]
},
{
"skill": "snappy-update",
"verbs": [
"commits",
"notify"
]
},
{
"skill": "snappy-video",
"verbs": [
"caption",
"clip",
"transcribe"
]
},
{
"skill": "snappy-voice-control",
"verbs": [
"devices",
"permissions",
"pick",
"record",
"stream-url",
"transcribe"
]
},
{
"skill": "snappy-walkthrough",
"verbs": [
"annotate",
"capture",
"gates",
"lesson",
"run",
"status",
"steps"
]
},
{
"skill": "snappy-website",
"verbs": [
"deploys",
"health"
]
},
{
"skill": "snappy-xano-dashboard",
"verbs": [
"logs",
"open",
"test"
]
},
{
"skill": "snappy-xano-mcp",
"verbs": [
"health",
"test"
]
}
],
"clusters": [
{
"key": "hosts-road-names-no-face",
"headline": "1 of the four asks never reach a named face on the runner itself",
"mechanism": "Before any host is blamed, the same ask was put to the runner's own MCP door. These came back with no face named, so no host on any row could have drawn them — the defect is under the hosts, not in them.",
"cells": [
"linkedin-decide: snappy-linkedin post → not driven, `post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it."
],
"ownerCommand": null
},
{
"key": "hosts-not-driven-chatgpt",
"headline": "ChatGPT web app could not be driven at all",
"mechanism": "Every ask to ChatGPT web app is unreachable: no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one.",
"cells": [
"chatgpt/gmail-inbox: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"chatgpt/skool-feed: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"chatgpt/linkedin-profile: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"chatgpt/linkedin-decide: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
],
"ownerCommand": "agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt"
},
{
"key": "hosts-not-driven-claude",
"headline": "Claude.ai web app could not be driven at all",
"mechanism": "Every ask to Claude.ai web app is unreachable: no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one.",
"cells": [
"claude/gmail-inbox: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"claude/skool-feed: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"claude/linkedin-profile: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"claude/linkedin-decide: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
],
"ownerCommand": "agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude"
},
{
"key": "hosts-not-driven-codex",
"headline": "Codex CLI could not be driven at all",
"mechanism": "Every ask to Codex CLI is unreachable: codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.). Until that is true the host is a blank column, and a blank column is not a passing one.",
"cells": [
"codex/gmail-inbox: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)",
"codex/skool-feed: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)",
"codex/linkedin-profile: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)",
"codex/linkedin-decide: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
],
"ownerCommand": "open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>"
},
{
"key": "hosts-verb-folds-onto-no-shape",
"headline": "56 hand(s) have no verb the face road can even shape",
"mechanism": "The runner asks two questions in order — does this verb's word fold onto one of the manifest's shapes, and does the hand's name land on a family. These hands fail the FIRST one, so they never reach the family question and answer prose in every host however many families exist. Most are legitimately faceless (`snappy-ffmpeg concat` has no picture of itself). The ones that are not — `snappy-krisp fetch-meetings` is the worked example — are fixed by one word in the runner's own VERB_SHAPE fold, never by a new table.",
"cells": [
"snappy-ads: metrics, targets",
"snappy-agent-host: start, prompt, status, cancel, sessions, models",
"snappy-ai-models: chat, embed, image",
"snappy-analytics: scorecard",
"snappy-artifact-loop: channels, choose, envelope, kit, parse",
"snappy-ax: at-point, find, focused, macos-use, permissions, press",
"snappy-box: call, routes, sql",
"snappy-browse: clear-requests, click, close, extract, metrics, navigate",
"snappy-cleanshot: area, ax, crop, fullscreen, grab, grab-app",
"snappy-content: atoms, draft-new, metrics, status, tick, tick-all",
"snappy-course: module, modules",
"snappy-data-hygiene: classify, prompt, review",
"snappy-database: describe, query, tables",
"snappy-deploy: status, vercel",
"snappy-desktop: run, screenshot",
"snappy-dom-cartographer: eval, log, patch, snap, start, verify",
"snappy-faces: draw, lang, faces, grammar",
"snappy-ffmpeg: burn-subs, compress, concat, extract-audio, loudnorm, overlay",
"snappy-gemini: describe, generate, image",
"snappy-hands: census",
"snappy-image: canva, course-folder, generate, logo, logos, metrics",
"snappy-inbound: classify, slack-joins",
"snappy-inbox-sweep: gmail-personal, gmail-work, inbox-zero, send-skool, send-slack, slack",
"snappy-infra: ssh, vercel, xano",
"snappy-krisp: fetch-meetings, fetch-document, fetch-action-items, metrics",
"snappy-libretto: record, lessons, lesson, replay, promote",
"snappy-maintenance: all, check, ssl",
"snappy-mine: files, manifest, meetings-by, metrics, pending, persist",
"snappy-nightshift: bar, blocked, board, log, preflight, reap",
"snappy-offer: icp, offer, pricing",
"snappy-openrouter: chat, fallback",
"snappy-ops: ab, actions, catchup, dashboard, diff-machine, dispatch",
"snappy-os-operator: approvals, approve, deny, deploy-truth, grant-create, grant-update",
"snappy-pipeline: avatars, batch-overview, completeness, crash-patterns, data-landing, duplicates",
"snappy-playbook: week, weeks",
"snappy-positioning: banned, check, cite, flow, tone",
"snappy-post: linkedin, slack, telegram",
"snappy-publish: deploys, status",
"snappy-report-publish: gen-batch, publish, style, update-index, verify",
"snappy-review-pages: kit, provenance",
"snappy-sales: clients, leads, log",
"snappy-scheduling: available, propose",
"snappy-sensors: dump, section, stale",
"snappy-session-close: handoff, reconcile",
"snappy-skill: scaffold",
"snappy-sync: share",
"snappy-thumbnails: audit, brief, generate, set, variants",
"snappy-tool-design: lint, example, probe, render, loop, hosts",
"snappy-update: commits, notify",
"snappy-video: caption, clip, transcribe",
"snappy-voice-control: devices, permissions, pick, record, stream-url, transcribe",
"snappy-walkthrough: annotate, capture, gates, lesson, run, status",
"snappy-website: deploys, health",
"snappy-xano-dashboard: logs, open, test",
"snappy-xano-mcp: health, test"
],
"ownerCommand": null
},
{
"key": "hosts-no-face-family",
"headline": "18 platform hand(s) name no face family",
"mechanism": "The runner derives a face family from the hand's own name (snappy-runner/src/face.ts#familyForHand) against snappy-faces' FACE_FAMILIES. These hands land on a family that does not exist, so their answers are prose in EVERY host — no host driver can fix them and no screenshot will ever exist for them. The fix is a face family in the faces library, one per platform, in that platform's own look.",
"cells": [
"snappy-api-sniffer → \"api-sniffer\": snappy-faces declares no \"api-sniffer\" family, so every host draws snappy-api-sniffer's answers as prose — a face to build, not a host to fix",
"snappy-blog → \"blog\": snappy-faces declares no \"blog\" family, so every host draws snappy-blog's answers as prose — a face to build, not a host to fix",
"snappy-channel-contract → \"channel-contract\": snappy-faces declares no \"channel-contract\" family, so every host draws snappy-channel-contract's answers as prose — a face to build, not a host to fix",
"snappy-client-orbiter → \"client-orbiter\": snappy-faces declares no \"client-orbiter\" family, so every host draws snappy-client-orbiter's answers as prose — a face to build, not a host to fix",
"snappy-client-ray → \"client-ray\": snappy-faces declares no \"client-ray\" family, so every host draws snappy-client-ray's answers as prose — a face to build, not a host to fix",
"snappy-client-scott → \"client-scott\": snappy-faces declares no \"client-scott\" family, so every host draws snappy-client-scott's answers as prose — a face to build, not a host to fix",
"snappy-client-total → \"client-total\": snappy-faces declares no \"client-total\" family, so every host draws snappy-client-total's answers as prose — a face to build, not a host to fix",
"snappy-clients → \"clients\": snappy-faces declares no \"clients\" family, so every host draws snappy-clients's answers as prose — a face to build, not a host to fix",
"snappy-corpus → \"corpus\": snappy-faces declares no \"corpus\" family, so every host draws snappy-corpus's answers as prose — a face to build, not a host to fix",
"snappy-docs → \"docs\": snappy-faces declares no \"docs\" family, so every host draws snappy-docs's answers as prose — a face to build, not a host to fix",
"snappy-email → \"email\": snappy-faces declares no \"email\" family, so every host draws snappy-email's answers as prose — a face to build, not a host to fix",
"snappy-gateway → \"gateway\": snappy-faces declares no \"gateway\" family, so every host draws snappy-gateway's answers as prose — a face to build, not a host to fix",
"snappy-hooks → \"hooks\": snappy-faces declares no \"hooks\" family, so every host draws snappy-hooks's answers as prose — a face to build, not a host to fix",
"snappy-mastermind-model → \"mastermind-model\": snappy-faces declares no \"mastermind-model\" family, so every host draws snappy-mastermind-model's answers as prose — a face to build, not a host to fix",
"snappy-outbound → \"outbound\": snappy-faces declares no \"outbound\" family, so every host draws snappy-outbound's answers as prose — a face to build, not a host to fix",
"snappy-remotion → \"remotion\": snappy-faces declares no \"remotion\" family, so every host draws snappy-remotion's answers as prose — a face to build, not a host to fix",
"snappy-settings → \"settings\": snappy-faces declares no \"settings\" family, so every host draws snappy-settings's answers as prose — a face to build, not a host to fix",
"snappy-testimonials → \"testimonials\": snappy-faces declares no \"testimonials\" family, so every host draws snappy-testimonials's answers as prose — a face to build, not a host to fix"
],
"ownerCommand": null
},
{
"key": "hosts-family-has-no-read",
"headline": "6 published face family(ies) no read can reach",
"mechanism": "A family is only as true as the sentence that reaches it. The runner takes TWO roads from an answer to a family, in this order: the hand's own declaration — `HAND_CONTRACT.verbs.<verb>.face: \"<kind>\"` — and, for hands that declare nothing, the derivation `familyForHand` (the hand's name minus the prefix) with VERB_SHAPE folding the verb onto a slot. These families pass neither. They draw beautifully and nobody's words arrive at them. THE FIX IS ALMOST NEVER A FACE. It is one of three, and the cell below says which: (1) a hand already makes this thing and does not say so — it names the kind on the verb that draws it, and prints the rows that kind declares as an ADDITIVE fold, renaming nothing; (2) the hand's read prints text or the wrong shape, so the declaration would be a status over a missing artifact — the read gains a JSON answer first; (3) no hand makes this thing at all, which is an honest red that stays in the denominator until some hand does.",
"cells": [
"\"blocks\": no hand named snappy-blocks exists and no hand's contract declares a \"blocks\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"blocks\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"code\": no hand named snappy-code exists and no hand's contract declares a \"code\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"code\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"instagram\": no hand named snappy-instagram exists and no hand's contract declares a \"instagram\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"instagram\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"sheets\": no hand named snappy-sheets exists and no hand's contract declares a \"sheets\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"sheets\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"stripe\": no hand named snappy-stripe exists and no hand's contract declares a \"stripe\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"stripe\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"x\": no hand named snappy-x exists and no hand's contract declares a \"x\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"x\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
],
"ownerCommand": null
}
],
"number": {
"green": 46,
"total": 84,
"line": "hosts: 46/84 cells"
}
}
{
"schema": "snappy-tool-design/hosts@1",
"generated_at": "2026-09-09T11:04:42.350Z",
"runner": {
"mcp_door": "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
"health_door": "https://roberts-mac-mini.tailca7f42.ts.net/runner/health",
"revision": "1da5a73711-41156a7220a9-1879d420a2",
"hands": 98,
"kernel_head": "1da5a73711be",
"attributed": true,
"proved_at": "2026-09-09T11:03:41.310Z"
},
"prompts": [
{
"id": "gmail-inbox",
"text": "Using Snappy Skills, show me my Gmail inbox, three messages",
"hand": "snappy-gmail",
"family": "gmail",
"press": null
},
{
"id": "skool-feed",
"text": "Using Snappy Skills, show me my Skool feed",
"hand": "snappy-skool",
"family": "skool",
"press": null
},
{
"id": "linkedin-profile",
"text": "Using Snappy Skills, show me my LinkedIn profile",
"hand": "snappy-linkedin",
"family": "linkedin",
"press": null
},
{
"id": "linkedin-decide",
"text": "Using Snappy Skills, draft a LinkedIn post about launch checklists and let me decide",
"hand": "snappy-linkedin",
"family": "linkedin",
"press": "Later"
}
],
"drivers": [
{
"host": "chatgpt",
"label": "ChatGPT web app",
"road": "agent-browser --state chatgpt → https://chatgpt.com",
"surface": "widget",
"driven": false,
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"ownerCommand": "agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt"
},
{
"host": "claude",
"label": "Claude.ai web app",
"road": "agent-browser --state claude → https://claude.ai",
"surface": "widget",
"driven": false,
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"ownerCommand": "agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude"
},
{
"host": "codex",
"label": "Codex CLI",
"road": "codex exec --json (mcp_servers.snappy-skills)",
"surface": "none",
"driven": false,
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)",
"ownerCommand": "open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>"
},
{
"host": "menubar",
"label": "Snappy menu bar",
"road": "SnappyBar popover WKWebView over its local runner",
"surface": "webview",
"driven": true,
"reason": "the bar's own page mounted a face in its stage: 120 element(s) under #snappy-faces-root inside the stage <iframe>, 2393 character(s) of drawn text, history row \"Gmail · list\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves.",
"ownerCommand": "cd ~/Projects/snappy-runner/apps/snappy-bar && ./scripts/make-app.sh && open dist/Snappy.app # then: npx tsx api.ts hosts --only menubar --token-file <file>"
},
{
"host": "telegram",
"label": "Telegram on the phone",
"road": "snappy-telegram show (PNG) + decisions",
"surface": "image",
"driven": true,
"reason": "the card is already on the owner's own chat as photo message_id 8387, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself",
"ownerCommand": null
}
],
"cells": [
{
"host": "chatgpt",
"prompt": "gmail-inbox",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "chatgpt",
"prompt": "skool-feed",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "chatgpt",
"prompt": "linkedin-profile",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "chatgpt",
"prompt": "linkedin-decide",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "gmail-inbox",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "skool-feed",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "linkedin-profile",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "claude",
"prompt": "linkedin-decide",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
},
{
"host": "codex",
"prompt": "gmail-inbox",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)"
},
{
"host": "codex",
"prompt": "skool-feed",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
},
{
"host": "codex",
"prompt": "linkedin-profile",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
},
{
"host": "codex",
"prompt": "linkedin-decide",
"ms": null,
"kind": null,
"shot": null,
"press": null,
"standing": null,
"mount": "not-driven",
"reason": "codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
},
{
"host": "menubar",
"prompt": "gmail-inbox",
"ms": 6953,
"kind": "gmail-list",
"shot": "/tmp/hosts-battery/menubar-gmail-inbox.png",
"press": null,
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 120 element(s) under #snappy-faces-root inside the stage <iframe>, 2393 character(s) of drawn text, history row \"Gmail · list\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "menubar",
"prompt": "skool-feed",
"ms": 4944,
"kind": "skool-feed",
"shot": "/tmp/hosts-battery/menubar-skool-feed.png",
"press": null,
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 418 element(s) under #snappy-faces-root inside the stage <iframe>, 10117 character(s) of drawn text, history row \"Skool · list\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "menubar",
"prompt": "linkedin-profile",
"ms": 4930,
"kind": "linkedin-profile",
"shot": "/tmp/hosts-battery/menubar-linkedin-profile.png",
"press": null,
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 17 element(s) under #snappy-faces-root inside the stage <iframe>, 29 character(s) of drawn text, history row \"Linkedin · profile\". Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "menubar",
"prompt": "linkedin-decide",
"ms": 4569,
"kind": "linkedin-decision",
"shot": "/tmp/hosts-battery/menubar-linkedin-decide.png",
"press": {
"word": "Later",
"reached_runner": false,
"within_ms": null,
"how": "the doors are drawn and pressable in the face's own frame; this rig presses none — a press is the owner's, and pressing Later here would be answering for him."
},
"standing": null,
"mount": "mounted",
"reason": "the bar's own page mounted a face in its stage: 71 element(s) under #snappy-faces-root inside the stage <iframe>, 1139 character(s) of drawn text, history row \"Linkedin · decision\", drawn as the faces skill's shipped example rather than a staged draft — the bar's `show <kind>` road, so nothing was staged to reach it. Measured on the shipped bar bundle over an http origin with Swift's switchboard stood in for; the .app's own popover window is not what this proves."
},
{
"host": "telegram",
"prompt": "gmail-inbox",
"ms": null,
"kind": "gmail-list",
"shot": null,
"press": null,
"standing": 8387,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8387, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself"
},
{
"host": "telegram",
"prompt": "skool-feed",
"ms": null,
"kind": "skool-feed",
"shot": null,
"press": null,
"standing": 8388,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8388, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself"
},
{
"host": "telegram",
"prompt": "linkedin-profile",
"ms": null,
"kind": "linkedin-profile",
"shot": null,
"press": null,
"standing": 8389,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8389, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself"
},
{
"host": "telegram",
"prompt": "linkedin-decide",
"ms": null,
"kind": "linkedin-decision",
"shot": null,
"press": {
"word": "Later",
"reached_runner": false,
"within_ms": null,
"how": "snappy-telegram decisions, matched on the ref the send packed (no ref to match — a press could only be read if the send had named one). Nothing here presses: the doors are on the owner's own phone and pressing one would be answering for him."
},
"standing": 8386,
"mount": "mounted",
"reason": "the card is already on the owner's own chat as photo message_id 8386, put there by the 2026-09-09 03:5x run, and this lane did not send a second copy of a decision to measure itself — its doors are still unpressed; shown, press pending"
}
],
"road": [
{
"prompt": "gmail-inbox",
"hand": "snappy-gmail",
"verb": "list",
"outcome": "answered",
"kind": "gmail-list",
"family": "gmail",
"resource_uri": "ui://snappy/faces",
"ms": 1745,
"hops": 1,
"note": "the runner's own search ranked snappy-gmail list first for the person's sentence; one call"
},
{
"prompt": "skool-feed",
"hand": "snappy-skool",
"verb": "posts",
"outcome": "answered",
"kind": "skool-feed",
"family": "skool",
"resource_uri": "ui://snappy/faces",
"ms": 1053,
"hops": 1,
"note": "the runner's own search ranked snappy-skool posts first for the person's sentence; one call"
},
{
"prompt": "linkedin-profile",
"hand": "snappy-linkedin",
"verb": "profile",
"outcome": "answered",
"kind": "linkedin-profile",
"family": "linkedin",
"resource_uri": "ui://snappy/faces",
"ms": 902,
"hops": 1,
"note": "the runner's own search ranked snappy-linkedin profile first for the person's sentence; one call"
},
{
"prompt": "linkedin-decide",
"hand": "snappy-linkedin",
"verb": "post",
"outcome": "not driven",
"kind": null,
"family": null,
"resource_uri": null,
"ms": null,
"hops": 0,
"note": "`post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it."
}
],
"face_families": {
"published": [
"gmail",
"linkedin",
"slack",
"telegram",
"whatsapp",
"imessage",
"x",
"instagram",
"youtube",
"skool",
"notion",
"github",
"freshbooks",
"calendar",
"sheets",
"stripe",
"media",
"blocks",
"statechange",
"data",
"code",
"agent",
"thumbnails",
"deploy",
"schedule",
"krisp",
"knowledge",
"transcripts",
"imageset",
"walkthrough",
"doc"
],
"missing": [
{
"skill": "snappy-api-sniffer",
"family": "api-sniffer",
"has_family": false,
"reason": "snappy-faces declares no \"api-sniffer\" family, so every host draws snappy-api-sniffer's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-blog",
"family": "blog",
"has_family": false,
"reason": "snappy-faces declares no \"blog\" family, so every host draws snappy-blog's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-calendar",
"family": "calendar",
"has_family": true,
"reason": "snappy-faces declares the \"calendar\" family"
},
{
"skill": "snappy-channel-contract",
"family": "channel-contract",
"has_family": false,
"reason": "snappy-faces declares no \"channel-contract\" family, so every host draws snappy-channel-contract's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-orbiter",
"family": "client-orbiter",
"has_family": false,
"reason": "snappy-faces declares no \"client-orbiter\" family, so every host draws snappy-client-orbiter's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-ray",
"family": "client-ray",
"has_family": false,
"reason": "snappy-faces declares no \"client-ray\" family, so every host draws snappy-client-ray's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-scott",
"family": "client-scott",
"has_family": false,
"reason": "snappy-faces declares no \"client-scott\" family, so every host draws snappy-client-scott's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-client-total",
"family": "client-total",
"has_family": false,
"reason": "snappy-faces declares no \"client-total\" family, so every host draws snappy-client-total's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-clients",
"family": "clients",
"has_family": false,
"reason": "snappy-faces declares no \"clients\" family, so every host draws snappy-clients's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-corpus",
"family": "corpus",
"has_family": false,
"reason": "snappy-faces declares no \"corpus\" family, so every host draws snappy-corpus's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-docs",
"family": "docs",
"has_family": false,
"reason": "snappy-faces declares no \"docs\" family, so every host draws snappy-docs's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-email",
"family": "email",
"has_family": false,
"reason": "snappy-faces declares no \"email\" family, so every host draws snappy-email's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-freshbooks",
"family": "freshbooks",
"has_family": true,
"reason": "snappy-faces declares the \"freshbooks\" family"
},
{
"skill": "snappy-gateway",
"family": "gateway",
"has_family": false,
"reason": "snappy-faces declares no \"gateway\" family, so every host draws snappy-gateway's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-github",
"family": "github",
"has_family": true,
"reason": "snappy-faces declares the \"github\" family"
},
{
"skill": "snappy-gmail",
"family": "gmail",
"has_family": true,
"reason": "snappy-faces declares the \"gmail\" family"
},
{
"skill": "snappy-hooks",
"family": "hooks",
"has_family": false,
"reason": "snappy-faces declares no \"hooks\" family, so every host draws snappy-hooks's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-imessage",
"family": "imessage",
"has_family": true,
"reason": "snappy-faces declares the \"imessage\" family"
},
{
"skill": "snappy-knowledge",
"family": "knowledge",
"has_family": true,
"reason": "snappy-faces declares the \"knowledge\" family"
},
{
"skill": "snappy-linkedin",
"family": "linkedin",
"has_family": true,
"reason": "snappy-faces declares the \"linkedin\" family"
},
{
"skill": "snappy-mastermind-model",
"family": "mastermind-model",
"has_family": false,
"reason": "snappy-faces declares no \"mastermind-model\" family, so every host draws snappy-mastermind-model's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-notion",
"family": "notion",
"has_family": true,
"reason": "snappy-faces declares the \"notion\" family"
},
{
"skill": "snappy-outbound",
"family": "outbound",
"has_family": false,
"reason": "snappy-faces declares no \"outbound\" family, so every host draws snappy-outbound's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-remotion",
"family": "remotion",
"has_family": false,
"reason": "snappy-faces declares no \"remotion\" family, so every host draws snappy-remotion's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-settings",
"family": "settings",
"has_family": false,
"reason": "snappy-faces declares no \"settings\" family, so every host draws snappy-settings's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-skool",
"family": "skool",
"has_family": true,
"reason": "snappy-faces declares the \"skool\" family"
},
{
"skill": "snappy-slack",
"family": "slack",
"has_family": true,
"reason": "snappy-faces declares the \"slack\" family"
},
{
"skill": "snappy-statechange",
"family": "statechange",
"has_family": true,
"reason": "snappy-faces declares the \"statechange\" family"
},
{
"skill": "snappy-telegram",
"family": "telegram",
"has_family": true,
"reason": "snappy-faces declares the \"telegram\" family"
},
{
"skill": "snappy-testimonials",
"family": "testimonials",
"has_family": false,
"reason": "snappy-faces declares no \"testimonials\" family, so every host draws snappy-testimonials's answers as prose — a face to build, not a host to fix"
},
{
"skill": "snappy-transcripts",
"family": "transcripts",
"has_family": true,
"reason": "snappy-faces declares the \"transcripts\" family"
},
{
"skill": "snappy-whatsapp",
"family": "whatsapp",
"has_family": true,
"reason": "snappy-faces declares the \"whatsapp\" family"
},
{
"skill": "snappy-youtube",
"family": "youtube",
"has_family": true,
"reason": "snappy-faces declares the \"youtube\" family"
}
],
"unreachable": [
{
"family": "agent",
"read": "snappy-agent-host status → agent-run",
"reachable": true,
"reason": "snappy-agent-host's contract declares \"status\" draws agent-run, which the build report files under \"agent\" — the hand named the face and nothing was derived over it"
},
{
"family": "blocks",
"read": null,
"reachable": false,
"reason": "no hand named snappy-blocks exists and no hand's contract declares a \"blocks\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"blocks\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "calendar",
"read": "snappy-calendar list → list",
"reachable": true,
"reason": "snappy-calendar list folds onto list, so a person's read reaches the \"calendar\" family"
},
{
"family": "code",
"read": null,
"reachable": false,
"reason": "no hand named snappy-code exists and no hand's contract declares a \"code\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"code\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "data",
"read": "snappy-database query → data-table",
"reachable": true,
"reason": "snappy-database's contract declares \"query\" draws data-table, which the build report files under \"data\" — the hand named the face and nothing was derived over it"
},
{
"family": "deploy",
"read": "snappy-deploy status → deploy-receipt",
"reachable": true,
"reason": "snappy-deploy's contract declares \"status\" draws deploy-receipt, which the build report files under \"deploy\" — the hand named the face and nothing was derived over it"
},
{
"family": "doc",
"read": "snappy-docs page → doc-card",
"reachable": true,
"reason": "snappy-docs's contract declares \"page\" draws doc-card, which the build report files under \"doc\" — the hand named the face and nothing was derived over it"
},
{
"family": "freshbooks",
"read": "snappy-freshbooks list → list",
"reachable": true,
"reason": "snappy-freshbooks list folds onto list, so a person's read reaches the \"freshbooks\" family"
},
{
"family": "github",
"read": "snappy-github comments → list",
"reachable": true,
"reason": "snappy-github comments folds onto list, so a person's read reaches the \"github\" family"
},
{
"family": "gmail",
"read": "snappy-gmail list → list",
"reachable": true,
"reason": "snappy-gmail list folds onto list, so a person's read reaches the \"gmail\" family"
},
{
"family": "imageset",
"read": "snappy-image logos → imageset-grid",
"reachable": true,
"reason": "snappy-image's contract declares \"logos\" draws imageset-grid, which the build report files under \"imageset\" — the hand named the face and nothing was derived over it"
},
{
"family": "imessage",
"read": "snappy-imessage recent → list",
"reachable": true,
"reason": "snappy-imessage recent folds onto list, so a person's read reaches the \"imessage\" family"
},
{
"family": "instagram",
"read": null,
"reachable": false,
"reason": "no hand named snappy-instagram exists and no hand's contract declares a \"instagram\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"instagram\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "knowledge",
"read": "snappy-knowledge get → one",
"reachable": true,
"reason": "snappy-knowledge get folds onto one, so a person's read reaches the \"knowledge\" family"
},
{
"family": "krisp",
"read": "snappy-krisp fetch-action-items → krisp-actions",
"reachable": true,
"reason": "snappy-krisp's contract declares \"fetch-action-items\" draws krisp-actions, which the build report files under \"krisp\" — the hand named the face and nothing was derived over it"
},
{
"family": "linkedin",
"read": "snappy-linkedin profile → profile",
"reachable": true,
"reason": "snappy-linkedin profile folds onto profile, so a person's read reaches the \"linkedin\" family"
},
{
"family": "media",
"read": "snappy-ffmpeg probe → media-video",
"reachable": true,
"reason": "snappy-ffmpeg's contract declares \"probe\" draws media-video, which the build report files under \"media\" — the hand named the face and nothing was derived over it"
},
{
"family": "notion",
"read": "snappy-notion me → profile",
"reachable": true,
"reason": "snappy-notion me folds onto profile, so a person's read reaches the \"notion\" family"
},
{
"family": "schedule",
"read": "snappy-scheduling propose → schedule-pick",
"reachable": true,
"reason": "snappy-scheduling's contract declares \"propose\" draws schedule-pick, which the build report files under \"schedule\" — the hand named the face and nothing was derived over it"
},
{
"family": "sheets",
"read": null,
"reachable": false,
"reason": "no hand named snappy-sheets exists and no hand's contract declares a \"sheets\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"sheets\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "skool",
"read": "snappy-skool me → profile",
"reachable": true,
"reason": "snappy-skool me folds onto profile, so a person's read reaches the \"skool\" family"
},
{
"family": "slack",
"read": "snappy-slack messages → list",
"reachable": true,
"reason": "snappy-slack messages folds onto list, so a person's read reaches the \"slack\" family"
},
{
"family": "statechange",
"read": "snappy-statechange posts → list",
"reachable": true,
"reason": "snappy-statechange posts folds onto list, so a person's read reaches the \"statechange\" family"
},
{
"family": "stripe",
"read": null,
"reachable": false,
"reason": "no hand named snappy-stripe exists and no hand's contract declares a \"stripe\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"stripe\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "telegram",
"read": "snappy-telegram read → one",
"reachable": true,
"reason": "snappy-telegram read folds onto one, so a person's read reaches the \"telegram\" family"
},
{
"family": "thumbnails",
"read": "snappy-thumbnails variants → thumbnails-set",
"reachable": true,
"reason": "snappy-thumbnails's contract declares \"variants\" draws thumbnails-set, which the build report files under \"thumbnails\" — the hand named the face and nothing was derived over it"
},
{
"family": "transcripts",
"read": "snappy-transcripts list → list",
"reachable": true,
"reason": "snappy-transcripts list folds onto list, so a person's read reaches the \"transcripts\" family"
},
{
"family": "walkthrough",
"read": "snappy-walkthrough steps → walkthrough-steps",
"reachable": true,
"reason": "snappy-walkthrough's contract declares \"steps\" draws walkthrough-steps, which the build report files under \"walkthrough\" — the hand named the face and nothing was derived over it"
},
{
"family": "whatsapp",
"read": "snappy-whatsapp read → one",
"reachable": true,
"reason": "snappy-whatsapp read folds onto one, so a person's read reaches the \"whatsapp\" family"
},
{
"family": "x",
"read": null,
"reachable": false,
"reason": "no hand named snappy-x exists and no hand's contract declares a \"x\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"x\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
},
{
"family": "youtube",
"read": "snappy-youtube comments → list",
"reachable": true,
"reason": "snappy-youtube comments folds onto list, so a person's read reaches the \"youtube\" family"
}
]
},
"no_shape": [
{
"skill": "snappy-ads",
"verbs": [
"metrics",
"targets"
]
},
{
"skill": "snappy-agent-host",
"verbs": [
"start",
"prompt",
"status",
"cancel",
"sessions",
"models"
]
},
{
"skill": "snappy-ai-models",
"verbs": [
"chat",
"embed",
"image"
]
},
{
"skill": "snappy-analytics",
"verbs": [
"scorecard"
]
},
{
"skill": "snappy-artifact-loop",
"verbs": [
"channels",
"choose",
"envelope",
"kit",
"parse"
]
},
{
"skill": "snappy-ax",
"verbs": [
"at-point",
"find",
"focused",
"macos-use",
"permissions",
"press",
"raw",
"set-value",
"text",
"tree"
]
},
{
"skill": "snappy-box",
"verbs": [
"call",
"routes",
"sql"
]
},
{
"skill": "snappy-browse",
"verbs": [
"clear-requests",
"click",
"close",
"extract",
"metrics",
"navigate",
"requests",
"snapshot"
]
},
{
"skill": "snappy-cleanshot",
"verbs": [
"area",
"ax",
"crop",
"fullscreen",
"grab",
"grab-app",
"history",
"icons",
"last",
"ocr",
"ocr-region",
"previous",
"probe",
"record",
"screens",
"scroll",
"space",
"spaces",
"sweep",
"tab",
"tabs",
"vlm",
"win",
"window",
"windows"
]
},
{
"skill": "snappy-content",
"verbs": [
"atoms",
"draft-new",
"metrics",
"status",
"tick",
"tick-all"
]
},
{
"skill": "snappy-course",
"verbs": [
"module",
"modules"
]
},
{
"skill": "snappy-data-hygiene",
"verbs": [
"classify",
"prompt",
"review"
]
},
{
"skill": "snappy-database",
"verbs": [
"describe",
"query",
"tables"
]
},
{
"skill": "snappy-deploy",
"verbs": [
"status",
"vercel"
]
},
{
"skill": "snappy-desktop",
"verbs": [
"run",
"screenshot"
]
},
{
"skill": "snappy-dom-cartographer",
"verbs": [
"eval",
"log",
"patch",
"snap",
"start",
"verify"
]
},
{
"skill": "snappy-faces",
"verbs": [
"draw",
"lang",
"faces",
"grammar"
]
},
{
"skill": "snappy-ffmpeg",
"verbs": [
"burn-subs",
"compress",
"concat",
"extract-audio",
"loudnorm",
"overlay",
"probe",
"resize",
"thumbnail",
"trim"
]
},
{
"skill": "snappy-gemini",
"verbs": [
"describe",
"generate",
"image"
]
},
{
"skill": "snappy-hands",
"verbs": [
"census"
]
},
{
"skill": "snappy-image",
"verbs": [
"canva",
"course-folder",
"generate",
"logo",
"logos",
"metrics",
"reframe",
"upload",
"url"
]
},
{
"skill": "snappy-inbound",
"verbs": [
"classify",
"slack-joins"
]
},
{
"skill": "snappy-inbox-sweep",
"verbs": [
"gmail-personal",
"gmail-work",
"inbox-zero",
"send-skool",
"send-slack",
"slack",
"sweep"
]
},
{
"skill": "snappy-infra",
"verbs": [
"ssh",
"vercel",
"xano"
]
},
{
"skill": "snappy-krisp",
"verbs": [
"fetch-meetings",
"fetch-document",
"fetch-action-items",
"metrics"
]
},
{
"skill": "snappy-libretto",
"verbs": [
"record",
"lessons",
"lesson",
"replay",
"promote"
]
},
{
"skill": "snappy-maintenance",
"verbs": [
"all",
"check",
"ssl"
]
},
{
"skill": "snappy-mine",
"verbs": [
"files",
"manifest",
"meetings-by",
"metrics",
"pending",
"persist",
"sources"
]
},
{
"skill": "snappy-nightshift",
"verbs": [
"bar",
"blocked",
"board",
"log",
"preflight",
"reap",
"report",
"status"
]
},
{
"skill": "snappy-offer",
"verbs": [
"icp",
"offer",
"pricing"
]
},
{
"skill": "snappy-openrouter",
"verbs": [
"chat",
"fallback"
]
},
{
"skill": "snappy-ops",
"verbs": [
"ab",
"actions",
"catchup",
"dashboard",
"diff-machine",
"dispatch",
"dry",
"fingerprint",
"ladder",
"loops",
"menu",
"recipes",
"remind",
"replay",
"run",
"settle",
"share",
"snapshot",
"status",
"story",
"sweep",
"trace",
"trend"
]
},
{
"skill": "snappy-os-operator",
"verbs": [
"approvals",
"approve",
"deny",
"deploy-truth",
"grant-create",
"grant-update",
"health",
"run",
"runs",
"script-create",
"script-run",
"script-version",
"session",
"skill-create",
"stage"
]
},
{
"skill": "snappy-pipeline",
"verbs": [
"avatars",
"batch-overview",
"completeness",
"crash-patterns",
"data-landing",
"duplicates",
"edge-gaps",
"history",
"queue-health",
"stuck-analysis"
]
},
{
"skill": "snappy-playbook",
"verbs": [
"week",
"weeks"
]
},
{
"skill": "snappy-positioning",
"verbs": [
"banned",
"check",
"cite",
"flow",
"tone"
]
},
{
"skill": "snappy-post",
"verbs": [
"linkedin",
"slack",
"telegram"
]
},
{
"skill": "snappy-publish",
"verbs": [
"deploys",
"status"
]
},
{
"skill": "snappy-report-publish",
"verbs": [
"gen-batch",
"publish",
"style",
"update-index",
"verify"
]
},
{
"skill": "snappy-review-pages",
"verbs": [
"kit",
"provenance"
]
},
{
"skill": "snappy-sales",
"verbs": [
"clients",
"leads",
"log"
]
},
{
"skill": "snappy-scheduling",
"verbs": [
"available",
"propose"
]
},
{
"skill": "snappy-sensors",
"verbs": [
"dump",
"section",
"stale"
]
},
{
"skill": "snappy-session-close",
"verbs": [
"handoff",
"reconcile"
]
},
{
"skill": "snappy-skill",
"verbs": [
"scaffold"
]
},
{
"skill": "snappy-sync",
"verbs": [
"share"
]
},
{
"skill": "snappy-thumbnails",
"verbs": [
"audit",
"brief",
"generate",
"set",
"variants"
]
},
{
"skill": "snappy-tool-design",
"verbs": [
"lint",
"example",
"probe",
"render",
"loop",
"hosts",
"fix-loader"
]
},
{
"skill": "snappy-update",
"verbs": [
"commits",
"notify"
]
},
{
"skill": "snappy-video",
"verbs": [
"caption",
"clip",
"transcribe"
]
},
{
"skill": "snappy-voice-control",
"verbs": [
"devices",
"permissions",
"pick",
"record",
"stream-url",
"transcribe"
]
},
{
"skill": "snappy-walkthrough",
"verbs": [
"annotate",
"capture",
"gates",
"lesson",
"run",
"status",
"steps"
]
},
{
"skill": "snappy-website",
"verbs": [
"deploys",
"health"
]
},
{
"skill": "snappy-xano-dashboard",
"verbs": [
"logs",
"open",
"test"
]
},
{
"skill": "snappy-xano-mcp",
"verbs": [
"health",
"test"
]
}
],
"clusters": [
{
"key": "hosts-road-names-no-face",
"headline": "1 of the four asks never reach a named face on the runner itself",
"mechanism": "Before any host is blamed, the same ask was put to the runner's own MCP door. These came back with no face named, so no host on any row could have drawn them — the defect is under the hosts, not in them.",
"cells": [
"linkedin-decide: snappy-linkedin post → not driven, `post` stages by contract (`--now` is the only bypass), and staging puts a decision in front of the owner. This lane does not interrupt him to measure itself; the press axis is driven by a lane he arms for it."
],
"ownerCommand": null
},
{
"key": "hosts-not-driven-chatgpt",
"headline": "ChatGPT web app could not be driven at all",
"mechanism": "Every ask to ChatGPT web app is unreachable: no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one.",
"cells": [
"chatgpt/gmail-inbox: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"chatgpt/skool-feed: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"chatgpt/linkedin-profile: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"chatgpt/linkedin-decide: not-driven — no saved agent-browser state for chatgpt — 184 state(s) exist on this Mac and none match /chatgpt/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
],
"ownerCommand": "agent-browser --session save-chatgpt open https://chatgpt.com # log in, then: agent-browser --session save-chatgpt state save chatgpt"
},
{
"key": "hosts-not-driven-claude",
"headline": "Claude.ai web app could not be driven at all",
"mechanism": "Every ask to Claude.ai web app is unreachable: no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.. Until that is true the host is a blank column, and a blank column is not a passing one.",
"cells": [
"claude/gmail-inbox: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"claude/skool-feed: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"claude/linkedin-profile: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩.",
"claude/linkedin-decide: not-driven — no saved agent-browser state for claude — 184 state(s) exist on this Mac and none match /claude/. The owner saves it once; nothing here can log in for him ⟨never pop an auth browser⟩."
],
"ownerCommand": "agent-browser --session save-claude open https://claude.ai # log in, then: agent-browser --session save-claude state save claude"
},
{
"key": "hosts-not-driven-codex",
"headline": "Codex CLI could not be driven at all",
"mechanism": "Every ask to Codex CLI is unreachable: codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.). Until that is true the host is a blank column, and a blank column is not a passing one.",
"cells": [
"codex/gmail-inbox: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.)",
"codex/skool-feed: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)",
"codex/linkedin-profile: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)",
"codex/linkedin-decide: not-driven — codex exec failed before answering: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 14th, 2026 10:03 PM. (also, every turn: Exceeded skills context budget of 2%. All skill descriptions were removed and 85 additional skills were not included in the model-visible skills list.) (the ask was not re-driven: the window belongs to the account and had already answered for this host)"
],
"ownerCommand": "open https://chatgpt.com/codex/settings/usage # top up, then: npx tsx api.ts hosts --only codex --token-file <file>"
},
{
"key": "hosts-verb-folds-onto-no-shape",
"headline": "56 hand(s) have no verb the face road can even shape",
"mechanism": "The runner asks two questions in order — does this verb's word fold onto one of the manifest's shapes, and does the hand's name land on a family. These hands fail the FIRST one, so they never reach the family question and answer prose in every host however many families exist. Most are legitimately faceless (`snappy-ffmpeg concat` has no picture of itself). The ones that are not — `snappy-krisp fetch-meetings` is the worked example — are fixed by one word in the runner's own VERB_SHAPE fold, never by a new table.",
"cells": [
"snappy-ads: metrics, targets",
"snappy-agent-host: start, prompt, status, cancel, sessions, models",
"snappy-ai-models: chat, embed, image",
"snappy-analytics: scorecard",
"snappy-artifact-loop: channels, choose, envelope, kit, parse",
"snappy-ax: at-point, find, focused, macos-use, permissions, press",
"snappy-box: call, routes, sql",
"snappy-browse: clear-requests, click, close, extract, metrics, navigate",
"snappy-cleanshot: area, ax, crop, fullscreen, grab, grab-app",
"snappy-content: atoms, draft-new, metrics, status, tick, tick-all",
"snappy-course: module, modules",
"snappy-data-hygiene: classify, prompt, review",
"snappy-database: describe, query, tables",
"snappy-deploy: status, vercel",
"snappy-desktop: run, screenshot",
"snappy-dom-cartographer: eval, log, patch, snap, start, verify",
"snappy-faces: draw, lang, faces, grammar",
"snappy-ffmpeg: burn-subs, compress, concat, extract-audio, loudnorm, overlay",
"snappy-gemini: describe, generate, image",
"snappy-hands: census",
"snappy-image: canva, course-folder, generate, logo, logos, metrics",
"snappy-inbound: classify, slack-joins",
"snappy-inbox-sweep: gmail-personal, gmail-work, inbox-zero, send-skool, send-slack, slack",
"snappy-infra: ssh, vercel, xano",
"snappy-krisp: fetch-meetings, fetch-document, fetch-action-items, metrics",
"snappy-libretto: record, lessons, lesson, replay, promote",
"snappy-maintenance: all, check, ssl",
"snappy-mine: files, manifest, meetings-by, metrics, pending, persist",
"snappy-nightshift: bar, blocked, board, log, preflight, reap",
"snappy-offer: icp, offer, pricing",
"snappy-openrouter: chat, fallback",
"snappy-ops: ab, actions, catchup, dashboard, diff-machine, dispatch",
"snappy-os-operator: approvals, approve, deny, deploy-truth, grant-create, grant-update",
"snappy-pipeline: avatars, batch-overview, completeness, crash-patterns, data-landing, duplicates",
"snappy-playbook: week, weeks",
"snappy-positioning: banned, check, cite, flow, tone",
"snappy-post: linkedin, slack, telegram",
"snappy-publish: deploys, status",
"snappy-report-publish: gen-batch, publish, style, update-index, verify",
"snappy-review-pages: kit, provenance",
"snappy-sales: clients, leads, log",
"snappy-scheduling: available, propose",
"snappy-sensors: dump, section, stale",
"snappy-session-close: handoff, reconcile",
"snappy-skill: scaffold",
"snappy-sync: share",
"snappy-thumbnails: audit, brief, generate, set, variants",
"snappy-tool-design: lint, example, probe, render, loop, hosts",
"snappy-update: commits, notify",
"snappy-video: caption, clip, transcribe",
"snappy-voice-control: devices, permissions, pick, record, stream-url, transcribe",
"snappy-walkthrough: annotate, capture, gates, lesson, run, status",
"snappy-website: deploys, health",
"snappy-xano-dashboard: logs, open, test",
"snappy-xano-mcp: health, test"
],
"ownerCommand": null
},
{
"key": "hosts-no-face-family",
"headline": "18 platform hand(s) name no face family",
"mechanism": "The runner derives a face family from the hand's own name (snappy-runner/src/face.ts#familyForHand) against snappy-faces' FACE_FAMILIES. These hands land on a family that does not exist, so their answers are prose in EVERY host — no host driver can fix them and no screenshot will ever exist for them. The fix is a face family in the faces library, one per platform, in that platform's own look.",
"cells": [
"snappy-api-sniffer → \"api-sniffer\": snappy-faces declares no \"api-sniffer\" family, so every host draws snappy-api-sniffer's answers as prose — a face to build, not a host to fix",
"snappy-blog → \"blog\": snappy-faces declares no \"blog\" family, so every host draws snappy-blog's answers as prose — a face to build, not a host to fix",
"snappy-channel-contract → \"channel-contract\": snappy-faces declares no \"channel-contract\" family, so every host draws snappy-channel-contract's answers as prose — a face to build, not a host to fix",
"snappy-client-orbiter → \"client-orbiter\": snappy-faces declares no \"client-orbiter\" family, so every host draws snappy-client-orbiter's answers as prose — a face to build, not a host to fix",
"snappy-client-ray → \"client-ray\": snappy-faces declares no \"client-ray\" family, so every host draws snappy-client-ray's answers as prose — a face to build, not a host to fix",
"snappy-client-scott → \"client-scott\": snappy-faces declares no \"client-scott\" family, so every host draws snappy-client-scott's answers as prose — a face to build, not a host to fix",
"snappy-client-total → \"client-total\": snappy-faces declares no \"client-total\" family, so every host draws snappy-client-total's answers as prose — a face to build, not a host to fix",
"snappy-clients → \"clients\": snappy-faces declares no \"clients\" family, so every host draws snappy-clients's answers as prose — a face to build, not a host to fix",
"snappy-corpus → \"corpus\": snappy-faces declares no \"corpus\" family, so every host draws snappy-corpus's answers as prose — a face to build, not a host to fix",
"snappy-docs → \"docs\": snappy-faces declares no \"docs\" family, so every host draws snappy-docs's answers as prose — a face to build, not a host to fix",
"snappy-email → \"email\": snappy-faces declares no \"email\" family, so every host draws snappy-email's answers as prose — a face to build, not a host to fix",
"snappy-gateway → \"gateway\": snappy-faces declares no \"gateway\" family, so every host draws snappy-gateway's answers as prose — a face to build, not a host to fix",
"snappy-hooks → \"hooks\": snappy-faces declares no \"hooks\" family, so every host draws snappy-hooks's answers as prose — a face to build, not a host to fix",
"snappy-mastermind-model → \"mastermind-model\": snappy-faces declares no \"mastermind-model\" family, so every host draws snappy-mastermind-model's answers as prose — a face to build, not a host to fix",
"snappy-outbound → \"outbound\": snappy-faces declares no \"outbound\" family, so every host draws snappy-outbound's answers as prose — a face to build, not a host to fix",
"snappy-remotion → \"remotion\": snappy-faces declares no \"remotion\" family, so every host draws snappy-remotion's answers as prose — a face to build, not a host to fix",
"snappy-settings → \"settings\": snappy-faces declares no \"settings\" family, so every host draws snappy-settings's answers as prose — a face to build, not a host to fix",
"snappy-testimonials → \"testimonials\": snappy-faces declares no \"testimonials\" family, so every host draws snappy-testimonials's answers as prose — a face to build, not a host to fix"
],
"ownerCommand": null
},
{
"key": "hosts-family-has-no-read",
"headline": "6 published face family(ies) no read can reach",
"mechanism": "A family is only as true as the sentence that reaches it. The runner takes TWO roads from an answer to a family, in this order: the hand's own declaration — `HAND_CONTRACT.verbs.<verb>.face: \"<kind>\"` — and, for hands that declare nothing, the derivation `familyForHand` (the hand's name minus the prefix) with VERB_SHAPE folding the verb onto a slot. These families pass neither. They draw beautifully and nobody's words arrive at them. THE FIX IS ALMOST NEVER A FACE. It is one of three, and the cell below says which: (1) a hand already makes this thing and does not say so — it names the kind on the verb that draws it, and prints the rows that kind declares as an ADDITIVE fold, renaming nothing; (2) the hand's read prints text or the wrong shape, so the declaration would be a status over a missing artifact — the read gains a JSON answer first; (3) no hand makes this thing at all, which is an honest red that stays in the denominator until some hand does.",
"cells": [
"\"blocks\": no hand named snappy-blocks exists and no hand's contract declares a \"blocks\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"blocks\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"code\": no hand named snappy-code exists and no hand's contract declares a \"code\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"code\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"instagram\": no hand named snappy-instagram exists and no hand's contract declares a \"instagram\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"instagram\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"sheets\": no hand named snappy-sheets exists and no hand's contract declares a \"sheets\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"sheets\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"stripe\": no hand named snappy-stripe exists and no hand's contract declares a \"stripe\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"stripe\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red",
"\"x\": no hand named snappy-x exists and no hand's contract declares a \"x\" face on any verb — the two roads the runner takes — so no sentence a person types can land on the \"x\" faces, however well they draw. If a hand already makes this thing, it says so with `verbs.<verb>.face`; if none does, this family is made by no hand yet and that is the honest red"
],
"ownerCommand": null
}
],
"number": {
"green": 46,
"total": 84,
"line": "hosts: 46/84 cells"
}
}
{
"schema": "snappy-tool-design/probe@1",
"generated_at": "2026-09-09T13:07:37.594Z",
"server": {
"runner_url": "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
"via": "mini",
"client_created_at": "2026-09-09T13:00:28.950Z",
"published_tools": 5,
"tool_names": [
"execute",
"info",
"search",
"shell",
"update"
],
"tools_list_bytes": 5763,
"tools_list_tokens": 1441,
"tools_sorted": true,
"ttl_ms_present": false,
"cache_scope_present": false,
"prompts_count": 101,
"prompts_bytes": 28317,
"prompts_ms": 81,
"resources_count": 327,
"resources_bytes": 107511,
"resources_ms": 49,
"faces_view_listed": true,
"faces_view_bytes": 673535,
"faces_view_ms": 128,
"faces_view_mime": "text/html;profile=mcp-app",
"revision_first": "94ed3d871b-a732f69f0d25-c72403f851",
"revision_last": "94ed3d871b-a732f69f0d25-c72403f851",
"fresh_client": true,
"handshake_after_deploy": true,
"metrics": [
"accuracy",
"runtime",
"tool_call_count",
"tokens",
"errors"
],
"run": {
"accuracy": 0.7452,
"runtime": 428643,
"tool_call_count": 486,
"tokens": 576689,
"errors": 80
}
},
"skills": {
"snappy-ads": {
"reads": [
{
"verb": "metrics",
"chars": 1252,
"bytes": 1252,
"tokens": 313,
"duration_ms": 324,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 164
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "targets",
"chars": 1487,
"bytes": 1487,
"tokens": 372,
"duration_ms": 310,
"round_trip_ms": 326,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 277
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-agent-host": {
"reads": [
{
"verb": "sessions",
"chars": 3659,
"bytes": 3659,
"tokens": 915,
"duration_ms": 319,
"round_trip_ms": 335,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1204
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "prompt",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-analytics": {
"reads": [
{
"verb": "scorecard",
"chars": 7389,
"bytes": 7389,
"tokens": 1848,
"duration_ms": 5754,
"round_trip_ms": 5772,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2095
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-api-sniffer": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1142,
"bytes": 1142,
"tokens": 286,
"duration_ms": 332,
"round_trip_ms": 346,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 102
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1142,
"bytes": 1142,
"tokens": 286,
"duration_ms": 338,
"round_trip_ms": 356,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 102
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1143,
"bytes": 1143,
"tokens": 286,
"duration_ms": 339,
"round_trip_ms": 356,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 102
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: recipe-name? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-artifact-loop": {
"reads": [
{
"verb": "channels",
"chars": 2254,
"bytes": 2254,
"tokens": 564,
"duration_ms": 284,
"round_trip_ms": 301,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 746
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "choose",
"chars": 1079,
"bytes": 1079,
"tokens": 270,
"duration_ms": 313,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 172
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "parse",
"chars": 1825,
"bytes": 1825,
"tokens": 457,
"duration_ms": 303,
"round_trip_ms": 316,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 378
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "parse",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "parse",
"provided": "body",
"expected": "text",
"refusal": "nothing ran REFUSED argument_unknown: parse names no argument \"body\". Its grammar is: text?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-ax": {
"reads": [
{
"verb": "focused",
"chars": 1819,
"bytes": 1819,
"tokens": 455,
"duration_ms": 358,
"round_trip_ms": 374,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 250
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "permissions",
"chars": 1401,
"bytes": 1401,
"tokens": 351,
"duration_ms": 324,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 118
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-blog": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1137,
"bytes": 1137,
"tokens": 285,
"duration_ms": 311,
"round_trip_ms": 324,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1137,
"bytes": 1137,
"tokens": 285,
"duration_ms": 302,
"round_trip_ms": 313,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1138,
"bytes": 1138,
"tokens": 285,
"duration_ms": 303,
"round_trip_ms": 316,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-box": {
"reads": [
{
"verb": "routes",
"chars": 2828,
"bytes": 2828,
"tokens": 707,
"duration_ms": 316,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 369
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "routes",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Box server at http://10.0.0.199:8080 did not answer (fetch failed). Start it on the Mac Mini, or call a verb that reads local state.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-browse": {
"reads": [
{
"verb": "requests",
"chars": 2004,
"bytes": 2004,
"tokens": 501,
"duration_ms": 889,
"round_trip_ms": 910,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 313
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "snapshot",
"chars": 1193,
"bytes": 1193,
"tokens": 299,
"duration_ms": 969,
"round_trip_ms": 987,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 116
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-calendar": {
"reads": [
{
"verb": "availability",
"chars": 4104,
"bytes": 4104,
"tokens": 1026,
"duration_ms": 937,
"round_trip_ms": 951,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 587
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "events",
"limit": 1,
"chars": 2611,
"bytes": 2611,
"tokens": 653,
"duration_ms": 1202,
"round_trip_ms": 1219,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 468
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "events",
"limit": 5,
"chars": 2611,
"bytes": 2611,
"tokens": 653,
"duration_ms": 1138,
"round_trip_ms": 1162,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 468
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "events",
"limit": 25,
"chars": 2612,
"bytes": 2612,
"tokens": 653,
"duration_ms": 1070,
"round_trip_ms": 1095,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 468
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 1,
"chars": 2603,
"bytes": 2603,
"tokens": 651,
"duration_ms": 1079,
"round_trip_ms": 1100,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 466
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 2603,
"bytes": 2603,
"tokens": 651,
"duration_ms": 1068,
"round_trip_ms": 1084,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 466
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 2604,
"bytes": 2604,
"tokens": 651,
"duration_ms": 1332,
"round_trip_ms": 1352,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 466
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "today",
"chars": 2593,
"bytes": 2593,
"tokens": 649,
"duration_ms": 1179,
"round_trip_ms": 1195,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 467
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "events",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: events names no argument \"count\". Its grammar is: days? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: days? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-channel-contract": {
"reads": [
{
"verb": "certs",
"chars": 1173,
"bytes": 1173,
"tokens": 294,
"duration_ms": 322,
"round_trip_ms": 340,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 121
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 1,
"chars": 1189,
"bytes": 1189,
"tokens": 298,
"duration_ms": 325,
"round_trip_ms": 477,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 123
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1189,
"bytes": 1189,
"tokens": 298,
"duration_ms": 314,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 123
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1190,
"bytes": 1190,
"tokens": 298,
"duration_ms": 365,
"round_trip_ms": 391,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 123
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "schema",
"chars": 2598,
"bytes": 2598,
"tokens": 650,
"duration_ms": 319,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 785
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "validate",
"chars": 2626,
"bytes": 2626,
"tokens": 657,
"duration_ms": 290,
"round_trip_ms": 315,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 384
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "validate",
"outcome": "refused",
"code": "missing_argument",
"message": "validate reads one Event as JSON on stdin: `npx tsx api.ts validate < event.json`",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-cleanshot": {
"reads": [
{
"verb": "history",
"limit": 1,
"chars": 1175,
"bytes": 1175,
"tokens": 294,
"duration_ms": 318,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 122
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "ran"
},
{
"verb": "history",
"limit": 5,
"chars": 1175,
"bytes": 1175,
"tokens": 294,
"duration_ms": 316,
"round_trip_ms": 331,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 122
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "ran"
},
{
"verb": "history",
"limit": 25,
"chars": 1176,
"bytes": 1176,
"tokens": 294,
"duration_ms": 308,
"round_trip_ms": 321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 122
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "ran"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "history",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: history names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-client-orbiter": {
"reads": [
{
"verb": "catchup",
"chars": 6145,
"bytes": 6145,
"tokens": 1537,
"duration_ms": 1359,
"round_trip_ms": 1380,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1699
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "contacts",
"chars": 121706,
"bytes": 121706,
"tokens": 30427,
"duration_ms": 1664,
"round_trip_ms": 1731,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2099
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-client-ray": {
"reads": [
{
"verb": "status",
"chars": 3054,
"bytes": 3054,
"tokens": 764,
"duration_ms": 325,
"round_trip_ms": 340,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 654
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-client-scott": {
"reads": [
{
"verb": "contacts",
"chars": 121696,
"bytes": 121696,
"tokens": 30424,
"duration_ms": 1451,
"round_trip_ms": 1527,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2097
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-client-total": {
"reads": [
{
"verb": "catchup",
"chars": 3748,
"bytes": 3748,
"tokens": 937,
"duration_ms": 1779,
"round_trip_ms": 1797,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 886
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "contacts",
"chars": 121539,
"bytes": 121539,
"tokens": 30385,
"duration_ms": 1500,
"round_trip_ms": 1570,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2097
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-clients": {
"reads": [
{
"verb": "health",
"chars": 1252,
"bytes": 1252,
"tokens": 313,
"duration_ms": 1235,
"round_trip_ms": 1256,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 163
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 1,
"chars": 121476,
"bytes": 121476,
"tokens": 30369,
"duration_ms": 1301,
"round_trip_ms": 1370,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 121476,
"bytes": 121476,
"tokens": 30369,
"duration_ms": 1300,
"round_trip_ms": 1380,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 121477,
"bytes": 121477,
"tokens": 30370,
"duration_ms": 1483,
"round_trip_ms": 1570,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-content": {
"reads": [
{
"verb": "atoms",
"chars": 2193,
"bytes": 2193,
"tokens": 549,
"duration_ms": 1188,
"round_trip_ms": 1202,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 372
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "status",
"chars": 7782,
"bytes": 7782,
"tokens": 1946,
"duration_ms": 349,
"round_trip_ms": 371,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-corpus": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1145,
"bytes": 1145,
"tokens": 287,
"duration_ms": 320,
"round_trip_ms": 338,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 106
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1145,
"bytes": 1145,
"tokens": 287,
"duration_ms": 309,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 106
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1146,
"bytes": 1146,
"tokens": 287,
"duration_ms": 309,
"round_trip_ms": 326,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 106
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: year? month? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-course": {
"reads": [
{
"verb": "modules",
"chars": 2882,
"bytes": 2882,
"tokens": 721,
"duration_ms": 309,
"round_trip_ms": 381,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 958
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-data-hygiene": {
"reads": [
{
"verb": "prompt",
"chars": 4405,
"bytes": 4405,
"tokens": 1102,
"duration_ms": 323,
"round_trip_ms": 339,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1698
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-database": {
"reads": [
{
"verb": "tables",
"chars": 1430,
"bytes": 1430,
"tokens": 358,
"duration_ms": 0,
"round_trip_ms": 15,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "tables",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-database declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-deploy": {
"reads": [
{
"verb": "status",
"chars": 2780,
"bytes": 2780,
"tokens": 695,
"duration_ms": 630,
"round_trip_ms": 646,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 517
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "deploy-receipt",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-docs": {
"reads": [
{
"verb": "me",
"chars": 2043,
"bytes": 2043,
"tokens": 511,
"duration_ms": 789,
"round_trip_ms": 807,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 312
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-dom-cartographer": {
"reads": [
{
"verb": "verify",
"chars": 1804,
"bytes": 1804,
"tokens": 451,
"duration_ms": 325,
"round_trip_ms": 338,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 244
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "log",
"provided": "msg",
"expected": "message",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-email": {
"reads": [],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
2
],
"near_miss_skipped": [
{
"verb": "draft",
"provided": "text",
"expected": "body",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "draft",
"provided": "recipient",
"expected": "to",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "text",
"expected": "body",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-faces": {
"reads": [
{
"verb": "faces",
"chars": 14038,
"bytes": 14038,
"tokens": 3510,
"duration_ms": 416,
"round_trip_ms": 439,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "grammar",
"chars": 18506,
"bytes": 18506,
"tokens": 4627,
"duration_ms": 344,
"round_trip_ms": 393,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-freshbooks": {
"reads": [
{
"verb": "clients",
"limit": 1,
"chars": 3129,
"bytes": 3129,
"tokens": 783,
"duration_ms": 652,
"round_trip_ms": 665,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 639
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "clients",
"limit": 5,
"chars": 5721,
"bytes": 5721,
"tokens": 1431,
"duration_ms": 697,
"round_trip_ms": 717,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1492
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "clients",
"limit": 25,
"chars": 12106,
"bytes": 12106,
"tokens": 3027,
"duration_ms": 626,
"round_trip_ms": 660,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2093
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "invoices",
"limit": 1,
"chars": 3412,
"bytes": 3412,
"tokens": 853,
"duration_ms": 995,
"round_trip_ms": 1014,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 731
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "invoices",
"limit": 5,
"chars": 7308,
"bytes": 7308,
"tokens": 1827,
"duration_ms": 1043,
"round_trip_ms": 1063,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2011
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "invoices",
"limit": 25,
"chars": 19122,
"bytes": 19122,
"tokens": 4781,
"duration_ms": 1645,
"round_trip_ms": 1682,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2095
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 1,
"chars": 3398,
"bytes": 3398,
"tokens": 850,
"duration_ms": 1058,
"round_trip_ms": 1082,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 728
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 7292,
"bytes": 7292,
"tokens": 1823,
"duration_ms": 1116,
"round_trip_ms": 1134,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2007
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 19104,
"bytes": 19104,
"tokens": 4776,
"duration_ms": 980,
"round_trip_ms": 1011,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2090
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "time-entries",
"limit": 1,
"chars": 4286,
"bytes": 4286,
"tokens": 1072,
"duration_ms": 869,
"round_trip_ms": 893,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1134
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "time-entries",
"limit": 5,
"chars": 10001,
"bytes": 10001,
"tokens": 2501,
"duration_ms": 745,
"round_trip_ms": 771,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2098
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "time-entries",
"limit": 25,
"chars": 33922,
"bytes": 33922,
"tokens": 8481,
"duration_ms": 805,
"round_trip_ms": 853,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2098
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "expenses",
"limit": 1,
"chars": 4824,
"bytes": 4824,
"tokens": 1206,
"duration_ms": 796,
"round_trip_ms": 816,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1309
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "expenses",
"limit": 5,
"chars": 14909,
"bytes": 14909,
"tokens": 3728,
"duration_ms": 699,
"round_trip_ms": 735,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2094
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "expenses",
"limit": 25,
"chars": 44698,
"bytes": 44698,
"tokens": 11175,
"duration_ms": 784,
"round_trip_ms": 843,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2094
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "clients",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: clients names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "invoices",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: invoices names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "time-entries",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: time-entries names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "expenses",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: expenses names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-gateway": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1385,
"bytes": 1385,
"tokens": 347,
"duration_ms": 569,
"round_trip_ms": 595,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 223
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 2367,
"bytes": 2367,
"tokens": 592,
"duration_ms": 435,
"round_trip_ms": 462,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 708
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 2368,
"bytes": 2368,
"tokens": 592,
"duration_ms": 485,
"round_trip_ms": 511,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 708
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-github": {
"reads": [
{
"verb": "repos",
"limit": 1,
"chars": 1217,
"bytes": 1217,
"tokens": 305,
"duration_ms": 741,
"round_trip_ms": 758,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 139
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "repos",
"limit": 5,
"chars": 1674,
"bytes": 1674,
"tokens": 419,
"duration_ms": 760,
"round_trip_ms": 778,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 355
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "repos",
"limit": 25,
"chars": 3910,
"bytes": 3910,
"tokens": 978,
"duration_ms": 1107,
"round_trip_ms": 1124,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1412
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "repos",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: repos names no argument \"count\". Its grammar is: org? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "comment",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-gmail": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 4197,
"bytes": 4197,
"tokens": 1050,
"duration_ms": 1386,
"round_trip_ms": 1407,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1005
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "gmail-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 9779,
"bytes": 9779,
"tokens": 2445,
"duration_ms": 1816,
"round_trip_ms": 1845,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "gmail-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 30337,
"bytes": 30337,
"tokens": 7585,
"duration_ms": 5886,
"round_trip_ms": 5924,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "gmail-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: limit? query? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "draft",
"provided": "body",
"expected": "text",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "draft",
"provided": "recipient",
"expected": "to",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "reply",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-hands": {
"reads": [
{
"verb": "census",
"chars": 2931,
"bytes": 2931,
"tokens": 733,
"duration_ms": 333,
"round_trip_ms": 349,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "census",
"outcome": "refused",
"code": "service_unavailable",
"message": "census unavailable: http://127.0.0.1:3147/hands did not answer (fetch failed). The census lives in the Snappy daemon; start it, or ask the person whose Mac this is.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-hooks": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1258,
"bytes": 1258,
"tokens": 315,
"duration_ms": 307,
"round_trip_ms": 324,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 160
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 2051,
"bytes": 2051,
"tokens": 513,
"duration_ms": 303,
"round_trip_ms": 328,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 541
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 3461,
"bytes": 3461,
"tokens": 866,
"duration_ms": 309,
"round_trip_ms": 325,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1217
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "status",
"chars": 1180,
"bytes": 1180,
"tokens": 295,
"duration_ms": 304,
"round_trip_ms": 321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 129
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-image": {
"reads": [
{
"verb": "logos",
"chars": 24697,
"bytes": 24697,
"tokens": 6175,
"duration_ms": 321,
"round_trip_ms": 378,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "imageset-grid",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "generate",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-imessage": {
"reads": [
{
"verb": "recent",
"limit": 1,
"chars": 1812,
"bytes": 1812,
"tokens": 453,
"duration_ms": 323,
"round_trip_ms": 358,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 260
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "recent",
"limit": 5,
"chars": 1812,
"bytes": 1812,
"tokens": 453,
"duration_ms": 291,
"round_trip_ms": 311,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 260
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "recent",
"limit": 25,
"chars": 1813,
"bytes": 1813,
"tokens": 454,
"duration_ms": 288,
"round_trip_ms": 309,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 260
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "contacts",
"limit": 1,
"chars": 1822,
"bytes": 1822,
"tokens": 456,
"duration_ms": 290,
"round_trip_ms": 319,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 262
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "contacts",
"limit": 5,
"chars": 1822,
"bytes": 1822,
"tokens": 456,
"duration_ms": 337,
"round_trip_ms": 350,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 262
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "contacts",
"limit": 25,
"chars": 1823,
"bytes": 1823,
"tokens": 456,
"duration_ms": 302,
"round_trip_ms": 321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 262
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "recent",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "recent",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "recent",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "contacts",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "contacts",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "contacts",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "recent",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: recent names no argument \"count\". Its grammar is: contact? and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "contacts",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: contacts names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-inbound": {
"reads": [
{
"verb": "slack-joins",
"chars": 1168,
"bytes": 1168,
"tokens": 292,
"duration_ms": 596,
"round_trip_ms": 614,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 117
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-inbox-sweep": {
"reads": [
{
"verb": "gmail-personal",
"chars": 2281,
"bytes": 2281,
"tokens": 571,
"duration_ms": 577,
"round_trip_ms": 597,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 385
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "gmail-work",
"chars": 25783,
"bytes": 25783,
"tokens": 6446,
"duration_ms": 7700,
"round_trip_ms": 7736,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2098
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "slack",
"chars": 2008,
"bytes": 2008,
"tokens": 502,
"duration_ms": 2084,
"round_trip_ms": 2143,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 313
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "gmail-personal",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send-skool",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "send-slack",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-infra": {
"reads": [],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-knowledge": {
"reads": [
{
"verb": "birthdays",
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "contacts",
"chars": 1435,
"bytes": 1435,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "dormant",
"chars": 1434,
"bytes": 1434,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "entities",
"chars": 1435,
"bytes": 1435,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "interactions",
"chars": 1439,
"bytes": 1439,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "resolve",
"chars": 1434,
"bytes": 1434,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "birthdays",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "contacts",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "dormant",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "entities",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "interactions",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "resolve",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-krisp": {
"reads": [
{
"verb": "fetch-meetings",
"limit": 1,
"chars": 10026,
"bytes": 10026,
"tokens": 2507,
"duration_ms": 3540,
"round_trip_ms": 3566,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2096
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "fetch-meetings",
"limit": 5,
"chars": 10026,
"bytes": 10026,
"tokens": 2507,
"duration_ms": 2364,
"round_trip_ms": 2393,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2096
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "fetch-meetings",
"limit": 25,
"chars": 10029,
"bytes": 10029,
"tokens": 2508,
"duration_ms": 8009,
"round_trip_ms": 8043,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2096
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "fetch-action-items",
"limit": 1,
"chars": 4312,
"bytes": 4312,
"tokens": 1078,
"duration_ms": 3052,
"round_trip_ms": 3075,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1039
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "krisp-actions",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "fetch-action-items",
"limit": 5,
"chars": 9174,
"bytes": 9174,
"tokens": 2294,
"duration_ms": 10515,
"round_trip_ms": 10548,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2101
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "krisp-actions",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "fetch-action-items",
"limit": 25,
"chars": 30793,
"bytes": 30793,
"tokens": 7699,
"duration_ms": 1069,
"round_trip_ms": 1148,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2100
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "krisp-actions",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "fetch-meetings",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: fetch-meetings names no argument \"count\". Its grammar is: after-date? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "fetch-action-items",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: fetch-action-items names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-libretto": {
"reads": [
{
"verb": "lessons",
"chars": 3263,
"bytes": 3263,
"tokens": 816,
"duration_ms": 344,
"round_trip_ms": 372,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 693
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-linkedin": {
"reads": [
{
"verb": "profile",
"chars": 2739,
"bytes": 2739,
"tokens": 685,
"duration_ms": 979,
"round_trip_ms": 1134,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 511
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "linkedin-profile",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "scheduled",
"chars": 1731,
"bytes": 1731,
"tokens": 433,
"duration_ms": 745,
"round_trip_ms": 780,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 193
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "linkedin-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "token-status",
"chars": 1372,
"bytes": 1372,
"tokens": 343,
"duration_ms": 340,
"round_trip_ms": 374,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 213
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "schedule",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "comment",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "draft-patch",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-maintenance": {
"reads": [
{
"verb": "all",
"limit": 1,
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 331
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "all",
"limit": 5,
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 17,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 331
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "all",
"limit": 25,
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 331
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "all",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "all",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "all",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "all",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED retired_backend: snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.\nFIX: Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host "
}
],
"search_ranks": [
1
]
},
"snappy-mastermind-model": {
"reads": [
{
"verb": "funnels",
"chars": 4318,
"bytes": 4318,
"tokens": 1080,
"duration_ms": 317,
"round_trip_ms": 337,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1084
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "ladders",
"chars": 5826,
"bytes": 5826,
"tokens": 1457,
"duration_ms": 312,
"round_trip_ms": 335,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1577
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "math",
"chars": 5749,
"bytes": 5749,
"tokens": 1438,
"duration_ms": 302,
"round_trip_ms": 330,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1539
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "sales",
"chars": 4094,
"bytes": 4094,
"tokens": 1024,
"duration_ms": 305,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 993
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "team",
"chars": 2809,
"bytes": 2809,
"tokens": 703,
"duration_ms": 303,
"round_trip_ms": 316,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 573
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "topics",
"chars": 3112,
"bytes": 3112,
"tokens": 778,
"duration_ms": 306,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 674
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "transcripts",
"chars": 9354,
"bytes": 9354,
"tokens": 2339,
"duration_ms": 314,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-mine": {
"reads": [
{
"verb": "files",
"limit": 1,
"chars": 1237,
"bytes": 1237,
"tokens": 310,
"duration_ms": 314,
"round_trip_ms": 334,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 151
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "files",
"limit": 5,
"chars": 1566,
"bytes": 1566,
"tokens": 392,
"duration_ms": 305,
"round_trip_ms": 326,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 311
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "files",
"limit": 25,
"chars": 2385,
"bytes": 2385,
"tokens": 597,
"duration_ms": 309,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 709
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "manifest",
"chars": 2374,
"bytes": 2374,
"tokens": 594,
"duration_ms": 291,
"round_trip_ms": 308,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 372
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "meetings-by",
"limit": 1,
"chars": 1331,
"bytes": 1331,
"tokens": 333,
"duration_ms": 307,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 94
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "meetings-by",
"limit": 5,
"chars": 1331,
"bytes": 1331,
"tokens": 333,
"duration_ms": 305,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 94
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "meetings-by",
"limit": 25,
"chars": 1332,
"bytes": 1332,
"tokens": 333,
"duration_ms": 310,
"round_trip_ms": 328,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 94
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "pending",
"limit": 1,
"chars": 2480,
"bytes": 2480,
"tokens": 620,
"duration_ms": 333,
"round_trip_ms": 351,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "pending",
"limit": 5,
"chars": 2480,
"bytes": 2480,
"tokens": 620,
"duration_ms": 291,
"round_trip_ms": 305,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "pending",
"limit": 25,
"chars": 2481,
"bytes": 2481,
"tokens": 621,
"duration_ms": 284,
"round_trip_ms": 302,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "sources",
"chars": 2469,
"bytes": 2469,
"tokens": 618,
"duration_ms": 285,
"round_trip_ms": 300,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "manifest",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "pending",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "pending",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "pending",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "sources",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "files",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: files names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "meetings-by",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: meetings-by names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "pending",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: pending names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-nightshift": {
"reads": [
{
"verb": "bar",
"chars": 3087,
"bytes": 3087,
"tokens": 772,
"duration_ms": 308,
"round_trip_ms": 327,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 454
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "board",
"chars": 11498,
"bytes": 11498,
"tokens": 2875,
"duration_ms": 345,
"round_trip_ms": 377,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 492
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "log",
"chars": 1131,
"bytes": 1131,
"tokens": 283,
"duration_ms": 306,
"round_trip_ms": 331,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 105
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "preflight",
"chars": 3277,
"bytes": 3277,
"tokens": 820,
"duration_ms": 815,
"round_trip_ms": 834,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 379
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "report",
"chars": 9609,
"bytes": 9609,
"tokens": 2403,
"duration_ms": 323,
"round_trip_ms": 344,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 493
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "status",
"chars": 2421,
"bytes": 2421,
"tokens": 606,
"duration_ms": 411,
"round_trip_ms": 428,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 441
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "bar",
"outcome": "refused",
"code": "not_found",
"message": "THE-WORLD-CLASS-BAR.md is not at /Users/robert[name]/Projects/snappy-os-app/THE-WORLD-CLASS-BAR.md; `bar` derives every row from it. Run from the repo that holds it, or set SNAPPY_NIGHTSHIFT_REPO.",
"fix": "List the collection first and name an identifier the list returned.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "board",
"outcome": "refused",
"message": "the hand's own words name a refusal, with exit 0 \u2014 a zero exit is not a success"
},
{
"verb": "preflight",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "report",
"outcome": "refused",
"message": "the hand's own words name a refusal, with exit 0 \u2014 a zero exit is not a success"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-notion": {
"reads": [
{
"verb": "me",
"chars": 3386,
"bytes": 3386,
"tokens": 847,
"duration_ms": 593,
"round_trip_ms": 618,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 314
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "append",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-offer": {
"reads": [
{
"verb": "icp",
"chars": 11041,
"bytes": 11041,
"tokens": 2761,
"duration_ms": 321,
"round_trip_ms": 347,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2084
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "offer",
"chars": 5715,
"bytes": 5715,
"tokens": 1429,
"duration_ms": 305,
"round_trip_ms": 323,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "pricing",
"chars": 10887,
"bytes": 10887,
"tokens": 2722,
"duration_ms": 309,
"round_trip_ms": 334,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-ops": {
"reads": [
{
"verb": "actions",
"chars": 3906,
"bytes": 3906,
"tokens": 977,
"duration_ms": 344,
"round_trip_ms": 369,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 929
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "catchup",
"chars": 2463,
"bytes": 2463,
"tokens": 616,
"duration_ms": 937,
"round_trip_ms": 953,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 370
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "diff-machine",
"chars": 1533,
"bytes": 1533,
"tokens": 384,
"duration_ms": 307,
"round_trip_ms": 324,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 320
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "fingerprint",
"chars": 51984,
"bytes": 51984,
"tokens": 12996,
"duration_ms": 421,
"round_trip_ms": 461,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2090
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "ladder",
"chars": 3018,
"bytes": 3018,
"tokens": 755,
"duration_ms": 407,
"round_trip_ms": 423,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1028
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "recipes",
"chars": 2466,
"bytes": 2466,
"tokens": 617,
"duration_ms": 947,
"round_trip_ms": 961,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 370
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "snapshot",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 18696,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
},
{
"verb": "status",
"chars": 1501,
"bytes": 1501,
"tokens": 376,
"duration_ms": 356,
"round_trip_ms": 428,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 287
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "story",
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 313,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 293
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "trend",
"chars": 1323,
"bytes": 1323,
"tokens": 331,
"duration_ms": 335,
"round_trip_ms": 359,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 200
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "catchup",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "diff-machine",
"outcome": "failed",
"message": "the hand exited 2"
},
{
"verb": "recipes",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "story",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "remind",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-os-operator": {
"reads": [
{
"verb": "approvals",
"chars": 2883,
"bytes": 2883,
"tokens": 721,
"duration_ms": 335,
"round_trip_ms": 356,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 380
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "deploy-truth",
"chars": 2895,
"bytes": 2895,
"tokens": 724,
"duration_ms": 328,
"round_trip_ms": 371,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 383
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "health",
"chars": 2871,
"bytes": 2871,
"tokens": 718,
"duration_ms": 325,
"round_trip_ms": 350,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 377
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "runs",
"limit": 1,
"chars": 2877,
"bytes": 2877,
"tokens": 720,
"duration_ms": 325,
"round_trip_ms": 341,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "runs",
"limit": 5,
"chars": 2877,
"bytes": 2877,
"tokens": 720,
"duration_ms": 321,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "runs",
"limit": 25,
"chars": 2878,
"bytes": 2878,
"tokens": 720,
"duration_ms": 330,
"round_trip_ms": 349,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "approvals",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "deploy-truth",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "health",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "runs",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "runs",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "runs",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "runs",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: runs names no argument \"count\". Its grammar is: script-id? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-outbound": {
"reads": [
{
"verb": "status",
"chars": 1454,
"bytes": 1454,
"tokens": 364,
"duration_ms": 326,
"round_trip_ms": 343,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 132
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-pipeline": {
"reads": [
{
"verb": "batch-overview",
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 21,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "crash-patterns",
"limit": 1,
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 29,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "crash-patterns",
"limit": 5,
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "crash-patterns",
"limit": 25,
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 17,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "queue-health",
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 16,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "stuck-analysis",
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "batch-overview",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "crash-patterns",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "crash-patterns",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "crash-patterns",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "queue-health",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "stuck-analysis",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "crash-patterns",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED retired_backend: snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.\nFIX: Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host ans"
}
],
"search_ranks": [
1
]
},
"snappy-playbook": {
"reads": [
{
"verb": "week",
"chars": 2119,
"bytes": 2119,
"tokens": 530,
"duration_ms": 304,
"round_trip_ms": 318,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 281
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "weeks",
"chars": 4533,
"bytes": 4533,
"tokens": 1134,
"duration_ms": 359,
"round_trip_ms": 375,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1759
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "week",
"outcome": "refused",
"code": "missing_argument",
"message": "week needs <1-6>; contract slice week.args = [number]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-positioning": {
"reads": [
{
"verb": "banned",
"chars": 5088,
"bytes": 5088,
"tokens": 1272,
"duration_ms": 312,
"round_trip_ms": 333,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1970
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "tone",
"chars": 2565,
"bytes": 2565,
"tokens": 642,
"duration_ms": 308,
"round_trip_ms": 327,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 806
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-publish": {
"reads": [
{
"verb": "deploys",
"chars": 2594,
"bytes": 2594,
"tokens": 649,
"duration_ms": 600,
"round_trip_ms": 617,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 374
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "deploys",
"outcome": "refused",
"code": "credential_scope_denied",
"message": "[snappy-publish] VERCEL_TOKEN needs authorization (HTTP 403)",
"fix": "Re-grant the credential with the scope the verb names, or call the verb whose road the grant covers.",
"contract_slice": "requires"
}
],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-remotion": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1131,
"bytes": 1131,
"tokens": 283,
"duration_ms": 334,
"round_trip_ms": 352,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 98
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1231,
"bytes": 1231,
"tokens": 308,
"duration_ms": 316,
"round_trip_ms": 331,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 144
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1329,
"bytes": 1329,
"tokens": 333,
"duration_ms": 315,
"round_trip_ms": 330,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 189
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-report-publish": {
"reads": [
{
"verb": "style",
"chars": 1812,
"bytes": 1812,
"tokens": 453,
"duration_ms": 317,
"round_trip_ms": 339,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 441
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-sales": {
"reads": [
{
"verb": "clients",
"limit": 1,
"chars": 10484,
"bytes": 10484,
"tokens": 2621,
"duration_ms": 1391,
"round_trip_ms": 1413,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "clients",
"limit": 5,
"chars": 16454,
"bytes": 16454,
"tokens": 4114,
"duration_ms": 1513,
"round_trip_ms": 1539,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "clients",
"limit": 25,
"chars": 47414,
"bytes": 47414,
"tokens": 11854,
"duration_ms": 1367,
"round_trip_ms": 1415,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "leads",
"chars": 121658,
"bytes": 121658,
"tokens": 30415,
"duration_ms": 1422,
"round_trip_ms": 1489,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2087
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "clients",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: clients names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-scheduling": {
"reads": [
{
"verb": "available",
"chars": 4100,
"bytes": 4100,
"tokens": 1025,
"duration_ms": 1022,
"round_trip_ms": 1041,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 587
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-sensors": {
"reads": [
{
"verb": "dump",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 35292,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
},
{
"verb": "stale",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 35160,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-settings": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1193,
"bytes": 1193,
"tokens": 299,
"duration_ms": 333,
"round_trip_ms": 366,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 128
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1390,
"bytes": 1390,
"tokens": 348,
"duration_ms": 321,
"round_trip_ms": 339,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 222
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 2309,
"bytes": 2309,
"tokens": 578,
"duration_ms": 323,
"round_trip_ms": 338,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 661
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-skool": {
"reads": [
{
"verb": "health",
"chars": 2706,
"bytes": 2706,
"tokens": 677,
"duration_ms": 858,
"round_trip_ms": 903,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 615
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "me",
"chars": 3449,
"bytes": 3449,
"tokens": 863,
"duration_ms": 657,
"round_trip_ms": 679,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 341
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "group",
"chars": 2274,
"bytes": 2274,
"tokens": 569,
"duration_ms": 509,
"round_trip_ms": 530,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 461
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "admins",
"chars": 2735,
"bytes": 2735,
"tokens": 684,
"duration_ms": 495,
"round_trip_ms": 514,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 617
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "levels",
"chars": 4728,
"bytes": 4728,
"tokens": 1182,
"duration_ms": 498,
"round_trip_ms": 520,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1273
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "courses",
"chars": 43477,
"bytes": 43477,
"tokens": 10870,
"duration_ms": 520,
"round_trip_ms": 572,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "labels",
"chars": 7005,
"bytes": 7005,
"tokens": 1752,
"duration_ms": 508,
"round_trip_ms": 548,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2027
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "links",
"chars": 1849,
"bytes": 1849,
"tokens": 463,
"duration_ms": 498,
"round_trip_ms": 515,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 326
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "posts",
"chars": 41504,
"bytes": 41504,
"tokens": 10376,
"duration_ms": 885,
"round_trip_ms": 934,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "members",
"chars": 15929,
"bytes": 15929,
"tokens": 3983,
"duration_ms": 720,
"round_trip_ms": 756,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "modules",
"chars": 34144,
"bytes": 34144,
"tokens": 8536,
"duration_ms": 635,
"round_trip_ms": 694,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "leaderboards",
"chars": 35409,
"bytes": 35409,
"tokens": 8853,
"duration_ms": 628,
"round_trip_ms": 675,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2093
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "calendar",
"chars": 16002,
"bytes": 16002,
"tokens": 4001,
"duration_ms": 659,
"round_trip_ms": 694,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "chats",
"limit": 1,
"chars": 7541,
"bytes": 7541,
"tokens": 1886,
"duration_ms": 507,
"round_trip_ms": 533,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "chats",
"limit": 5,
"chars": 21358,
"bytes": 21358,
"tokens": 5340,
"duration_ms": 507,
"round_trip_ms": 540,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "chats",
"limit": 25,
"chars": 56957,
"bytes": 56957,
"tokens": 14240,
"duration_ms": 522,
"round_trip_ms": 588,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "questions",
"chars": 35726,
"bytes": 35726,
"tokens": 8932,
"duration_ms": 689,
"round_trip_ms": 746,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2090
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "brief",
"chars": 60967,
"bytes": 60967,
"tokens": 15242,
"duration_ms": 698,
"round_trip_ms": 760,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "chats",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: chats names no argument \"count\". Its grammar is: limit? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "text",
"expected": "body",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "comment",
"provided": "text",
"expected": "body",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "dm",
"provided": "text",
"expected": "body",
"reason": "send effect \u2014 never called by the probe"
}
],
"safety_skipped": [
{
"verb": "post-detail",
"effect": "read",
"reason": "declared \"read\" but the name says it reaches a person or spends \u2014 never called by the probe"
}
]
},
"snappy-slack": {
"reads": [
{
"verb": "channels",
"chars": 3936,
"bytes": 3936,
"tokens": 984,
"duration_ms": 605,
"round_trip_ms": 625,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 925
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "slack-channels",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "reply",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "edit",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-statechange": {
"reads": [
{
"verb": "health",
"chars": 2222,
"bytes": 2222,
"tokens": 556,
"duration_ms": 801,
"round_trip_ms": 822,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 431
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "unread",
"chars": 2397,
"bytes": 2397,
"tokens": 600,
"duration_ms": 702,
"round_trip_ms": 724,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 488
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "posts",
"chars": 8727,
"bytes": 8727,
"tokens": 2182,
"duration_ms": 748,
"round_trip_ms": 777,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2092
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "statechange-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "stage-post",
"provided": "text",
"expected": "body",
"reason": "write-reversible effect \u2014 never called by the probe"
},
{
"verb": "stage-message",
"provided": "text",
"expected": "body",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-telegram": {
"reads": [
{
"verb": "read",
"limit": 1,
"chars": 2283,
"bytes": 2283,
"tokens": 571,
"duration_ms": 749,
"round_trip_ms": 777,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 364
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 5,
"chars": 2285,
"bytes": 2285,
"tokens": 572,
"duration_ms": 4032,
"round_trip_ms": 4054,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 365
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 25,
"chars": 2284,
"bytes": 2284,
"tokens": 571,
"duration_ms": 736,
"round_trip_ms": 805,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 364
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "messages",
"limit": 1,
"chars": 2301,
"bytes": 2301,
"tokens": 576,
"duration_ms": 3774,
"round_trip_ms": 3793,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 369
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "messages",
"limit": 5,
"chars": 2299,
"bytes": 2299,
"tokens": 575,
"duration_ms": 722,
"round_trip_ms": 742,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 368
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "messages",
"limit": 25,
"chars": 2302,
"bytes": 2302,
"tokens": 576,
"duration_ms": 3748,
"round_trip_ms": 3774,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 369
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "updates",
"limit": 1,
"chars": 2417,
"bytes": 2417,
"tokens": 605,
"duration_ms": 861,
"round_trip_ms": 882,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 406
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "updates",
"limit": 5,
"chars": 2419,
"bytes": 2419,
"tokens": 605,
"duration_ms": 3891,
"round_trip_ms": 3913,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 407
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "updates",
"limit": 25,
"chars": 2418,
"bytes": 2418,
"tokens": 605,
"duration_ms": 880,
"round_trip_ms": 900,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 406
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "me",
"chars": 4620,
"bytes": 4620,
"tokens": 1155,
"duration_ms": 741,
"round_trip_ms": 762,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 725
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "decisions",
"chars": 1144,
"bytes": 1144,
"tokens": 286,
"duration_ms": 3795,
"round_trip_ms": 3814,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 97
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "read",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: read names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "messages",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: messages names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "updates",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: updates names no argument \"count\". Its grammar is: and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "keyboard",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "edit",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
},
{
"verb": "progress",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-testimonials": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1167,
"bytes": 1167,
"tokens": 292,
"duration_ms": 1117,
"round_trip_ms": 1160,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 114
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1165,
"bytes": 1165,
"tokens": 292,
"duration_ms": 700,
"round_trip_ms": 725,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 113
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1166,
"bytes": 1166,
"tokens": 292,
"duration_ms": 751,
"round_trip_ms": 767,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 113
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: status? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-thumbnails": {
"reads": [
{
"verb": "audit",
"limit": 1,
"chars": 2115,
"bytes": 2115,
"tokens": 529,
"duration_ms": 1293,
"round_trip_ms": 1321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 411
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "audit",
"limit": 5,
"chars": 2115,
"bytes": 2115,
"tokens": 529,
"duration_ms": 1861,
"round_trip_ms": 1877,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 411
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "audit",
"limit": 25,
"chars": 1826,
"bytes": 1826,
"tokens": 457,
"duration_ms": 3355,
"round_trip_ms": 3375,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "audit",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "audit",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: audit names no argument \"count\". Its grammar is: channel? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "generate",
"provided": "limit",
"expected": "count",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-tool-design": {
"reads": [
{
"verb": "lint",
"limit": 1,
"chars": 2484,
"bytes": 2484,
"tokens": 621,
"duration_ms": 333,
"round_trip_ms": 375,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 324
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "lint",
"limit": 5,
"chars": 2484,
"bytes": 2484,
"tokens": 621,
"duration_ms": 322,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 324
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "lint",
"limit": 25,
"chars": 2485,
"bytes": 2485,
"tokens": 622,
"duration_ms": 321,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 324
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "probe",
"chars": 3144,
"bytes": 3144,
"tokens": 786,
"duration_ms": 395,
"round_trip_ms": 412,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 457
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "render",
"chars": 1819,
"bytes": 1819,
"tokens": 455,
"duration_ms": 325,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 352
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "loop",
"chars": 3140,
"bytes": 3140,
"tokens": 785,
"duration_ms": 329,
"round_trip_ms": 346,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 456
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "hosts",
"chars": 1859,
"bytes": 1859,
"tokens": 465,
"duration_ms": 336,
"round_trip_ms": 357,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 366
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "lint",
"outcome": "refused",
"code": "missing_argument",
"message": "lint needs <skill> or --all; contract slice lint.args = [skill?]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "lint",
"outcome": "refused",
"code": "missing_argument",
"message": "lint needs <skill> or --all; contract slice lint.args = [skill?]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "lint",
"outcome": "refused",
"code": "missing_argument",
"message": "lint needs <skill> or --all; contract slice lint.args = [skill?]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "probe",
"outcome": "refused",
"code": "missing_credential",
"message": "probe needs a bearer: set SNAPPY_RUNNER_TOKEN_FILE to a 600-mode file holding the runner token (ssh mini cat ~/.snappy-skills/token > <file>), or pass --token-file. The token is never an argument.",
"fix": "Add the named key to the environment cache. The key is named; the value is never printed.",
"contract_slice": "requires"
},
{
"verb": "render",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "loop",
"outcome": "refused",
"code": "missing_credential",
"message": "probe needs a bearer: set SNAPPY_RUNNER_TOKEN_FILE to a 600-mode file holding the runner token (ssh mini cat ~/.snappy-skills/token > <file>), or pass --token-file. The token is never an argument.",
"fix": "Add the named key to the environment cache. The key is named; the value is never printed.",
"contract_slice": "requires"
},
{
"verb": "hosts",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "lint",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: lint names no argument \"count\". Its grammar is: skill? and the flags all, summary, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-transcripts": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1160,
"bytes": 1160,
"tokens": 290,
"duration_ms": 328,
"round_trip_ms": 348,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1160,
"bytes": 1160,
"tokens": 290,
"duration_ms": 320,
"round_trip_ms": 341,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1161,
"bytes": 1161,
"tokens": 291,
"duration_ms": 320,
"round_trip_ms": 343,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: year? month? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-update": {
"reads": [
{
"verb": "commits",
"limit": 1,
"chars": 2830,
"bytes": 2830,
"tokens": 708,
"duration_ms": 721,
"round_trip_ms": 740,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 578
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "commits",
"limit": 5,
"chars": 4969,
"bytes": 4969,
"tokens": 1243,
"duration_ms": 780,
"round_trip_ms": 803,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1288
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "commits",
"limit": 25,
"chars": 13434,
"bytes": 13434,
"tokens": 3359,
"duration_ms": 823,
"round_trip_ms": 852,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "commits",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: commits names no argument \"count\". Its grammar is: repo? days? and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "notify",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-voice-control": {
"reads": [
{
"verb": "devices",
"chars": 1170,
"bytes": 1170,
"tokens": 293,
"duration_ms": 990,
"round_trip_ms": 1020,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 118
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "permissions",
"chars": 1681,
"bytes": 1681,
"tokens": 421,
"duration_ms": 407,
"round_trip_ms": 482,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 368
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "pick",
"chars": 1148,
"bytes": 1148,
"tokens": 287,
"duration_ms": 746,
"round_trip_ms": 785,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-walkthrough": {
"reads": [
{
"verb": "status",
"chars": 1235,
"bytes": 1235,
"tokens": 309,
"duration_ms": 339,
"round_trip_ms": 383,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 128
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-website": {
"reads": [
{
"verb": "deploys",
"chars": 2921,
"bytes": 2921,
"tokens": 731,
"duration_ms": 624,
"round_trip_ms": 637,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 374
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "health",
"chars": 1174,
"bytes": 1174,
"tokens": 294,
"duration_ms": 585,
"round_trip_ms": 608,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 125
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "deploys",
"outcome": "refused",
"code": "credential_scope_denied",
"message": "Vercel refused VERCEL_TOKEN for projectId=snappy-website (HTTP 403): {\"error\":{\"code\":\"forbidden\",\"message\":\"Not authorized\",\"invalidToken\":true}}",
"fix": "Re-grant the credential with the scope the verb names, or call the verb whose road the grant covers.",
"contract_slice": "requires"
}
],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-whatsapp": {
"reads": [
{
"verb": "read",
"limit": 1,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 333,
"round_trip_ms": 365,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 5,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 313,
"round_trip_ms": 333,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 25,
"chars": 1639,
"bytes": 1639,
"tokens": 410,
"duration_ms": 317,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 1,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 329,
"round_trip_ms": 383,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 324,
"round_trip_ms": 348,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 1639,
"bytes": 1639,
"tokens": 410,
"duration_ms": 320,
"round_trip_ms": 340,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "read",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: read names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "media",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "notify",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "msg",
"expected": "message",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-xano-dashboard": {
"reads": [
{
"verb": "logs",
"chars": 1446,
"bytes": 1446,
"tokens": 362,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 334
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "logs",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-xano-dashboard declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-xano-mcp": {
"reads": [
{
"verb": "health",
"chars": 1430,
"bytes": 1430,
"tokens": 358,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "health",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-xano-mcp declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-youtube": {
"reads": [
{
"verb": "auth",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 120031,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
},
{
"verb": "channel",
"chars": 5369,
"bytes": 5369,
"tokens": 1343,
"duration_ms": 826,
"round_trip_ms": 902,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1374
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "youtube-channel",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "recent",
"limit": 1,
"chars": 4453,
"bytes": 4453,
"tokens": 1114,
"duration_ms": 662,
"round_trip_ms": 689,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 688
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "recent",
"limit": 5,
"chars": 8905,
"bytes": 8905,
"tokens": 2227,
"duration_ms": 806,
"round_trip_ms": 830,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "recent",
"limit": 25,
"chars": 22712,
"bytes": 22712,
"tokens": 5678,
"duration_ms": 866,
"round_trip_ms": 901,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "token-status",
"chars": 1208,
"bytes": 1208,
"tokens": 302,
"duration_ms": 333,
"round_trip_ms": 351,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 136
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "recent",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: recent names no argument \"count\". Its grammar is: channel? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "reply",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
}
}
}{
"schema": "snappy-tool-design/probe@1",
"generated_at": "2026-09-09T13:07:37.594Z",
"server": {
"runner_url": "https://roberts-mac-mini.tailca7f42.ts.net/runner/mcp",
"via": "mini",
"client_created_at": "2026-09-09T13:00:28.950Z",
"published_tools": 5,
"tool_names": [
"execute",
"info",
"search",
"shell",
"update"
],
"tools_list_bytes": 5763,
"tools_list_tokens": 1441,
"tools_sorted": true,
"ttl_ms_present": false,
"cache_scope_present": false,
"prompts_count": 101,
"prompts_bytes": 28317,
"prompts_ms": 81,
"resources_count": 327,
"resources_bytes": 107511,
"resources_ms": 49,
"faces_view_listed": true,
"faces_view_bytes": 673535,
"faces_view_ms": 128,
"faces_view_mime": "text/html;profile=mcp-app",
"revision_first": "94ed3d871b-a732f69f0d25-c72403f851",
"revision_last": "94ed3d871b-a732f69f0d25-c72403f851",
"fresh_client": true,
"handshake_after_deploy": true,
"metrics": [
"accuracy",
"runtime",
"tool_call_count",
"tokens",
"errors"
],
"run": {
"accuracy": 0.7452,
"runtime": 428643,
"tool_call_count": 486,
"tokens": 576689,
"errors": 80
}
},
"skills": {
"snappy-ads": {
"reads": [
{
"verb": "metrics",
"chars": 1252,
"bytes": 1252,
"tokens": 313,
"duration_ms": 324,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 164
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "targets",
"chars": 1487,
"bytes": 1487,
"tokens": 372,
"duration_ms": 310,
"round_trip_ms": 326,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 277
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-agent-host": {
"reads": [
{
"verb": "sessions",
"chars": 3659,
"bytes": 3659,
"tokens": 915,
"duration_ms": 319,
"round_trip_ms": 335,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1204
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "prompt",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-analytics": {
"reads": [
{
"verb": "scorecard",
"chars": 7389,
"bytes": 7389,
"tokens": 1848,
"duration_ms": 5754,
"round_trip_ms": 5772,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2095
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-api-sniffer": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1142,
"bytes": 1142,
"tokens": 286,
"duration_ms": 332,
"round_trip_ms": 346,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 102
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1142,
"bytes": 1142,
"tokens": 286,
"duration_ms": 338,
"round_trip_ms": 356,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 102
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1143,
"bytes": 1143,
"tokens": 286,
"duration_ms": 339,
"round_trip_ms": 356,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 102
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: recipe-name? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-artifact-loop": {
"reads": [
{
"verb": "channels",
"chars": 2254,
"bytes": 2254,
"tokens": 564,
"duration_ms": 284,
"round_trip_ms": 301,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 746
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "choose",
"chars": 1079,
"bytes": 1079,
"tokens": 270,
"duration_ms": 313,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 172
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "parse",
"chars": 1825,
"bytes": 1825,
"tokens": 457,
"duration_ms": 303,
"round_trip_ms": 316,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 378
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "parse",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "parse",
"provided": "body",
"expected": "text",
"refusal": "nothing ran REFUSED argument_unknown: parse names no argument \"body\". Its grammar is: text?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-ax": {
"reads": [
{
"verb": "focused",
"chars": 1819,
"bytes": 1819,
"tokens": 455,
"duration_ms": 358,
"round_trip_ms": 374,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 250
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "permissions",
"chars": 1401,
"bytes": 1401,
"tokens": 351,
"duration_ms": 324,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 118
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-blog": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1137,
"bytes": 1137,
"tokens": 285,
"duration_ms": 311,
"round_trip_ms": 324,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1137,
"bytes": 1137,
"tokens": 285,
"duration_ms": 302,
"round_trip_ms": 313,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1138,
"bytes": 1138,
"tokens": 285,
"duration_ms": 303,
"round_trip_ms": 316,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-box": {
"reads": [
{
"verb": "routes",
"chars": 2828,
"bytes": 2828,
"tokens": 707,
"duration_ms": 316,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 369
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "routes",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Box server at http://10.0.0.199:8080 did not answer (fetch failed). Start it on the Mac Mini, or call a verb that reads local state.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-browse": {
"reads": [
{
"verb": "requests",
"chars": 2004,
"bytes": 2004,
"tokens": 501,
"duration_ms": 889,
"round_trip_ms": 910,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 313
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "snapshot",
"chars": 1193,
"bytes": 1193,
"tokens": 299,
"duration_ms": 969,
"round_trip_ms": 987,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 116
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-calendar": {
"reads": [
{
"verb": "availability",
"chars": 4104,
"bytes": 4104,
"tokens": 1026,
"duration_ms": 937,
"round_trip_ms": 951,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 587
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "events",
"limit": 1,
"chars": 2611,
"bytes": 2611,
"tokens": 653,
"duration_ms": 1202,
"round_trip_ms": 1219,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 468
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "events",
"limit": 5,
"chars": 2611,
"bytes": 2611,
"tokens": 653,
"duration_ms": 1138,
"round_trip_ms": 1162,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 468
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "events",
"limit": 25,
"chars": 2612,
"bytes": 2612,
"tokens": 653,
"duration_ms": 1070,
"round_trip_ms": 1095,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 468
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 1,
"chars": 2603,
"bytes": 2603,
"tokens": 651,
"duration_ms": 1079,
"round_trip_ms": 1100,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 466
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 2603,
"bytes": 2603,
"tokens": 651,
"duration_ms": 1068,
"round_trip_ms": 1084,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 466
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 2604,
"bytes": 2604,
"tokens": 651,
"duration_ms": 1332,
"round_trip_ms": 1352,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 466
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "today",
"chars": 2593,
"bytes": 2593,
"tokens": 649,
"duration_ms": 1179,
"round_trip_ms": 1195,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 467
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "calendar-week",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "events",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: events names no argument \"count\". Its grammar is: days? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: days? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-channel-contract": {
"reads": [
{
"verb": "certs",
"chars": 1173,
"bytes": 1173,
"tokens": 294,
"duration_ms": 322,
"round_trip_ms": 340,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 121
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 1,
"chars": 1189,
"bytes": 1189,
"tokens": 298,
"duration_ms": 325,
"round_trip_ms": 477,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 123
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1189,
"bytes": 1189,
"tokens": 298,
"duration_ms": 314,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 123
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1190,
"bytes": 1190,
"tokens": 298,
"duration_ms": 365,
"round_trip_ms": 391,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 123
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "schema",
"chars": 2598,
"bytes": 2598,
"tokens": 650,
"duration_ms": 319,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 785
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "validate",
"chars": 2626,
"bytes": 2626,
"tokens": 657,
"duration_ms": 290,
"round_trip_ms": 315,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 384
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "validate",
"outcome": "refused",
"code": "missing_argument",
"message": "validate reads one Event as JSON on stdin: `npx tsx api.ts validate < event.json`",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-cleanshot": {
"reads": [
{
"verb": "history",
"limit": 1,
"chars": 1175,
"bytes": 1175,
"tokens": 294,
"duration_ms": 318,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 122
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "ran"
},
{
"verb": "history",
"limit": 5,
"chars": 1175,
"bytes": 1175,
"tokens": 294,
"duration_ms": 316,
"round_trip_ms": 331,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 122
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "ran"
},
{
"verb": "history",
"limit": 25,
"chars": 1176,
"bytes": 1176,
"tokens": 294,
"duration_ms": 308,
"round_trip_ms": 321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 122
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "ran"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "history",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: history names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-client-orbiter": {
"reads": [
{
"verb": "catchup",
"chars": 6145,
"bytes": 6145,
"tokens": 1537,
"duration_ms": 1359,
"round_trip_ms": 1380,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1699
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "contacts",
"chars": 121706,
"bytes": 121706,
"tokens": 30427,
"duration_ms": 1664,
"round_trip_ms": 1731,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2099
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-client-ray": {
"reads": [
{
"verb": "status",
"chars": 3054,
"bytes": 3054,
"tokens": 764,
"duration_ms": 325,
"round_trip_ms": 340,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 654
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-client-scott": {
"reads": [
{
"verb": "contacts",
"chars": 121696,
"bytes": 121696,
"tokens": 30424,
"duration_ms": 1451,
"round_trip_ms": 1527,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2097
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-client-total": {
"reads": [
{
"verb": "catchup",
"chars": 3748,
"bytes": 3748,
"tokens": 937,
"duration_ms": 1779,
"round_trip_ms": 1797,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 886
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "contacts",
"chars": 121539,
"bytes": 121539,
"tokens": 30385,
"duration_ms": 1500,
"round_trip_ms": 1570,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2097
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-clients": {
"reads": [
{
"verb": "health",
"chars": 1252,
"bytes": 1252,
"tokens": 313,
"duration_ms": 1235,
"round_trip_ms": 1256,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 163
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 1,
"chars": 121476,
"bytes": 121476,
"tokens": 30369,
"duration_ms": 1301,
"round_trip_ms": 1370,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 121476,
"bytes": 121476,
"tokens": 30369,
"duration_ms": 1300,
"round_trip_ms": 1380,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 121477,
"bytes": 121477,
"tokens": 30370,
"duration_ms": 1483,
"round_trip_ms": 1570,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-content": {
"reads": [
{
"verb": "atoms",
"chars": 2193,
"bytes": 2193,
"tokens": 549,
"duration_ms": 1188,
"round_trip_ms": 1202,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 372
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "status",
"chars": 7782,
"bytes": 7782,
"tokens": 1946,
"duration_ms": 349,
"round_trip_ms": 371,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-corpus": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1145,
"bytes": 1145,
"tokens": 287,
"duration_ms": 320,
"round_trip_ms": 338,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 106
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1145,
"bytes": 1145,
"tokens": 287,
"duration_ms": 309,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 106
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1146,
"bytes": 1146,
"tokens": 287,
"duration_ms": 309,
"round_trip_ms": 326,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 106
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: year? month? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-course": {
"reads": [
{
"verb": "modules",
"chars": 2882,
"bytes": 2882,
"tokens": 721,
"duration_ms": 309,
"round_trip_ms": 381,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 958
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-data-hygiene": {
"reads": [
{
"verb": "prompt",
"chars": 4405,
"bytes": 4405,
"tokens": 1102,
"duration_ms": 323,
"round_trip_ms": 339,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1698
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-database": {
"reads": [
{
"verb": "tables",
"chars": 1430,
"bytes": 1430,
"tokens": 358,
"duration_ms": 0,
"round_trip_ms": 15,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "tables",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-database declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-deploy": {
"reads": [
{
"verb": "status",
"chars": 2780,
"bytes": 2780,
"tokens": 695,
"duration_ms": 630,
"round_trip_ms": 646,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 517
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "deploy-receipt",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-docs": {
"reads": [
{
"verb": "me",
"chars": 2043,
"bytes": 2043,
"tokens": 511,
"duration_ms": 789,
"round_trip_ms": 807,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 312
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-dom-cartographer": {
"reads": [
{
"verb": "verify",
"chars": 1804,
"bytes": 1804,
"tokens": 451,
"duration_ms": 325,
"round_trip_ms": 338,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 244
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "log",
"provided": "msg",
"expected": "message",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-email": {
"reads": [],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
2
],
"near_miss_skipped": [
{
"verb": "draft",
"provided": "text",
"expected": "body",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "draft",
"provided": "recipient",
"expected": "to",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "text",
"expected": "body",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-faces": {
"reads": [
{
"verb": "faces",
"chars": 14038,
"bytes": 14038,
"tokens": 3510,
"duration_ms": 416,
"round_trip_ms": 439,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "grammar",
"chars": 18506,
"bytes": 18506,
"tokens": 4627,
"duration_ms": 344,
"round_trip_ms": 393,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-freshbooks": {
"reads": [
{
"verb": "clients",
"limit": 1,
"chars": 3129,
"bytes": 3129,
"tokens": 783,
"duration_ms": 652,
"round_trip_ms": 665,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 639
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "clients",
"limit": 5,
"chars": 5721,
"bytes": 5721,
"tokens": 1431,
"duration_ms": 697,
"round_trip_ms": 717,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1492
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "clients",
"limit": 25,
"chars": 12106,
"bytes": 12106,
"tokens": 3027,
"duration_ms": 626,
"round_trip_ms": 660,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2093
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "invoices",
"limit": 1,
"chars": 3412,
"bytes": 3412,
"tokens": 853,
"duration_ms": 995,
"round_trip_ms": 1014,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 731
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "invoices",
"limit": 5,
"chars": 7308,
"bytes": 7308,
"tokens": 1827,
"duration_ms": 1043,
"round_trip_ms": 1063,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2011
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "invoices",
"limit": 25,
"chars": 19122,
"bytes": 19122,
"tokens": 4781,
"duration_ms": 1645,
"round_trip_ms": 1682,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2095
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 1,
"chars": 3398,
"bytes": 3398,
"tokens": 850,
"duration_ms": 1058,
"round_trip_ms": 1082,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 728
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 7292,
"bytes": 7292,
"tokens": 1823,
"duration_ms": 1116,
"round_trip_ms": 1134,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2007
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 19104,
"bytes": 19104,
"tokens": 4776,
"duration_ms": 980,
"round_trip_ms": 1011,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2090
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "freshbooks-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "time-entries",
"limit": 1,
"chars": 4286,
"bytes": 4286,
"tokens": 1072,
"duration_ms": 869,
"round_trip_ms": 893,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1134
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "time-entries",
"limit": 5,
"chars": 10001,
"bytes": 10001,
"tokens": 2501,
"duration_ms": 745,
"round_trip_ms": 771,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2098
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "time-entries",
"limit": 25,
"chars": 33922,
"bytes": 33922,
"tokens": 8481,
"duration_ms": 805,
"round_trip_ms": 853,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2098
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "expenses",
"limit": 1,
"chars": 4824,
"bytes": 4824,
"tokens": 1206,
"duration_ms": 796,
"round_trip_ms": 816,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1309
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "expenses",
"limit": 5,
"chars": 14909,
"bytes": 14909,
"tokens": 3728,
"duration_ms": 699,
"round_trip_ms": 735,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2094
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "expenses",
"limit": 25,
"chars": 44698,
"bytes": 44698,
"tokens": 11175,
"duration_ms": 784,
"round_trip_ms": 843,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2094
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "clients",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: clients names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "invoices",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: invoices names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "time-entries",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: time-entries names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "expenses",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: expenses names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-gateway": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1385,
"bytes": 1385,
"tokens": 347,
"duration_ms": 569,
"round_trip_ms": 595,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 223
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 2367,
"bytes": 2367,
"tokens": 592,
"duration_ms": 435,
"round_trip_ms": 462,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 708
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 2368,
"bytes": 2368,
"tokens": 592,
"duration_ms": 485,
"round_trip_ms": 511,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 708
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-github": {
"reads": [
{
"verb": "repos",
"limit": 1,
"chars": 1217,
"bytes": 1217,
"tokens": 305,
"duration_ms": 741,
"round_trip_ms": 758,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 139
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "repos",
"limit": 5,
"chars": 1674,
"bytes": 1674,
"tokens": 419,
"duration_ms": 760,
"round_trip_ms": 778,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 355
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "repos",
"limit": 25,
"chars": 3910,
"bytes": 3910,
"tokens": 978,
"duration_ms": 1107,
"round_trip_ms": 1124,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1412
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "repos",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: repos names no argument \"count\". Its grammar is: org? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "comment",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-gmail": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 4197,
"bytes": 4197,
"tokens": 1050,
"duration_ms": 1386,
"round_trip_ms": 1407,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1005
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "gmail-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 9779,
"bytes": 9779,
"tokens": 2445,
"duration_ms": 1816,
"round_trip_ms": 1845,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "gmail-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 30337,
"bytes": 30337,
"tokens": 7585,
"duration_ms": 5886,
"round_trip_ms": 5924,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "gmail-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: limit? query? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "draft",
"provided": "body",
"expected": "text",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "draft",
"provided": "recipient",
"expected": "to",
"reason": "draft effect \u2014 never called by the probe"
},
{
"verb": "reply",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-hands": {
"reads": [
{
"verb": "census",
"chars": 2931,
"bytes": 2931,
"tokens": 733,
"duration_ms": 333,
"round_trip_ms": 349,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "census",
"outcome": "refused",
"code": "service_unavailable",
"message": "census unavailable: http://127.0.0.1:3147/hands did not answer (fetch failed). The census lives in the Snappy daemon; start it, or ask the person whose Mac this is.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-hooks": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1258,
"bytes": 1258,
"tokens": 315,
"duration_ms": 307,
"round_trip_ms": 324,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 160
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 2051,
"bytes": 2051,
"tokens": 513,
"duration_ms": 303,
"round_trip_ms": 328,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 541
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 3461,
"bytes": 3461,
"tokens": 866,
"duration_ms": 309,
"round_trip_ms": 325,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1217
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "status",
"chars": 1180,
"bytes": 1180,
"tokens": 295,
"duration_ms": 304,
"round_trip_ms": 321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 129
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-image": {
"reads": [
{
"verb": "logos",
"chars": 24697,
"bytes": 24697,
"tokens": 6175,
"duration_ms": 321,
"round_trip_ms": 378,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "imageset-grid",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "generate",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-imessage": {
"reads": [
{
"verb": "recent",
"limit": 1,
"chars": 1812,
"bytes": 1812,
"tokens": 453,
"duration_ms": 323,
"round_trip_ms": 358,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 260
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "recent",
"limit": 5,
"chars": 1812,
"bytes": 1812,
"tokens": 453,
"duration_ms": 291,
"round_trip_ms": 311,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 260
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "recent",
"limit": 25,
"chars": 1813,
"bytes": 1813,
"tokens": 454,
"duration_ms": 288,
"round_trip_ms": 309,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 260
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "contacts",
"limit": 1,
"chars": 1822,
"bytes": 1822,
"tokens": 456,
"duration_ms": 290,
"round_trip_ms": 319,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 262
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "contacts",
"limit": 5,
"chars": 1822,
"bytes": 1822,
"tokens": 456,
"duration_ms": 337,
"round_trip_ms": 350,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 262
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "contacts",
"limit": 25,
"chars": 1823,
"bytes": 1823,
"tokens": 456,
"duration_ms": 302,
"round_trip_ms": 321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 262
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "recent",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "recent",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "recent",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "contacts",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "contacts",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "contacts",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "recent",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: recent names no argument \"count\". Its grammar is: contact? and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "contacts",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: contacts names no argument \"count\". Its grammar is: and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-inbound": {
"reads": [
{
"verb": "slack-joins",
"chars": 1168,
"bytes": 1168,
"tokens": 292,
"duration_ms": 596,
"round_trip_ms": 614,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 117
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-inbox-sweep": {
"reads": [
{
"verb": "gmail-personal",
"chars": 2281,
"bytes": 2281,
"tokens": 571,
"duration_ms": 577,
"round_trip_ms": 597,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 385
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "gmail-work",
"chars": 25783,
"bytes": 25783,
"tokens": 6446,
"duration_ms": 7700,
"round_trip_ms": 7736,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2098
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "slack",
"chars": 2008,
"bytes": 2008,
"tokens": 502,
"duration_ms": 2084,
"round_trip_ms": 2143,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 313
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "gmail-personal",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send-skool",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "send-slack",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-infra": {
"reads": [],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-knowledge": {
"reads": [
{
"verb": "birthdays",
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "contacts",
"chars": 1435,
"bytes": 1435,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "dormant",
"chars": 1434,
"bytes": 1434,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "entities",
"chars": 1435,
"bytes": 1435,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "interactions",
"chars": 1439,
"bytes": 1439,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "resolve",
"chars": 1434,
"bytes": 1434,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 329
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "birthdays",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "contacts",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "dormant",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "entities",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "interactions",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "resolve",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-knowledge declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-krisp": {
"reads": [
{
"verb": "fetch-meetings",
"limit": 1,
"chars": 10026,
"bytes": 10026,
"tokens": 2507,
"duration_ms": 3540,
"round_trip_ms": 3566,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2096
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "fetch-meetings",
"limit": 5,
"chars": 10026,
"bytes": 10026,
"tokens": 2507,
"duration_ms": 2364,
"round_trip_ms": 2393,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2096
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "fetch-meetings",
"limit": 25,
"chars": 10029,
"bytes": 10029,
"tokens": 2508,
"duration_ms": 8009,
"round_trip_ms": 8043,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2096
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "fetch-action-items",
"limit": 1,
"chars": 4312,
"bytes": 4312,
"tokens": 1078,
"duration_ms": 3052,
"round_trip_ms": 3075,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1039
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "krisp-actions",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "fetch-action-items",
"limit": 5,
"chars": 9174,
"bytes": 9174,
"tokens": 2294,
"duration_ms": 10515,
"round_trip_ms": 10548,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2101
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "krisp-actions",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "fetch-action-items",
"limit": 25,
"chars": 30793,
"bytes": 30793,
"tokens": 7699,
"duration_ms": 1069,
"round_trip_ms": 1148,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2100
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "krisp-actions",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "fetch-meetings",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: fetch-meetings names no argument \"count\". Its grammar is: after-date? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "fetch-action-items",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: fetch-action-items names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-libretto": {
"reads": [
{
"verb": "lessons",
"chars": 3263,
"bytes": 3263,
"tokens": 816,
"duration_ms": 344,
"round_trip_ms": 372,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 693
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-linkedin": {
"reads": [
{
"verb": "profile",
"chars": 2739,
"bytes": 2739,
"tokens": 685,
"duration_ms": 979,
"round_trip_ms": 1134,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 511
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "linkedin-profile",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "scheduled",
"chars": 1731,
"bytes": 1731,
"tokens": 433,
"duration_ms": 745,
"round_trip_ms": 780,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 193
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "linkedin-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "token-status",
"chars": 1372,
"bytes": 1372,
"tokens": 343,
"duration_ms": 340,
"round_trip_ms": 374,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 213
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "schedule",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "comment",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "draft-patch",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-maintenance": {
"reads": [
{
"verb": "all",
"limit": 1,
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 12,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 331
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "all",
"limit": 5,
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 17,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 331
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "all",
"limit": 25,
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 331
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "all",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "all",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "all",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "all",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED retired_backend: snappy-maintenance declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.\nFIX: Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host "
}
],
"search_ranks": [
1
]
},
"snappy-mastermind-model": {
"reads": [
{
"verb": "funnels",
"chars": 4318,
"bytes": 4318,
"tokens": 1080,
"duration_ms": 317,
"round_trip_ms": 337,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1084
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "ladders",
"chars": 5826,
"bytes": 5826,
"tokens": 1457,
"duration_ms": 312,
"round_trip_ms": 335,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1577
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "math",
"chars": 5749,
"bytes": 5749,
"tokens": 1438,
"duration_ms": 302,
"round_trip_ms": 330,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1539
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "sales",
"chars": 4094,
"bytes": 4094,
"tokens": 1024,
"duration_ms": 305,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 993
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "team",
"chars": 2809,
"bytes": 2809,
"tokens": 703,
"duration_ms": 303,
"round_trip_ms": 316,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 573
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "topics",
"chars": 3112,
"bytes": 3112,
"tokens": 778,
"duration_ms": 306,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 674
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "transcripts",
"chars": 9354,
"bytes": 9354,
"tokens": 2339,
"duration_ms": 314,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2103
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-mine": {
"reads": [
{
"verb": "files",
"limit": 1,
"chars": 1237,
"bytes": 1237,
"tokens": 310,
"duration_ms": 314,
"round_trip_ms": 334,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 151
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "files",
"limit": 5,
"chars": 1566,
"bytes": 1566,
"tokens": 392,
"duration_ms": 305,
"round_trip_ms": 326,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 311
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "files",
"limit": 25,
"chars": 2385,
"bytes": 2385,
"tokens": 597,
"duration_ms": 309,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 709
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "manifest",
"chars": 2374,
"bytes": 2374,
"tokens": 594,
"duration_ms": 291,
"round_trip_ms": 308,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 372
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "meetings-by",
"limit": 1,
"chars": 1331,
"bytes": 1331,
"tokens": 333,
"duration_ms": 307,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 94
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "meetings-by",
"limit": 5,
"chars": 1331,
"bytes": 1331,
"tokens": 333,
"duration_ms": 305,
"round_trip_ms": 320,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 94
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "meetings-by",
"limit": 25,
"chars": 1332,
"bytes": 1332,
"tokens": 333,
"duration_ms": 310,
"round_trip_ms": 328,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 94
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "pending",
"limit": 1,
"chars": 2480,
"bytes": 2480,
"tokens": 620,
"duration_ms": 333,
"round_trip_ms": 351,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "pending",
"limit": 5,
"chars": 2480,
"bytes": 2480,
"tokens": 620,
"duration_ms": 291,
"round_trip_ms": 305,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "pending",
"limit": 25,
"chars": 2481,
"bytes": 2481,
"tokens": 621,
"duration_ms": 284,
"round_trip_ms": 302,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "sources",
"chars": 2469,
"bytes": 2469,
"tokens": 618,
"duration_ms": 285,
"round_trip_ms": 300,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 371
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "manifest",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "pending",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "pending",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "pending",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "sources",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "files",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: files names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "meetings-by",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: meetings-by names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "pending",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: pending names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-nightshift": {
"reads": [
{
"verb": "bar",
"chars": 3087,
"bytes": 3087,
"tokens": 772,
"duration_ms": 308,
"round_trip_ms": 327,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 454
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "board",
"chars": 11498,
"bytes": 11498,
"tokens": 2875,
"duration_ms": 345,
"round_trip_ms": 377,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 492
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "log",
"chars": 1131,
"bytes": 1131,
"tokens": 283,
"duration_ms": 306,
"round_trip_ms": 331,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 105
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "preflight",
"chars": 3277,
"bytes": 3277,
"tokens": 820,
"duration_ms": 815,
"round_trip_ms": 834,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 379
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "report",
"chars": 9609,
"bytes": 9609,
"tokens": 2403,
"duration_ms": 323,
"round_trip_ms": 344,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 493
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "status",
"chars": 2421,
"bytes": 2421,
"tokens": 606,
"duration_ms": 411,
"round_trip_ms": 428,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 441
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "bar",
"outcome": "refused",
"code": "not_found",
"message": "THE-WORLD-CLASS-BAR.md is not at /Users/robert[name]/Projects/snappy-os-app/THE-WORLD-CLASS-BAR.md; `bar` derives every row from it. Run from the repo that holds it, or set SNAPPY_NIGHTSHIFT_REPO.",
"fix": "List the collection first and name an identifier the list returned.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "board",
"outcome": "refused",
"message": "the hand's own words name a refusal, with exit 0 \u2014 a zero exit is not a success"
},
{
"verb": "preflight",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "report",
"outcome": "refused",
"message": "the hand's own words name a refusal, with exit 0 \u2014 a zero exit is not a success"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-notion": {
"reads": [
{
"verb": "me",
"chars": 3386,
"bytes": 3386,
"tokens": 847,
"duration_ms": 593,
"round_trip_ms": 618,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 314
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "append",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-offer": {
"reads": [
{
"verb": "icp",
"chars": 11041,
"bytes": 11041,
"tokens": 2761,
"duration_ms": 321,
"round_trip_ms": 347,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2084
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "offer",
"chars": 5715,
"bytes": 5715,
"tokens": 1429,
"duration_ms": 305,
"round_trip_ms": 323,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "pricing",
"chars": 10887,
"bytes": 10887,
"tokens": 2722,
"duration_ms": 309,
"round_trip_ms": 334,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-ops": {
"reads": [
{
"verb": "actions",
"chars": 3906,
"bytes": 3906,
"tokens": 977,
"duration_ms": 344,
"round_trip_ms": 369,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 929
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "catchup",
"chars": 2463,
"bytes": 2463,
"tokens": 616,
"duration_ms": 937,
"round_trip_ms": 953,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 370
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "diff-machine",
"chars": 1533,
"bytes": 1533,
"tokens": 384,
"duration_ms": 307,
"round_trip_ms": 324,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 320
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "fingerprint",
"chars": 51984,
"bytes": 51984,
"tokens": 12996,
"duration_ms": 421,
"round_trip_ms": 461,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2090
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "ladder",
"chars": 3018,
"bytes": 3018,
"tokens": 755,
"duration_ms": 407,
"round_trip_ms": 423,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1028
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "recipes",
"chars": 2466,
"bytes": 2466,
"tokens": 617,
"duration_ms": 947,
"round_trip_ms": 961,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 370
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "snapshot",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 18696,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
},
{
"verb": "status",
"chars": 1501,
"bytes": 1501,
"tokens": 376,
"duration_ms": 356,
"round_trip_ms": 428,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 287
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "story",
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 313,
"round_trip_ms": 332,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 293
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "trend",
"chars": 1323,
"bytes": 1323,
"tokens": 331,
"duration_ms": 335,
"round_trip_ms": 359,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 200
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "catchup",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "diff-machine",
"outcome": "failed",
"message": "the hand exited 2"
},
{
"verb": "recipes",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "story",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "remind",
"provided": "body",
"expected": "text",
"reason": "post effect \u2014 never called by the probe"
}
]
},
"snappy-os-operator": {
"reads": [
{
"verb": "approvals",
"chars": 2883,
"bytes": 2883,
"tokens": 721,
"duration_ms": 335,
"round_trip_ms": 356,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 380
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "deploy-truth",
"chars": 2895,
"bytes": 2895,
"tokens": 724,
"duration_ms": 328,
"round_trip_ms": 371,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 383
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "health",
"chars": 2871,
"bytes": 2871,
"tokens": 718,
"duration_ms": 325,
"round_trip_ms": 350,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 377
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "runs",
"limit": 1,
"chars": 2877,
"bytes": 2877,
"tokens": 720,
"duration_ms": 325,
"round_trip_ms": 341,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "runs",
"limit": 5,
"chars": 2877,
"bytes": 2877,
"tokens": 720,
"duration_ms": 321,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "runs",
"limit": 25,
"chars": 2878,
"bytes": 2878,
"tokens": 720,
"duration_ms": 330,
"round_trip_ms": 349,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "approvals",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "deploy-truth",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "health",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "runs",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "runs",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
},
{
"verb": "runs",
"outcome": "refused",
"code": "service_unavailable",
"message": "the Snappy daemon at http://127.0.0.1:3147 did not answer (fetch failed). Start the head-screen daemon on this Mac, then repeat the call.",
"fix": "Start or install the named service on this machine, then repeat the call. The refusal names it; it never names a value.",
"contract_slice": "requires"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "runs",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: runs names no argument \"count\". Its grammar is: script-id? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-outbound": {
"reads": [
{
"verb": "status",
"chars": 1454,
"bytes": 1454,
"tokens": 364,
"duration_ms": 326,
"round_trip_ms": 343,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 132
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-pipeline": {
"reads": [
{
"verb": "batch-overview",
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 21,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "crash-patterns",
"limit": 1,
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 29,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "crash-patterns",
"limit": 5,
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "crash-patterns",
"limit": 25,
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 17,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "queue-health",
"chars": 1436,
"bytes": 1436,
"tokens": 359,
"duration_ms": 0,
"round_trip_ms": 16,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
},
{
"verb": "stuck-analysis",
"chars": 1438,
"bytes": 1438,
"tokens": 360,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "batch-overview",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "crash-patterns",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "crash-patterns",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "crash-patterns",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "queue-health",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
},
{
"verb": "stuck-analysis",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "crash-patterns",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED retired_backend: snappy-pipeline declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.\nFIX: Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host ans"
}
],
"search_ranks": [
1
]
},
"snappy-playbook": {
"reads": [
{
"verb": "week",
"chars": 2119,
"bytes": 2119,
"tokens": 530,
"duration_ms": 304,
"round_trip_ms": 318,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 281
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "weeks",
"chars": 4533,
"bytes": 4533,
"tokens": 1134,
"duration_ms": 359,
"round_trip_ms": 375,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1759
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "week",
"outcome": "refused",
"code": "missing_argument",
"message": "week needs <1-6>; contract slice week.args = [number]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-positioning": {
"reads": [
{
"verb": "banned",
"chars": 5088,
"bytes": 5088,
"tokens": 1272,
"duration_ms": 312,
"round_trip_ms": 333,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1970
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "tone",
"chars": 2565,
"bytes": 2565,
"tokens": 642,
"duration_ms": 308,
"round_trip_ms": 327,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 806
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-publish": {
"reads": [
{
"verb": "deploys",
"chars": 2594,
"bytes": 2594,
"tokens": 649,
"duration_ms": 600,
"round_trip_ms": 617,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 374
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "deploys",
"outcome": "refused",
"code": "credential_scope_denied",
"message": "[snappy-publish] VERCEL_TOKEN needs authorization (HTTP 403)",
"fix": "Re-grant the credential with the scope the verb names, or call the verb whose road the grant covers.",
"contract_slice": "requires"
}
],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-remotion": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1131,
"bytes": 1131,
"tokens": 283,
"duration_ms": 334,
"round_trip_ms": 352,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 98
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1231,
"bytes": 1231,
"tokens": 308,
"duration_ms": 316,
"round_trip_ms": 331,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 144
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1329,
"bytes": 1329,
"tokens": 333,
"duration_ms": 315,
"round_trip_ms": 330,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 189
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-report-publish": {
"reads": [
{
"verb": "style",
"chars": 1812,
"bytes": 1812,
"tokens": 453,
"duration_ms": 317,
"round_trip_ms": 339,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 441
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-sales": {
"reads": [
{
"verb": "clients",
"limit": 1,
"chars": 10484,
"bytes": 10484,
"tokens": 2621,
"duration_ms": 1391,
"round_trip_ms": 1413,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "clients",
"limit": 5,
"chars": 16454,
"bytes": 16454,
"tokens": 4114,
"duration_ms": 1513,
"round_trip_ms": 1539,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "clients",
"limit": 25,
"chars": 47414,
"bytes": 47414,
"tokens": 11854,
"duration_ms": 1367,
"round_trip_ms": 1415,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "leads",
"chars": 121658,
"bytes": 121658,
"tokens": 30415,
"duration_ms": 1422,
"round_trip_ms": 1489,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2087
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "clients",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: clients names no argument \"count\". Its grammar is: limit?.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-scheduling": {
"reads": [
{
"verb": "available",
"chars": 4100,
"bytes": 4100,
"tokens": 1025,
"duration_ms": 1022,
"round_trip_ms": 1041,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 587
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-sensors": {
"reads": [
{
"verb": "dump",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 35292,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
},
{
"verb": "stale",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 35160,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-settings": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1193,
"bytes": 1193,
"tokens": 299,
"duration_ms": 333,
"round_trip_ms": 366,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 128
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1390,
"bytes": 1390,
"tokens": 348,
"duration_ms": 321,
"round_trip_ms": 339,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 222
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 2309,
"bytes": 2309,
"tokens": 578,
"duration_ms": 323,
"round_trip_ms": 338,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 661
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-skool": {
"reads": [
{
"verb": "health",
"chars": 2706,
"bytes": 2706,
"tokens": 677,
"duration_ms": 858,
"round_trip_ms": 903,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 615
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "me",
"chars": 3449,
"bytes": 3449,
"tokens": 863,
"duration_ms": 657,
"round_trip_ms": 679,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 341
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "group",
"chars": 2274,
"bytes": 2274,
"tokens": 569,
"duration_ms": 509,
"round_trip_ms": 530,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 461
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "admins",
"chars": 2735,
"bytes": 2735,
"tokens": 684,
"duration_ms": 495,
"round_trip_ms": 514,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 617
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "levels",
"chars": 4728,
"bytes": 4728,
"tokens": 1182,
"duration_ms": 498,
"round_trip_ms": 520,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1273
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "courses",
"chars": 43477,
"bytes": 43477,
"tokens": 10870,
"duration_ms": 520,
"round_trip_ms": 572,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "labels",
"chars": 7005,
"bytes": 7005,
"tokens": 1752,
"duration_ms": 508,
"round_trip_ms": 548,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2027
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "links",
"chars": 1849,
"bytes": 1849,
"tokens": 463,
"duration_ms": 498,
"round_trip_ms": 515,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 326
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "posts",
"chars": 41504,
"bytes": 41504,
"tokens": 10376,
"duration_ms": 885,
"round_trip_ms": 934,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "members",
"chars": 15929,
"bytes": 15929,
"tokens": 3983,
"duration_ms": 720,
"round_trip_ms": 756,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "modules",
"chars": 34144,
"bytes": 34144,
"tokens": 8536,
"duration_ms": 635,
"round_trip_ms": 694,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2088
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "leaderboards",
"chars": 35409,
"bytes": 35409,
"tokens": 8853,
"duration_ms": 628,
"round_trip_ms": 675,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2093
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "calendar",
"chars": 16002,
"bytes": 16002,
"tokens": 4001,
"duration_ms": 659,
"round_trip_ms": 694,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "chats",
"limit": 1,
"chars": 7541,
"bytes": 7541,
"tokens": 1886,
"duration_ms": 507,
"round_trip_ms": 533,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "chats",
"limit": 5,
"chars": 21358,
"bytes": 21358,
"tokens": 5340,
"duration_ms": 507,
"round_trip_ms": 540,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "chats",
"limit": 25,
"chars": 56957,
"bytes": 56957,
"tokens": 14240,
"duration_ms": 522,
"round_trip_ms": 588,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "skool-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "questions",
"chars": 35726,
"bytes": 35726,
"tokens": 8932,
"duration_ms": 689,
"round_trip_ms": 746,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2090
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "brief",
"chars": 60967,
"bytes": 60967,
"tokens": 15242,
"duration_ms": 698,
"round_trip_ms": 760,
"request_count": 1,
"truncated": false,
"cursor": 16000,
"content": [
{
"type": "text",
"chars": 2086
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "chats",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: chats names no argument \"count\". Its grammar is: limit? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "post",
"provided": "text",
"expected": "body",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "comment",
"provided": "text",
"expected": "body",
"reason": "post effect \u2014 never called by the probe"
},
{
"verb": "dm",
"provided": "text",
"expected": "body",
"reason": "send effect \u2014 never called by the probe"
}
],
"safety_skipped": [
{
"verb": "post-detail",
"effect": "read",
"reason": "declared \"read\" but the name says it reaches a person or spends \u2014 never called by the probe"
}
]
},
"snappy-slack": {
"reads": [
{
"verb": "channels",
"chars": 3936,
"bytes": 3936,
"tokens": 984,
"duration_ms": 605,
"round_trip_ms": 625,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 925
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "slack-channels",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "reply",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "edit",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-statechange": {
"reads": [
{
"verb": "health",
"chars": 2222,
"bytes": 2222,
"tokens": 556,
"duration_ms": 801,
"round_trip_ms": 822,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 431
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "unread",
"chars": 2397,
"bytes": 2397,
"tokens": 600,
"duration_ms": 702,
"round_trip_ms": 724,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 488
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "posts",
"chars": 8727,
"bytes": 8727,
"tokens": 2182,
"duration_ms": 748,
"round_trip_ms": 777,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2092
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "statechange-feed",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "stage-post",
"provided": "text",
"expected": "body",
"reason": "write-reversible effect \u2014 never called by the probe"
},
{
"verb": "stage-message",
"provided": "text",
"expected": "body",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-telegram": {
"reads": [
{
"verb": "read",
"limit": 1,
"chars": 2283,
"bytes": 2283,
"tokens": 571,
"duration_ms": 749,
"round_trip_ms": 777,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 364
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 5,
"chars": 2285,
"bytes": 2285,
"tokens": 572,
"duration_ms": 4032,
"round_trip_ms": 4054,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 365
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 25,
"chars": 2284,
"bytes": 2284,
"tokens": 571,
"duration_ms": 736,
"round_trip_ms": 805,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 364
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "messages",
"limit": 1,
"chars": 2301,
"bytes": 2301,
"tokens": 576,
"duration_ms": 3774,
"round_trip_ms": 3793,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 369
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "messages",
"limit": 5,
"chars": 2299,
"bytes": 2299,
"tokens": 575,
"duration_ms": 722,
"round_trip_ms": 742,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 368
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "messages",
"limit": 25,
"chars": 2302,
"bytes": 2302,
"tokens": 576,
"duration_ms": 3748,
"round_trip_ms": 3774,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 369
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "updates",
"limit": 1,
"chars": 2417,
"bytes": 2417,
"tokens": 605,
"duration_ms": 861,
"round_trip_ms": 882,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 406
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "updates",
"limit": 5,
"chars": 2419,
"bytes": 2419,
"tokens": 605,
"duration_ms": 3891,
"round_trip_ms": 3913,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 407
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "updates",
"limit": 25,
"chars": 2418,
"bytes": 2418,
"tokens": 605,
"duration_ms": 880,
"round_trip_ms": 900,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 406
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "telegram-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "me",
"chars": 4620,
"bytes": 4620,
"tokens": 1155,
"duration_ms": 741,
"round_trip_ms": 762,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 725
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "decisions",
"chars": 1144,
"bytes": 1144,
"tokens": 286,
"duration_ms": 3795,
"round_trip_ms": 3814,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 97
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "read",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: read names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "messages",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: messages names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "updates",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: updates names no argument \"count\". Its grammar is: and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "send",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "keyboard",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "edit",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
},
{
"verb": "progress",
"provided": "body",
"expected": "text",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-testimonials": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1167,
"bytes": 1167,
"tokens": 292,
"duration_ms": 1117,
"round_trip_ms": 1160,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 114
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1165,
"bytes": 1165,
"tokens": 292,
"duration_ms": 700,
"round_trip_ms": 725,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 113
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1166,
"bytes": 1166,
"tokens": 292,
"duration_ms": 751,
"round_trip_ms": 767,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 113
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: status? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-thumbnails": {
"reads": [
{
"verb": "audit",
"limit": 1,
"chars": 2115,
"bytes": 2115,
"tokens": 529,
"duration_ms": 1293,
"round_trip_ms": 1321,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 411
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "audit",
"limit": 5,
"chars": 2115,
"bytes": 2115,
"tokens": 529,
"duration_ms": 1861,
"round_trip_ms": 1877,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 411
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "audit",
"limit": 25,
"chars": 1826,
"bytes": 1826,
"tokens": 457,
"duration_ms": 3355,
"round_trip_ms": 3375,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 375
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "audit",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "audit",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: audit names no argument \"count\". Its grammar is: channel? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "generate",
"provided": "limit",
"expected": "count",
"reason": "write-reversible effect \u2014 never called by the probe"
}
]
},
"snappy-tool-design": {
"reads": [
{
"verb": "lint",
"limit": 1,
"chars": 2484,
"bytes": 2484,
"tokens": 621,
"duration_ms": 333,
"round_trip_ms": 375,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 324
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "lint",
"limit": 5,
"chars": 2484,
"bytes": 2484,
"tokens": 621,
"duration_ms": 322,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 324
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "lint",
"limit": 25,
"chars": 2485,
"bytes": 2485,
"tokens": 622,
"duration_ms": 321,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 324
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "probe",
"chars": 3144,
"bytes": 3144,
"tokens": 786,
"duration_ms": 395,
"round_trip_ms": 412,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 457
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "render",
"chars": 1819,
"bytes": 1819,
"tokens": 455,
"duration_ms": 325,
"round_trip_ms": 342,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 352
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "loop",
"chars": 3140,
"bytes": 3140,
"tokens": 785,
"duration_ms": 329,
"round_trip_ms": 346,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 456
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "hosts",
"chars": 1859,
"bytes": 1859,
"tokens": 465,
"duration_ms": 336,
"round_trip_ms": 357,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 366
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
}
],
"refusals": [
{
"verb": "lint",
"outcome": "refused",
"code": "missing_argument",
"message": "lint needs <skill> or --all; contract slice lint.args = [skill?]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "lint",
"outcome": "refused",
"code": "missing_argument",
"message": "lint needs <skill> or --all; contract slice lint.args = [skill?]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "lint",
"outcome": "refused",
"code": "missing_argument",
"message": "lint needs <skill> or --all; contract slice lint.args = [skill?]",
"fix": "Supply the named contract argument in the order `verbs.<verb>.args` declares.",
"contract_slice": "verbs.<verb>.args"
},
{
"verb": "probe",
"outcome": "refused",
"code": "missing_credential",
"message": "probe needs a bearer: set SNAPPY_RUNNER_TOKEN_FILE to a 600-mode file holding the runner token (ssh mini cat ~/.snappy-skills/token > <file>), or pass --token-file. The token is never an argument.",
"fix": "Add the named key to the environment cache. The key is named; the value is never printed.",
"contract_slice": "requires"
},
{
"verb": "render",
"outcome": "failed",
"message": "the hand exited 1"
},
{
"verb": "loop",
"outcome": "refused",
"code": "missing_credential",
"message": "probe needs a bearer: set SNAPPY_RUNNER_TOKEN_FILE to a 600-mode file holding the runner token (ssh mini cat ~/.snappy-skills/token > <file>), or pass --token-file. The token is never an argument.",
"fix": "Add the named key to the environment cache. The key is named; the value is never printed.",
"contract_slice": "requires"
},
{
"verb": "hosts",
"outcome": "failed",
"message": "the hand exited 1"
}
],
"unmet_requirement": [],
"near_miss": [
{
"verb": "lint",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: lint names no argument \"count\". Its grammar is: skill? and the flags all, summary, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-transcripts": {
"reads": [
{
"verb": "list",
"limit": 1,
"chars": 1160,
"bytes": 1160,
"tokens": 290,
"duration_ms": 328,
"round_trip_ms": 348,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 5,
"chars": 1160,
"bytes": 1160,
"tokens": 290,
"duration_ms": 320,
"round_trip_ms": 341,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "list",
"limit": 25,
"chars": 1161,
"bytes": 1161,
"tokens": 291,
"duration_ms": 320,
"round_trip_ms": 343,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: year? month? and the flags limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
]
},
"snappy-update": {
"reads": [
{
"verb": "commits",
"limit": 1,
"chars": 2830,
"bytes": 2830,
"tokens": 708,
"duration_ms": 721,
"round_trip_ms": 740,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 578
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "commits",
"limit": 5,
"chars": 4969,
"bytes": 4969,
"tokens": 1243,
"duration_ms": 780,
"round_trip_ms": 803,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1288
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "commits",
"limit": 25,
"chars": 13434,
"bytes": 13434,
"tokens": 3359,
"duration_ms": 823,
"round_trip_ms": 852,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "commits",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: commits names no argument \"count\". Its grammar is: repo? days? and the flags limit, json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "notify",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-voice-control": {
"reads": [
{
"verb": "devices",
"chars": 1170,
"bytes": 1170,
"tokens": 293,
"duration_ms": 990,
"round_trip_ms": 1020,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 118
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "permissions",
"chars": 1681,
"bytes": 1681,
"tokens": 421,
"duration_ms": 407,
"round_trip_ms": 482,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 368
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "pick",
"chars": 1148,
"bytes": 1148,
"tokens": 287,
"duration_ms": 746,
"round_trip_ms": 785,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 111
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-walkthrough": {
"reads": [
{
"verb": "status",
"chars": 1235,
"bytes": 1235,
"tokens": 309,
"duration_ms": 339,
"round_trip_ms": 383,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 128
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-website": {
"reads": [
{
"verb": "deploys",
"chars": 2921,
"bytes": 2921,
"tokens": 731,
"duration_ms": 624,
"round_trip_ms": 637,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 374
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "failed"
},
{
"verb": "health",
"chars": 1174,
"bytes": 1174,
"tokens": 294,
"duration_ms": 585,
"round_trip_ms": 608,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 125
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [
{
"verb": "deploys",
"outcome": "refused",
"code": "credential_scope_denied",
"message": "Vercel refused VERCEL_TOKEN for projectId=snappy-website (HTTP 403): {\"error\":{\"code\":\"forbidden\",\"message\":\"Not authorized\",\"invalidToken\":true}}",
"fix": "Re-grant the credential with the scope the verb names, or call the verb whose road the grant covers.",
"contract_slice": "requires"
}
],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-whatsapp": {
"reads": [
{
"verb": "read",
"limit": 1,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 333,
"round_trip_ms": 365,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 5,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 313,
"round_trip_ms": 333,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "read",
"limit": 25,
"chars": 1639,
"bytes": 1639,
"tokens": 410,
"duration_ms": 317,
"round_trip_ms": 336,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 1,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 329,
"round_trip_ms": 383,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 5,
"chars": 1638,
"bytes": 1638,
"tokens": 410,
"duration_ms": 324,
"round_trip_ms": 348,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "list",
"limit": 25,
"chars": 1639,
"bytes": 1639,
"tokens": 410,
"duration_ms": 320,
"round_trip_ms": 340,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 153
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "whatsapp-list",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "read",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: read names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
},
{
"verb": "list",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: list names no argument \"count\". Its grammar is: limit? and the flags json.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "media",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "notify",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "recipient",
"expected": "to",
"reason": "send effect \u2014 never called by the probe"
},
{
"verb": "send",
"provided": "msg",
"expected": "message",
"reason": "send effect \u2014 never called by the probe"
}
]
},
"snappy-xano-dashboard": {
"reads": [
{
"verb": "logs",
"chars": 1446,
"bytes": 1446,
"tokens": 362,
"duration_ms": 0,
"round_trip_ms": 13,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 334
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "logs",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-xano-dashboard declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-xano-mcp": {
"reads": [
{
"verb": "health",
"chars": 1430,
"bytes": 1430,
"tokens": 358,
"duration_ms": 0,
"round_trip_ms": 14,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 328
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "refused"
}
],
"refusals": [
{
"verb": "health",
"outcome": "refused",
"code": "retired_backend",
"message": "snappy-xano-mcp declares backend \"retired\": the road its verbs run on is banned, so no verb on it may execute here. Nothing was started.",
"fix": "Nothing to fix on this hand \u2014 find the live road with search. The banned backend is a RULING, not an outage: the host answering is not evidence the ban is stale."
}
],
"unmet_requirement": [],
"near_miss": [],
"search_ranks": [
1
]
},
"snappy-youtube": {
"reads": [
{
"verb": "auth",
"chars": 2,
"bytes": 2,
"tokens": 1,
"round_trip_ms": 120031,
"request_count": 1,
"truncated": false,
"content": [],
"outputSchemaValid": false,
"evidence_envelope": false,
"outcome": "unknown"
},
{
"verb": "channel",
"chars": 5369,
"bytes": 5369,
"tokens": 1343,
"duration_ms": 826,
"round_trip_ms": 902,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 1374
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered",
"face": {
"state": "named",
"kind": "youtube-channel",
"textFallback": true,
"resourceUri": "ui://snappy/faces",
"mimeType": "text/html;profile=mcp-app",
"renditions": [
"png",
"lang",
"text"
]
}
},
{
"verb": "recent",
"limit": 1,
"chars": 4453,
"bytes": 4453,
"tokens": 1114,
"duration_ms": 662,
"round_trip_ms": 689,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 688
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "recent",
"limit": 5,
"chars": 8905,
"bytes": 8905,
"tokens": 2227,
"duration_ms": 806,
"round_trip_ms": 830,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "recent",
"limit": 25,
"chars": 22712,
"bytes": 22712,
"tokens": 5678,
"duration_ms": 866,
"round_trip_ms": 901,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 2089
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
},
{
"verb": "token-status",
"chars": 1208,
"bytes": 1208,
"tokens": 302,
"duration_ms": 333,
"round_trip_ms": 351,
"request_count": 1,
"truncated": false,
"content": [
{
"type": "text",
"chars": 136
}
],
"structuredContent": {
"present": true
},
"outputSchemaValid": true,
"evidence_envelope": true,
"outcome": "answered"
}
],
"refusals": [],
"unmet_requirement": [],
"near_miss": [
{
"verb": "recent",
"provided": "count",
"expected": "limit",
"refusal": "nothing ran REFUSED argument_unknown: recent names no argument \"count\". Its grammar is: channel? and the flags json, limit.\nFIX: Call info { name, verb } \u2014 the grammar it prints is the only set of words this verb reads. \"\""
}
],
"search_ranks": [
1
],
"near_miss_skipped": [
{
"verb": "reply",
"provided": "body",
"expected": "text",
"reason": "send effect \u2014 never called by the probe"
}
]
}
}
}{
"schema": "snappy-tool-design/render@1",
"generated_at": "2026-09-09T16:24:35.151Z",
"host": {
"ext_apps": "1.7.5",
"widths": [
360,
720
],
"resource_uri": "ui://snappy/faces"
},
"kinds": [
{
"kind": "gmail-list",
"family": "gmail",
"member": "list",
"component": "GmailInboxList",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 261,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 39
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 39
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-list-720.png",
"threw": null
},
{
"kind": "gmail-message",
"family": "gmail",
"member": "one",
"component": "GmailMessage",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 652,
"nested_scroll": false,
"painted": 56
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 568,
"nested_scroll": false,
"painted": 56
}
},
"buttons": [
"Send",
"Later",
"Reply",
"Forward"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-message-720.png",
"threw": null
},
{
"kind": "gmail-thread",
"family": "gmail",
"member": "thread",
"component": "GmailThread",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 76,
"drew": "drew",
"height": 907,
"nested_scroll": false,
"painted": 122
},
"720": {
"first_paint_ms": 60,
"drew": "drew",
"height": 797,
"nested_scroll": false,
"painted": 122
}
},
"buttons": [
"Discard",
"Send",
"Later"
],
"press": {
"word": "Discard",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-thread-720.png",
"threw": null
},
{
"kind": "gmail-draft",
"family": "gmail",
"member": "compose",
"component": "EmailPreview",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 93,
"drew": "drew",
"height": 492,
"nested_scroll": false,
"painted": 29
},
"720": {
"first_paint_ms": 31,
"drew": "drew",
"height": 462,
"nested_scroll": false,
"painted": 29
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-draft-720.png",
"threw": null
},
{
"kind": "gmail-reply",
"family": "gmail",
"member": "compose",
"component": "GmailReply",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 11,
"drew": "drew",
"height": 834,
"nested_scroll": false,
"painted": 124
},
"720": {
"first_paint_ms": 31,
"drew": "drew",
"height": 808,
"nested_scroll": false,
"painted": 124
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-reply-720.png",
"threw": null
},
{
"kind": "gmail-decision",
"family": "gmail",
"member": "decision",
"component": "GmailDecision",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 90,
"drew": "drew",
"height": 802,
"nested_scroll": false,
"painted": 123
},
"720": {
"first_paint_ms": 103,
"drew": "drew",
"height": 758,
"nested_scroll": false,
"painted": 123
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-decision-720.png",
"threw": null
},
{
"kind": "gmail-bulk-change",
"family": "gmail",
"member": "decision",
"component": "GmailBulkChange",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 339,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 11,
"drew": "drew",
"height": 333,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-bulk-change-720.png",
"threw": null
},
{
"kind": "linkedin-feed",
"family": "linkedin",
"member": "list",
"component": "LinkedInFeed",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 62,
"drew": "drew",
"height": 919,
"nested_scroll": false,
"painted": 119
},
"720": {
"first_paint_ms": 40,
"drew": "drew",
"height": 867,
"nested_scroll": false,
"painted": 119
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-feed-720.png",
"threw": null
},
{
"kind": "linkedin-post",
"family": "linkedin",
"member": "one",
"component": "LinkedInPostPreview",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 53,
"drew": "drew",
"height": 363,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 71,
"drew": "drew",
"height": 353,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [
"Publish",
"Later"
],
"press": {
"word": "Publish",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-post-720.png",
"threw": null
},
{
"kind": "linkedin-comments",
"family": "linkedin",
"member": "thread",
"component": "LinkedInCommentThread",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 25,
"drew": "drew",
"height": 504,
"nested_scroll": false,
"painted": 48
},
"720": {
"first_paint_ms": 70,
"drew": "drew",
"height": 456,
"nested_scroll": false,
"painted": 48
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-comments-720.png",
"threw": null
},
{
"kind": "linkedin-profile",
"family": "linkedin",
"member": "profile",
"component": "LinkedInProfileCard",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 62,
"drew": "drew",
"height": 295,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 59,
"drew": "drew",
"height": 311,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-profile-720.png",
"threw": null
},
{
"kind": "linkedin-decision",
"family": "linkedin",
"member": "decision",
"component": "LinkedInDecision",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 982,
"nested_scroll": false,
"painted": 70
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 822,
"nested_scroll": false,
"painted": 70
}
},
"buttons": [
"Publishgoes live on your LinkedIn now",
"Laterkeeps it staged; the badge keeps counting"
],
"press": {
"word": "Publishgoes live on your LinkedIn now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-decision-720.png",
"threw": null
},
{
"kind": "slack-list",
"family": "slack",
"member": "list",
"component": "SlackMessageList",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 84,
"drew": "drew",
"height": 341,
"nested_scroll": false,
"painted": 46
},
"720": {
"first_paint_ms": 31,
"drew": "drew",
"height": 351,
"nested_scroll": false,
"painted": 46
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-list-720.png",
"threw": null
},
{
"kind": "slack-draft",
"family": "slack",
"member": "one",
"component": "SlackMessagePreview",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 21,
"drew": "drew",
"height": 252,
"nested_scroll": false,
"painted": 24
},
"720": {
"first_paint_ms": 36,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 24
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-draft-720.png",
"threw": null
},
{
"kind": "slack-channels",
"family": "slack",
"member": "list",
"component": "SlackChannelList",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 100,
"drew": "drew",
"height": 325,
"nested_scroll": false,
"painted": 29
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 357,
"nested_scroll": false,
"painted": 29
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-channels-720.png",
"threw": null
},
{
"kind": "slack-message",
"family": "slack",
"member": "one",
"component": "SlackMessage",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 251,
"nested_scroll": false,
"painted": 27
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 27
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-message-720.png",
"threw": null
},
{
"kind": "slack-thread",
"family": "slack",
"member": "thread",
"component": "SlackThread",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 33,
"drew": "drew",
"height": 472,
"nested_scroll": false,
"painted": 64
},
"720": {
"first_paint_ms": 53,
"drew": "drew",
"height": 460,
"nested_scroll": false,
"painted": 64
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-thread-720.png",
"threw": null
},
{
"kind": "slack-decision",
"family": "slack",
"member": "decision",
"component": "SlackDecision",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 95,
"drew": "drew",
"height": 756,
"nested_scroll": false,
"painted": 94
},
"720": {
"first_paint_ms": 37,
"drew": "drew",
"height": 683,
"nested_scroll": false,
"painted": 94
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-decision-720.png",
"threw": null
},
{
"kind": "telegram-list",
"family": "telegram",
"member": "list",
"component": "TelegramChatList",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 157,
"drew": "drew",
"height": 388,
"nested_scroll": false,
"painted": 26
},
"720": {
"first_paint_ms": 38,
"drew": "drew",
"height": 360,
"nested_scroll": false,
"painted": 26
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-list-720.png",
"threw": null
},
{
"kind": "telegram-message",
"family": "telegram",
"member": "one",
"component": "TelegramBubble",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 25,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 8
},
"720": {
"first_paint_ms": 11,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 8
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-message-720.png",
"threw": null
},
{
"kind": "telegram-draft",
"family": "telegram",
"member": "one",
"component": "TelegramMessagePreview",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 48,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
},
"720": {
"first_paint_ms": 55,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-draft-720.png",
"threw": null
},
{
"kind": "telegram-decision",
"family": "telegram",
"member": "decision",
"component": "TelegramDecision",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 15,
"drew": "drew",
"height": 256,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 249,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-decision-720.png",
"threw": null
},
{
"kind": "whatsapp-list",
"family": "whatsapp",
"member": "list",
"component": "WhatsAppChatList",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 76,
"drew": "drew",
"height": 267,
"nested_scroll": false,
"painted": 31
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 299,
"nested_scroll": false,
"painted": 31
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-list-720.png",
"threw": null
},
{
"kind": "whatsapp-message",
"family": "whatsapp",
"member": "one",
"component": "WhatsAppBubble",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
},
"720": {
"first_paint_ms": 33,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-message-720.png",
"threw": null
},
{
"kind": "whatsapp-thread",
"family": "whatsapp",
"member": "thread",
"component": "WhatsAppThread",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 101,
"drew": "drew",
"height": 421,
"nested_scroll": false,
"painted": 32
},
"720": {
"first_paint_ms": 68,
"drew": "drew",
"height": 375,
"nested_scroll": false,
"painted": 32
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-thread-720.png",
"threw": null
},
{
"kind": "whatsapp-compose",
"family": "whatsapp",
"member": "compose",
"component": "WhatsAppCompose",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 66,
"drew": "drew",
"height": 639,
"nested_scroll": false,
"painted": 47
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 556,
"nested_scroll": false,
"painted": 47
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-compose-720.png",
"threw": null
},
{
"kind": "whatsapp-decision",
"family": "whatsapp",
"member": "decision",
"component": "WhatsAppDecision",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 98,
"drew": "drew",
"height": 683,
"nested_scroll": false,
"painted": 53
},
"720": {
"first_paint_ms": 49,
"drew": "drew",
"height": 600,
"nested_scroll": false,
"painted": 53
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-decision-720.png",
"threw": null
},
{
"kind": "imessage-message",
"family": "imessage",
"member": "one",
"component": "IMessageBubble",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 93,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
},
"720": {
"first_paint_ms": 66,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-message-720.png",
"threw": null
},
{
"kind": "imessage-thread",
"family": "imessage",
"member": "thread",
"component": "IMessageThread",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 115,
"drew": "drew",
"height": 357,
"nested_scroll": false,
"painted": 18
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 328,
"nested_scroll": false,
"painted": 18
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-thread-720.png",
"threw": null
},
{
"kind": "imessage-compose",
"family": "imessage",
"member": "compose",
"component": "IMessageCompose",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 131,
"drew": "drew",
"height": 550,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 56,
"drew": "drew",
"height": 482,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-compose-720.png",
"threw": null
},
{
"kind": "imessage-decision",
"family": "imessage",
"member": "decision",
"component": "IMessageDecision",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 23,
"drew": "drew",
"height": 594,
"nested_scroll": false,
"painted": 39
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 526,
"nested_scroll": false,
"painted": 39
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-decision-720.png",
"threw": null
},
{
"kind": "freshbooks-list",
"family": "freshbooks",
"member": "list",
"component": "FreshBooksInvoiceList",
"skill": "snappy-freshbooks",
"widths": {
"360": {
"first_paint_ms": 143,
"drew": "drew",
"height": 424,
"nested_scroll": false,
"painted": 48
},
"720": {
"first_paint_ms": 178,
"drew": "drew",
"height": 368,
"nested_scroll": false,
"painted": 48
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/freshbooks-list-720.png",
"threw": null
},
{
"kind": "freshbooks-invoice",
"family": "freshbooks",
"member": "one",
"component": "FreshBooksInvoicePreview",
"skill": "snappy-freshbooks",
"widths": {
"360": {
"first_paint_ms": 25,
"drew": "drew",
"height": 479,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 439,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/freshbooks-invoice-720.png",
"threw": null
},
{
"kind": "freshbooks-decision",
"family": "freshbooks",
"member": "decision",
"component": "FreshBooksDecision",
"skill": "snappy-freshbooks",
"widths": {
"360": {
"first_paint_ms": 150,
"drew": "drew",
"height": 606,
"nested_scroll": false,
"painted": 52
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 526,
"nested_scroll": false,
"painted": 52
}
},
"buttons": [
"Send invoiceemails it to Quillworks for $1,250.00 CAD now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Send invoiceemails it to Quillworks for $1,250.00 CAD now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/freshbooks-decision-720.png",
"threw": null
},
{
"kind": "skool-feed",
"family": "skool",
"member": "list",
"component": "SkoolFeed",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 66,
"drew": "drew",
"height": 636,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 201,
"drew": "drew",
"height": 649,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-feed-720.png",
"threw": null
},
{
"kind": "skool-post",
"family": "skool",
"member": "one",
"component": "SkoolPostPreview",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 335,
"drew": "drew",
"height": 374,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 103,
"drew": "drew",
"height": 347,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-post-720.png",
"threw": null
},
{
"kind": "skool-comments",
"family": "skool",
"member": "thread",
"component": "SkoolCommentThread",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 52,
"drew": "drew",
"height": 547,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 475,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-comments-720.png",
"threw": null
},
{
"kind": "skool-classroom",
"family": "skool",
"member": "one",
"component": "SkoolClassroomModule",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 111,
"drew": "drew",
"height": 630,
"nested_scroll": false,
"painted": 35
},
"720": {
"first_paint_ms": 71,
"drew": "drew",
"height": 725,
"nested_scroll": false,
"painted": 35
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-classroom-720.png",
"threw": null
},
{
"kind": "github-pr",
"family": "github",
"member": "one",
"component": "GitHubPRPreview",
"skill": "snappy-github",
"widths": {
"360": {
"first_paint_ms": 131,
"drew": "drew",
"height": 359,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 314,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/github-pr-720.png",
"threw": null
},
{
"kind": "stripe-payment",
"family": "stripe",
"member": "one",
"component": "PaymentPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 164,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/stripe-payment-720.png",
"threw": null
},
{
"kind": "sheets-rows",
"family": "sheets",
"member": "list",
"component": "SheetRowsPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 77,
"drew": "drew",
"height": 384,
"nested_scroll": false,
"painted": 35
},
"720": {
"first_paint_ms": 22,
"drew": "drew",
"height": 344,
"nested_scroll": false,
"painted": 35
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/sheets-rows-720.png",
"threw": null
},
{
"kind": "media-video",
"family": "media",
"member": "one",
"component": "VideoPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 159,
"drew": "drew",
"height": 777,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 771,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [
"Publishuploads the cut and makes it visible",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Publishuploads the cut and makes it visible",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-video-720.png",
"threw": null
},
{
"kind": "media-document",
"family": "media",
"member": "one",
"component": "DocumentView",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 613,
"nested_scroll": false,
"painted": 10
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 512,
"nested_scroll": false,
"painted": 10
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-document-720.png",
"threw": null
},
{
"kind": "media-image",
"family": "media",
"member": "one",
"component": "ImagePreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 68,
"drew": "drew",
"height": 403,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 527,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-image-720.png",
"threw": null
},
{
"kind": "media-pdf",
"family": "media",
"member": "one",
"component": "PdfPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 23,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 10
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 10
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-pdf-720.png",
"threw": null
},
{
"kind": "media-slides",
"family": "media",
"member": "one",
"component": "SlidesPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 457,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 538,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [
"Where the build stands",
"What changed since July",
"The two decisions we need",
"Cost and timeline"
],
"press": {
"word": "Where the build stands",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-slides-720.png",
"threw": null
},
{
"kind": "media-html",
"family": "media",
"member": "one",
"component": "HTMLPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 420,
"nested_scroll": false,
"painted": 16
},
"720": {
"first_paint_ms": 32,
"drew": "drew",
"height": 452,
"nested_scroll": false,
"painted": 16
}
},
"buttons": [
"Preview",
"Code",
"Save as Artifact"
],
"press": {
"word": "Preview",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-html-720.png",
"threw": null
},
{
"kind": "media-google-doc",
"family": "media",
"member": "one",
"component": "GoogleDoc",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 2424,
"nested_scroll": false,
"painted": 27
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 824,
"nested_scroll": false,
"painted": 27
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-google-doc-720.png",
"threw": null
},
{
"kind": "calendar-event",
"family": "calendar",
"member": "one",
"component": "CalendarEventPreview",
"skill": "snappy-calendar",
"widths": {
"360": {
"first_paint_ms": 67,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 28
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 28
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/calendar-event-720.png",
"threw": null
},
{
"kind": "calendar-week",
"family": "calendar",
"member": "list",
"component": "CalendarWeekPreview",
"skill": "snappy-calendar",
"widths": {
"360": {
"first_paint_ms": 29,
"drew": "drew",
"height": 540,
"nested_scroll": false,
"painted": 121
},
"720": {
"first_paint_ms": 64,
"drew": "drew",
"height": 572,
"nested_scroll": false,
"painted": 121
}
},
"buttons": [
"Mon7",
"Tue8",
"Wed9",
"Thu10",
"Fri11",
"Sat12",
"Sun13",
"Daily Standup, 9am",
"Design Critique1:00 PM",
"Engineering Sync11:00 AM",
"Stakeholder Update3:00 PM"
],
"press": {
"word": "Mon7",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/calendar-week-720.png",
"threw": null
},
{
"kind": "x-post",
"family": "x",
"member": "one",
"component": "XPost",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 44,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 31
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 31
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/x-post-720.png",
"threw": null
},
{
"kind": "x-thread",
"family": "x",
"member": "thread",
"component": "XThread",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 21,
"drew": "drew",
"height": 479,
"nested_scroll": false,
"painted": 98
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 433,
"nested_scroll": false,
"painted": 98
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/x-thread-720.png",
"threw": null
},
{
"kind": "x-decision",
"family": "x",
"member": "decision",
"component": "XDecision",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 824,
"nested_scroll": false,
"painted": 119
},
"720": {
"first_paint_ms": 14,
"drew": "drew",
"height": 706,
"nested_scroll": false,
"painted": 119
}
},
"buttons": [
"Publishgoes live on your X account now",
"Laterkeeps it staged; the badge keeps counting"
],
"press": {
"word": "Publishgoes live on your X account now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/x-decision-720.png",
"threw": null
},
{
"kind": "youtube-package",
"family": "youtube",
"member": "compose",
"component": "YouTubeVideoPackage",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 220,
"drew": "drew",
"height": 719,
"nested_scroll": false,
"painted": 31
},
"720": {
"first_paint_ms": 12,
"drew": "drew",
"height": 762,
"nested_scroll": false,
"painted": 31
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-package-720.png",
"threw": null
},
{
"kind": "youtube-video",
"family": "youtube",
"member": "one",
"component": "YouTubeVideoCard",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 310,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 355,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-video-720.png",
"threw": null
},
{
"kind": "youtube-comments",
"family": "youtube",
"member": "thread",
"component": "YouTubeCommentThread",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 626,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 536,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-comments-720.png",
"threw": null
},
{
"kind": "youtube-channel",
"family": "youtube",
"member": "profile",
"component": "YouTubeChannelHeader",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 32,
"drew": "drew",
"height": 458,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 316,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-channel-720.png",
"threw": null
},
{
"kind": "instagram-post",
"family": "instagram",
"member": "one",
"component": "InstagramPost",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 159,
"drew": "drew",
"height": 686,
"nested_scroll": false,
"painted": 36
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 832,
"nested_scroll": false,
"painted": 36
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-post-720.png",
"threw": null
},
{
"kind": "instagram-carousel",
"family": "instagram",
"member": "compose",
"component": "InstagramCarousel",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 606,
"nested_scroll": false,
"painted": 50
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 616,
"nested_scroll": false,
"painted": 50
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-carousel-720.png",
"threw": null
},
{
"kind": "instagram-reel",
"family": "instagram",
"member": "one",
"component": "InstagramReelCover",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 556,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 588,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-reel-720.png",
"threw": null
},
{
"kind": "instagram-grid",
"family": "instagram",
"member": "list",
"component": "InstagramGrid",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 22,
"drew": "drew",
"height": 295,
"nested_scroll": false,
"painted": 22
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 416,
"nested_scroll": false,
"painted": 22
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-grid-720.png",
"threw": null
},
{
"kind": "notion-page",
"family": "notion",
"member": "one",
"component": "NotionPage",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 126,
"drew": "drew",
"height": 1153,
"nested_scroll": false,
"painted": 42
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 882,
"nested_scroll": false,
"painted": 42
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-page-720.png",
"threw": null
},
{
"kind": "notion-pages",
"family": "notion",
"member": "list",
"component": "NotionPageList",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 71,
"drew": "drew",
"height": 317,
"nested_scroll": false,
"painted": 27
},
"720": {
"first_paint_ms": 74,
"drew": "drew",
"height": 328,
"nested_scroll": false,
"painted": 27
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-pages-720.png",
"threw": null
},
{
"kind": "linkedin-post-published",
"family": "linkedin",
"member": "one",
"component": "LinkedInPost",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 668,
"nested_scroll": false,
"painted": 23
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 728,
"nested_scroll": false,
"painted": 23
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-post-published-720.png",
"threw": null
},
{
"kind": "blocks-options",
"family": "blocks",
"member": "decision",
"component": "OptionsCard",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 140,
"drew": "drew",
"height": 669,
"nested_scroll": false,
"painted": 34
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 490,
"nested_scroll": false,
"painted": 34
}
},
"buttons": [
"Use this one",
"Later"
],
"press": {
"word": "Use this one",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/blocks-options-720.png",
"threw": null
},
{
"kind": "blocks-audio",
"family": "blocks",
"member": "one",
"component": "AudioPlayer",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 36,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 4
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 4
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/blocks-audio-720.png",
"threw": null
},
{
"kind": "blocks-repair",
"family": "blocks",
"member": "one",
"component": "RepairState",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 307,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 269,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/blocks-repair-720.png",
"threw": null
},
{
"kind": "statechange-feed",
"family": "statechange",
"member": "list",
"component": "StatechangeFeed",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 77,
"drew": "drew",
"height": 2424,
"nested_scroll": false,
"painted": 235
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 2429,
"nested_scroll": false,
"painted": 235
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-feed-720.png",
"threw": null
},
{
"kind": "statechange-post",
"family": "statechange",
"member": "one",
"component": "StatechangePost",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 43,
"drew": "drew",
"height": 401,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 383,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-post-720.png",
"threw": null
},
{
"kind": "statechange-thread",
"family": "statechange",
"member": "thread",
"component": "StatechangeThread",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 791,
"nested_scroll": false,
"painted": 54
},
"720": {
"first_paint_ms": 22,
"drew": "drew",
"height": 746,
"nested_scroll": false,
"painted": 54
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-thread-720.png",
"threw": null
},
{
"kind": "statechange-unread",
"family": "statechange",
"member": "list",
"component": "StatechangeUnread",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 14,
"drew": "drew",
"height": 417,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 466,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-unread-720.png",
"threw": null
},
{
"kind": "data-chart",
"family": "data",
"member": "one",
"component": "Chart",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 53,
"drew": "drew",
"height": 400,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 423,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/data-chart-720.png",
"threw": null
},
{
"kind": "data-table",
"family": "data",
"member": "list",
"component": "DataTable",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 1295,
"nested_scroll": false,
"painted": 136
},
"720": {
"first_paint_ms": 28,
"drew": "drew",
"height": 941,
"nested_scroll": false,
"painted": 136
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/data-table-720.png",
"threw": null
},
{
"kind": "code-diff",
"family": "code",
"member": "one",
"component": "CodeDiff",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 61,
"drew": "drew",
"height": 883,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 658,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/code-diff-720.png",
"threw": null
},
{
"kind": "agent-run",
"family": "agent",
"member": "one",
"component": "AgentRun",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 321,
"drew": "drew",
"height": 883,
"nested_scroll": false,
"painted": 50
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 778,
"nested_scroll": false,
"painted": 50
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/agent-run-720.png",
"threw": null
},
{
"kind": "agent-permission",
"family": "agent",
"member": "decision",
"component": "RunPermission",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 379,
"nested_scroll": false,
"painted": 13
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 353,
"nested_scroll": false,
"painted": 13
}
},
"buttons": [
"Push it",
"Not yet"
],
"press": {
"word": "Push it",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/agent-permission-720.png",
"threw": null
},
{
"kind": "thumbnails-set",
"family": "thumbnails",
"member": "list",
"component": "ThumbnailSet",
"skill": "snappy-thumbnails",
"widths": {
"360": {
"first_paint_ms": 154,
"drew": "drew",
"height": 610,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 12,
"drew": "drew",
"height": 521,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [
"no imageClean pass · chosenthe number first — 41,000 rows, no repair",
"no imageThe slow sourceone bar taller than the rest",
"no imageBefore / aftertwo runs side by side",
"no imageMara at the boarda face, no number",
"no imageTerminal greenthe replay output, unedited",
"no imagePlain titlewords only, largest type"
],
"press": {
"word": "no imageClean pass · chosenthe number first — 41,000 rows, no repair",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/thumbnails-set-720.png",
"threw": null
},
{
"kind": "deploy-receipt",
"family": "deploy",
"member": "one",
"component": "DeployReceipt",
"skill": "snappy-deploy",
"widths": {
"360": {
"first_paint_ms": 100,
"drew": "drew",
"height": 402,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 16,
"drew": "drew",
"height": 342,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/deploy-receipt-720.png",
"threw": null
},
{
"kind": "schedule-pick",
"family": "schedule",
"member": "decision",
"component": "SchedulePick",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 175,
"drew": "drew",
"height": 454,
"nested_scroll": false,
"painted": 32
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 472,
"nested_scroll": false,
"painted": 32
}
},
"buttons": [
"Hold this time",
"Later"
],
"press": {
"word": "Hold this time",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/schedule-pick-720.png",
"threw": null
},
{
"kind": "krisp-meeting",
"family": "krisp",
"member": "one",
"component": "KrispMeeting",
"skill": "snappy-krisp",
"widths": {
"360": {
"first_paint_ms": 56,
"drew": "drew",
"height": 504,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 51,
"drew": "drew",
"height": 319,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/krisp-meeting-720.png",
"threw": null
},
{
"kind": "krisp-hits",
"family": "krisp",
"member": "list",
"component": "KrispTranscriptHits",
"skill": "snappy-krisp",
"widths": {
"360": {
"first_paint_ms": 23,
"drew": "drew",
"height": 708,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 14,
"drew": "drew",
"height": 479,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/krisp-hits-720.png",
"threw": null
},
{
"kind": "krisp-actions",
"family": "krisp",
"member": "list",
"component": "KrispActionItems",
"skill": "snappy-krisp",
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 616,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 459,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/krisp-actions-720.png",
"threw": null
},
{
"kind": "knowledge-hits",
"family": "knowledge",
"member": "list",
"component": "KnowledgeHits",
"skill": "snappy-knowledge",
"widths": {
"360": {
"first_paint_ms": 52,
"drew": "drew",
"height": 703,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 12,
"drew": "drew",
"height": 529,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/knowledge-hits-720.png",
"threw": null
},
{
"kind": "knowledge-contact",
"family": "knowledge",
"member": "profile",
"component": "KnowledgeContact",
"skill": "snappy-knowledge",
"widths": {
"360": {
"first_paint_ms": 10,
"drew": "drew",
"height": 460,
"nested_scroll": false,
"painted": 24
},
"720": {
"first_paint_ms": 17,
"drew": "drew",
"height": 339,
"nested_scroll": false,
"painted": 24
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/knowledge-contact-720.png",
"threw": null
},
{
"kind": "knowledge-company",
"family": "knowledge",
"member": "profile",
"component": "KnowledgeCompany",
"skill": "snappy-knowledge",
"widths": {
"360": {
"first_paint_ms": 18,
"drew": "drew",
"height": 412,
"nested_scroll": false,
"painted": 25
},
"720": {
"first_paint_ms": 28,
"drew": "drew",
"height": 339,
"nested_scroll": false,
"painted": 25
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/knowledge-company-720.png",
"threw": null
},
{
"kind": "transcripts-nugget",
"family": "transcripts",
"member": "one",
"component": "TranscriptNugget",
"skill": "snappy-transcripts",
"widths": {
"360": {
"first_paint_ms": 58,
"drew": "drew",
"height": 335,
"nested_scroll": false,
"painted": 9
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 9
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/transcripts-nugget-720.png",
"threw": null
},
{
"kind": "transcripts-hits",
"family": "transcripts",
"member": "list",
"component": "TranscriptHits",
"skill": "snappy-transcripts",
"widths": {
"360": {
"first_paint_ms": 28,
"drew": "drew",
"height": 640,
"nested_scroll": false,
"painted": 30
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 502,
"nested_scroll": false,
"painted": 30
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/transcripts-hits-720.png",
"threw": null
},
{
"kind": "imageset-grid",
"family": "imageset",
"member": "decision",
"component": "ImageSetGrid",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 124,
"drew": "drew",
"height": 1485,
"nested_scroll": false,
"painted": 43
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 695,
"nested_scroll": false,
"painted": 43
}
},
"buttons": [
"Use the kept two2 of 4 kept",
"Laterkeeps them staged; nothing leaves this machine"
],
"press": {
"word": "Use the kept two2 of 4 kept",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imageset-grid-720.png",
"threw": null
},
{
"kind": "walkthrough-steps",
"family": "walkthrough",
"member": "list",
"component": "WalkthroughSteps",
"skill": "snappy-walkthrough",
"widths": {
"360": {
"first_paint_ms": 102,
"drew": "drew",
"height": 689,
"nested_scroll": false,
"painted": 40
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 653,
"nested_scroll": false,
"painted": 40
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/walkthrough-steps-720.png",
"threw": null
},
{
"kind": "doc-card",
"family": "doc",
"member": "one",
"component": "DocCard",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 38,
"drew": "drew",
"height": 422,
"nested_scroll": false,
"painted": 15
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 331,
"nested_scroll": false,
"painted": 15
}
},
"buttons": [
"Publishposts to Harbourline — 1,240 members",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Publishposts to Harbourline — 1,240 members",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/doc-card-720.png",
"threw": null
},
{
"kind": "statechange-message",
"family": "statechange",
"member": "compose",
"component": "StatechangeMessage",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 14,
"drew": "drew",
"height": 426,
"nested_scroll": false,
"painted": 18
},
"720": {
"first_paint_ms": 9,
"drew": "drew",
"height": 335,
"nested_scroll": false,
"painted": 18
}
},
"buttons": [
"Sendsends into https://pro.statechange.ai/messages/8891 now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Sendsends into https://pro.statechange.ai/messages/8891 now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-message-720.png",
"threw": null
},
{
"kind": "statechange-decision",
"family": "statechange",
"member": "decision",
"component": "StatechangeDecision",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 14,
"drew": "drew",
"height": 704,
"nested_scroll": false,
"painted": 42
},
"720": {
"first_paint_ms": 10,
"drew": "drew",
"height": 637,
"nested_scroll": false,
"painted": 42
}
},
"buttons": [
"Postpublishes to Quillworks now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Postpublishes to Quillworks now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-decision-720.png",
"threw": null
},
{
"kind": "github-comments",
"family": "github",
"member": "thread",
"component": "GitHubCommentThread",
"skill": "snappy-github",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 590,
"nested_scroll": false,
"painted": 34
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 463,
"nested_scroll": false,
"painted": 34
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/github-comments-720.png",
"threw": null
},
{
"kind": "github-comment",
"family": "github",
"member": "compose",
"component": "GitHubComment",
"skill": "snappy-github",
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 885,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 10,
"drew": "drew",
"height": 684,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [
"Commentposts the comment on quillworks/atlas#218, where the whole team reads it, now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Commentposts the comment on quillworks/atlas#218, where the whole team reads it, now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/github-comment-720.png",
"threw": null
},
{
"kind": "youtube-reply",
"family": "youtube",
"member": "compose",
"component": "YouTubeReply",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 688,
"nested_scroll": false,
"painted": 45
},
"720": {
"first_paint_ms": 16,
"drew": "drew",
"height": 573,
"nested_scroll": false,
"painted": 45
}
},
"buttons": [
"Replyposts the reply publicly under comment UgxK1QuillworksAaBb now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Replyposts the reply publicly under comment UgxK1QuillworksAaBb now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-reply-720.png",
"threw": null
},
{
"kind": "notion-blocks",
"family": "notion",
"member": "thread",
"component": "NotionBlocks",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 19,
"drew": "drew",
"height": 541,
"nested_scroll": false,
"painted": 24
},
"720": {
"first_paint_ms": 10,
"drew": "drew",
"height": 425,
"nested_scroll": false,
"painted": 24
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-blocks-720.png",
"threw": null
},
{
"kind": "notion-append",
"family": "notion",
"member": "compose",
"component": "NotionAppend",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 11,
"drew": "drew",
"height": 807,
"nested_scroll": false,
"painted": 32
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 642,
"nested_scroll": false,
"painted": 32
}
},
"buttons": [
"Appendwrites the paragraph to the end of \"Harbourline import — decisions\" now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Appendwrites the paragraph to the end of \"Harbourline import — decisions\" now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-append-720.png",
"threw": null
}
],
"families": {
"gmail": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"linkedin": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"slack": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"telegram": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"whatsapp": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"imessage": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"freshbooks": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"skool": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"github": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"stripe": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"sheets": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"media": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"calendar": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"x": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"youtube": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"instagram": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"notion": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"blocks": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"statechange": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"data": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"code": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"agent": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"thumbnails": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"deploy": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"schedule": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"krisp": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"knowledge": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"transcripts": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"imageset": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"walkthrough": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"doc": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
}
},
"skill_families": {
"snappy-calendar": [
"calendar"
],
"snappy-deploy": [
"deploy"
],
"snappy-faces": [
"gmail",
"linkedin",
"slack",
"telegram",
"whatsapp",
"imessage",
"freshbooks",
"skool",
"github",
"stripe",
"sheets",
"media",
"calendar",
"x",
"youtube",
"instagram",
"notion",
"blocks",
"statechange",
"data",
"code",
"agent",
"thumbnails",
"deploy",
"schedule",
"krisp",
"knowledge",
"transcripts",
"imageset",
"walkthrough",
"doc"
],
"snappy-freshbooks": [
"freshbooks"
],
"snappy-github": [
"github"
],
"snappy-gmail": [
"gmail"
],
"snappy-imessage": [
"imessage"
],
"snappy-knowledge": [
"knowledge"
],
"snappy-krisp": [
"krisp"
],
"snappy-linkedin": [
"linkedin"
],
"snappy-notion": [
"notion"
],
"snappy-skool": [
"skool"
],
"snappy-slack": [
"slack"
],
"snappy-statechange": [
"statechange"
],
"snappy-telegram": [
"telegram"
],
"snappy-thumbnails": [
"thumbnails"
],
"snappy-transcripts": [
"transcripts"
],
"snappy-walkthrough": [
"walkthrough"
],
"snappy-whatsapp": [
"whatsapp"
],
"snappy-youtube": [
"youtube"
]
}
}{
"schema": "snappy-tool-design/render@1",
"generated_at": "2026-09-09T16:24:35.151Z",
"host": {
"ext_apps": "1.7.5",
"widths": [
360,
720
],
"resource_uri": "ui://snappy/faces"
},
"kinds": [
{
"kind": "gmail-list",
"family": "gmail",
"member": "list",
"component": "GmailInboxList",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 261,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 39
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 39
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-list-720.png",
"threw": null
},
{
"kind": "gmail-message",
"family": "gmail",
"member": "one",
"component": "GmailMessage",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 652,
"nested_scroll": false,
"painted": 56
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 568,
"nested_scroll": false,
"painted": 56
}
},
"buttons": [
"Send",
"Later",
"Reply",
"Forward"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-message-720.png",
"threw": null
},
{
"kind": "gmail-thread",
"family": "gmail",
"member": "thread",
"component": "GmailThread",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 76,
"drew": "drew",
"height": 907,
"nested_scroll": false,
"painted": 122
},
"720": {
"first_paint_ms": 60,
"drew": "drew",
"height": 797,
"nested_scroll": false,
"painted": 122
}
},
"buttons": [
"Discard",
"Send",
"Later"
],
"press": {
"word": "Discard",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-thread-720.png",
"threw": null
},
{
"kind": "gmail-draft",
"family": "gmail",
"member": "compose",
"component": "EmailPreview",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 93,
"drew": "drew",
"height": 492,
"nested_scroll": false,
"painted": 29
},
"720": {
"first_paint_ms": 31,
"drew": "drew",
"height": 462,
"nested_scroll": false,
"painted": 29
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-draft-720.png",
"threw": null
},
{
"kind": "gmail-reply",
"family": "gmail",
"member": "compose",
"component": "GmailReply",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 11,
"drew": "drew",
"height": 834,
"nested_scroll": false,
"painted": 124
},
"720": {
"first_paint_ms": 31,
"drew": "drew",
"height": 808,
"nested_scroll": false,
"painted": 124
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-reply-720.png",
"threw": null
},
{
"kind": "gmail-decision",
"family": "gmail",
"member": "decision",
"component": "GmailDecision",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 90,
"drew": "drew",
"height": 802,
"nested_scroll": false,
"painted": 123
},
"720": {
"first_paint_ms": 103,
"drew": "drew",
"height": 758,
"nested_scroll": false,
"painted": 123
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-decision-720.png",
"threw": null
},
{
"kind": "gmail-bulk-change",
"family": "gmail",
"member": "decision",
"component": "GmailBulkChange",
"skill": "snappy-gmail",
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 339,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 11,
"drew": "drew",
"height": 333,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/gmail-bulk-change-720.png",
"threw": null
},
{
"kind": "linkedin-feed",
"family": "linkedin",
"member": "list",
"component": "LinkedInFeed",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 62,
"drew": "drew",
"height": 919,
"nested_scroll": false,
"painted": 119
},
"720": {
"first_paint_ms": 40,
"drew": "drew",
"height": 867,
"nested_scroll": false,
"painted": 119
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-feed-720.png",
"threw": null
},
{
"kind": "linkedin-post",
"family": "linkedin",
"member": "one",
"component": "LinkedInPostPreview",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 53,
"drew": "drew",
"height": 363,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 71,
"drew": "drew",
"height": 353,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [
"Publish",
"Later"
],
"press": {
"word": "Publish",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-post-720.png",
"threw": null
},
{
"kind": "linkedin-comments",
"family": "linkedin",
"member": "thread",
"component": "LinkedInCommentThread",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 25,
"drew": "drew",
"height": 504,
"nested_scroll": false,
"painted": 48
},
"720": {
"first_paint_ms": 70,
"drew": "drew",
"height": 456,
"nested_scroll": false,
"painted": 48
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-comments-720.png",
"threw": null
},
{
"kind": "linkedin-profile",
"family": "linkedin",
"member": "profile",
"component": "LinkedInProfileCard",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 62,
"drew": "drew",
"height": 295,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 59,
"drew": "drew",
"height": 311,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-profile-720.png",
"threw": null
},
{
"kind": "linkedin-decision",
"family": "linkedin",
"member": "decision",
"component": "LinkedInDecision",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 982,
"nested_scroll": false,
"painted": 70
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 822,
"nested_scroll": false,
"painted": 70
}
},
"buttons": [
"Publishgoes live on your LinkedIn now",
"Laterkeeps it staged; the badge keeps counting"
],
"press": {
"word": "Publishgoes live on your LinkedIn now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-decision-720.png",
"threw": null
},
{
"kind": "slack-list",
"family": "slack",
"member": "list",
"component": "SlackMessageList",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 84,
"drew": "drew",
"height": 341,
"nested_scroll": false,
"painted": 46
},
"720": {
"first_paint_ms": 31,
"drew": "drew",
"height": 351,
"nested_scroll": false,
"painted": 46
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-list-720.png",
"threw": null
},
{
"kind": "slack-draft",
"family": "slack",
"member": "one",
"component": "SlackMessagePreview",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 21,
"drew": "drew",
"height": 252,
"nested_scroll": false,
"painted": 24
},
"720": {
"first_paint_ms": 36,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 24
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-draft-720.png",
"threw": null
},
{
"kind": "slack-channels",
"family": "slack",
"member": "list",
"component": "SlackChannelList",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 100,
"drew": "drew",
"height": 325,
"nested_scroll": false,
"painted": 29
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 357,
"nested_scroll": false,
"painted": 29
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-channels-720.png",
"threw": null
},
{
"kind": "slack-message",
"family": "slack",
"member": "one",
"component": "SlackMessage",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 251,
"nested_scroll": false,
"painted": 27
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 27
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-message-720.png",
"threw": null
},
{
"kind": "slack-thread",
"family": "slack",
"member": "thread",
"component": "SlackThread",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 33,
"drew": "drew",
"height": 472,
"nested_scroll": false,
"painted": 64
},
"720": {
"first_paint_ms": 53,
"drew": "drew",
"height": 460,
"nested_scroll": false,
"painted": 64
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-thread-720.png",
"threw": null
},
{
"kind": "slack-decision",
"family": "slack",
"member": "decision",
"component": "SlackDecision",
"skill": "snappy-slack",
"widths": {
"360": {
"first_paint_ms": 95,
"drew": "drew",
"height": 756,
"nested_scroll": false,
"painted": 94
},
"720": {
"first_paint_ms": 37,
"drew": "drew",
"height": 683,
"nested_scroll": false,
"painted": 94
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/slack-decision-720.png",
"threw": null
},
{
"kind": "telegram-list",
"family": "telegram",
"member": "list",
"component": "TelegramChatList",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 157,
"drew": "drew",
"height": 388,
"nested_scroll": false,
"painted": 26
},
"720": {
"first_paint_ms": 38,
"drew": "drew",
"height": 360,
"nested_scroll": false,
"painted": 26
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-list-720.png",
"threw": null
},
{
"kind": "telegram-message",
"family": "telegram",
"member": "one",
"component": "TelegramBubble",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 25,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 8
},
"720": {
"first_paint_ms": 11,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 8
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-message-720.png",
"threw": null
},
{
"kind": "telegram-draft",
"family": "telegram",
"member": "one",
"component": "TelegramMessagePreview",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 48,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
},
"720": {
"first_paint_ms": 55,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-draft-720.png",
"threw": null
},
{
"kind": "telegram-decision",
"family": "telegram",
"member": "decision",
"component": "TelegramDecision",
"skill": "snappy-telegram",
"widths": {
"360": {
"first_paint_ms": 15,
"drew": "drew",
"height": 256,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 249,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/telegram-decision-720.png",
"threw": null
},
{
"kind": "whatsapp-list",
"family": "whatsapp",
"member": "list",
"component": "WhatsAppChatList",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 76,
"drew": "drew",
"height": 267,
"nested_scroll": false,
"painted": 31
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 299,
"nested_scroll": false,
"painted": 31
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-list-720.png",
"threw": null
},
{
"kind": "whatsapp-message",
"family": "whatsapp",
"member": "one",
"component": "WhatsAppBubble",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
},
"720": {
"first_paint_ms": 33,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-message-720.png",
"threw": null
},
{
"kind": "whatsapp-thread",
"family": "whatsapp",
"member": "thread",
"component": "WhatsAppThread",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 101,
"drew": "drew",
"height": 421,
"nested_scroll": false,
"painted": 32
},
"720": {
"first_paint_ms": 68,
"drew": "drew",
"height": 375,
"nested_scroll": false,
"painted": 32
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-thread-720.png",
"threw": null
},
{
"kind": "whatsapp-compose",
"family": "whatsapp",
"member": "compose",
"component": "WhatsAppCompose",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 66,
"drew": "drew",
"height": 639,
"nested_scroll": false,
"painted": 47
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 556,
"nested_scroll": false,
"painted": 47
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-compose-720.png",
"threw": null
},
{
"kind": "whatsapp-decision",
"family": "whatsapp",
"member": "decision",
"component": "WhatsAppDecision",
"skill": "snappy-whatsapp",
"widths": {
"360": {
"first_paint_ms": 98,
"drew": "drew",
"height": 683,
"nested_scroll": false,
"painted": 53
},
"720": {
"first_paint_ms": 49,
"drew": "drew",
"height": 600,
"nested_scroll": false,
"painted": 53
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/whatsapp-decision-720.png",
"threw": null
},
{
"kind": "imessage-message",
"family": "imessage",
"member": "one",
"component": "IMessageBubble",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 93,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
},
"720": {
"first_paint_ms": 66,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 7
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-message-720.png",
"threw": null
},
{
"kind": "imessage-thread",
"family": "imessage",
"member": "thread",
"component": "IMessageThread",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 115,
"drew": "drew",
"height": 357,
"nested_scroll": false,
"painted": 18
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 328,
"nested_scroll": false,
"painted": 18
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-thread-720.png",
"threw": null
},
{
"kind": "imessage-compose",
"family": "imessage",
"member": "compose",
"component": "IMessageCompose",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 131,
"drew": "drew",
"height": 550,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 56,
"drew": "drew",
"height": 482,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-compose-720.png",
"threw": null
},
{
"kind": "imessage-decision",
"family": "imessage",
"member": "decision",
"component": "IMessageDecision",
"skill": "snappy-imessage",
"widths": {
"360": {
"first_paint_ms": 23,
"drew": "drew",
"height": 594,
"nested_scroll": false,
"painted": 39
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 526,
"nested_scroll": false,
"painted": 39
}
},
"buttons": [
"Send",
"Later"
],
"press": {
"word": "Send",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imessage-decision-720.png",
"threw": null
},
{
"kind": "freshbooks-list",
"family": "freshbooks",
"member": "list",
"component": "FreshBooksInvoiceList",
"skill": "snappy-freshbooks",
"widths": {
"360": {
"first_paint_ms": 143,
"drew": "drew",
"height": 424,
"nested_scroll": false,
"painted": 48
},
"720": {
"first_paint_ms": 178,
"drew": "drew",
"height": 368,
"nested_scroll": false,
"painted": 48
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/freshbooks-list-720.png",
"threw": null
},
{
"kind": "freshbooks-invoice",
"family": "freshbooks",
"member": "one",
"component": "FreshBooksInvoicePreview",
"skill": "snappy-freshbooks",
"widths": {
"360": {
"first_paint_ms": 25,
"drew": "drew",
"height": 479,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 439,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/freshbooks-invoice-720.png",
"threw": null
},
{
"kind": "freshbooks-decision",
"family": "freshbooks",
"member": "decision",
"component": "FreshBooksDecision",
"skill": "snappy-freshbooks",
"widths": {
"360": {
"first_paint_ms": 150,
"drew": "drew",
"height": 606,
"nested_scroll": false,
"painted": 52
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 526,
"nested_scroll": false,
"painted": 52
}
},
"buttons": [
"Send invoiceemails it to Quillworks for $1,250.00 CAD now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Send invoiceemails it to Quillworks for $1,250.00 CAD now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/freshbooks-decision-720.png",
"threw": null
},
{
"kind": "skool-feed",
"family": "skool",
"member": "list",
"component": "SkoolFeed",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 66,
"drew": "drew",
"height": 636,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 201,
"drew": "drew",
"height": 649,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-feed-720.png",
"threw": null
},
{
"kind": "skool-post",
"family": "skool",
"member": "one",
"component": "SkoolPostPreview",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 335,
"drew": "drew",
"height": 374,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 103,
"drew": "drew",
"height": 347,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-post-720.png",
"threw": null
},
{
"kind": "skool-comments",
"family": "skool",
"member": "thread",
"component": "SkoolCommentThread",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 52,
"drew": "drew",
"height": 547,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 475,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-comments-720.png",
"threw": null
},
{
"kind": "skool-classroom",
"family": "skool",
"member": "one",
"component": "SkoolClassroomModule",
"skill": "snappy-skool",
"widths": {
"360": {
"first_paint_ms": 111,
"drew": "drew",
"height": 630,
"nested_scroll": false,
"painted": 35
},
"720": {
"first_paint_ms": 71,
"drew": "drew",
"height": 725,
"nested_scroll": false,
"painted": 35
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/skool-classroom-720.png",
"threw": null
},
{
"kind": "github-pr",
"family": "github",
"member": "one",
"component": "GitHubPRPreview",
"skill": "snappy-github",
"widths": {
"360": {
"first_paint_ms": 131,
"drew": "drew",
"height": 359,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 314,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/github-pr-720.png",
"threw": null
},
{
"kind": "stripe-payment",
"family": "stripe",
"member": "one",
"component": "PaymentPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 164,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 15
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/stripe-payment-720.png",
"threw": null
},
{
"kind": "sheets-rows",
"family": "sheets",
"member": "list",
"component": "SheetRowsPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 77,
"drew": "drew",
"height": 384,
"nested_scroll": false,
"painted": 35
},
"720": {
"first_paint_ms": 22,
"drew": "drew",
"height": 344,
"nested_scroll": false,
"painted": 35
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/sheets-rows-720.png",
"threw": null
},
{
"kind": "media-video",
"family": "media",
"member": "one",
"component": "VideoPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 159,
"drew": "drew",
"height": 777,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 771,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [
"Publishuploads the cut and makes it visible",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Publishuploads the cut and makes it visible",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-video-720.png",
"threw": null
},
{
"kind": "media-document",
"family": "media",
"member": "one",
"component": "DocumentView",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 613,
"nested_scroll": false,
"painted": 10
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 512,
"nested_scroll": false,
"painted": 10
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-document-720.png",
"threw": null
},
{
"kind": "media-image",
"family": "media",
"member": "one",
"component": "ImagePreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 68,
"drew": "drew",
"height": 403,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 527,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-image-720.png",
"threw": null
},
{
"kind": "media-pdf",
"family": "media",
"member": "one",
"component": "PdfPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 23,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 10
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 10
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-pdf-720.png",
"threw": null
},
{
"kind": "media-slides",
"family": "media",
"member": "one",
"component": "SlidesPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 457,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 538,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [
"Where the build stands",
"What changed since July",
"The two decisions we need",
"Cost and timeline"
],
"press": {
"word": "Where the build stands",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-slides-720.png",
"threw": null
},
{
"kind": "media-html",
"family": "media",
"member": "one",
"component": "HTMLPreview",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 420,
"nested_scroll": false,
"painted": 16
},
"720": {
"first_paint_ms": 32,
"drew": "drew",
"height": 452,
"nested_scroll": false,
"painted": 16
}
},
"buttons": [
"Preview",
"Code",
"Save as Artifact"
],
"press": {
"word": "Preview",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-html-720.png",
"threw": null
},
{
"kind": "media-google-doc",
"family": "media",
"member": "one",
"component": "GoogleDoc",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 2424,
"nested_scroll": false,
"painted": 27
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 824,
"nested_scroll": false,
"painted": 27
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/media-google-doc-720.png",
"threw": null
},
{
"kind": "calendar-event",
"family": "calendar",
"member": "one",
"component": "CalendarEventPreview",
"skill": "snappy-calendar",
"widths": {
"360": {
"first_paint_ms": 67,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 28
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 28
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/calendar-event-720.png",
"threw": null
},
{
"kind": "calendar-week",
"family": "calendar",
"member": "list",
"component": "CalendarWeekPreview",
"skill": "snappy-calendar",
"widths": {
"360": {
"first_paint_ms": 29,
"drew": "drew",
"height": 540,
"nested_scroll": false,
"painted": 121
},
"720": {
"first_paint_ms": 64,
"drew": "drew",
"height": 572,
"nested_scroll": false,
"painted": 121
}
},
"buttons": [
"Mon7",
"Tue8",
"Wed9",
"Thu10",
"Fri11",
"Sat12",
"Sun13",
"Daily Standup, 9am",
"Design Critique1:00 PM",
"Engineering Sync11:00 AM",
"Stakeholder Update3:00 PM"
],
"press": {
"word": "Mon7",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/calendar-week-720.png",
"threw": null
},
{
"kind": "x-post",
"family": "x",
"member": "one",
"component": "XPost",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 44,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 31
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 31
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/x-post-720.png",
"threw": null
},
{
"kind": "x-thread",
"family": "x",
"member": "thread",
"component": "XThread",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 21,
"drew": "drew",
"height": 479,
"nested_scroll": false,
"painted": 98
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 433,
"nested_scroll": false,
"painted": 98
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/x-thread-720.png",
"threw": null
},
{
"kind": "x-decision",
"family": "x",
"member": "decision",
"component": "XDecision",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 824,
"nested_scroll": false,
"painted": 119
},
"720": {
"first_paint_ms": 14,
"drew": "drew",
"height": 706,
"nested_scroll": false,
"painted": 119
}
},
"buttons": [
"Publishgoes live on your X account now",
"Laterkeeps it staged; the badge keeps counting"
],
"press": {
"word": "Publishgoes live on your X account now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/x-decision-720.png",
"threw": null
},
{
"kind": "youtube-package",
"family": "youtube",
"member": "compose",
"component": "YouTubeVideoPackage",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 220,
"drew": "drew",
"height": 719,
"nested_scroll": false,
"painted": 31
},
"720": {
"first_paint_ms": 12,
"drew": "drew",
"height": 762,
"nested_scroll": false,
"painted": 31
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-package-720.png",
"threw": null
},
{
"kind": "youtube-video",
"family": "youtube",
"member": "one",
"component": "YouTubeVideoCard",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 310,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 355,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-video-720.png",
"threw": null
},
{
"kind": "youtube-comments",
"family": "youtube",
"member": "thread",
"component": "YouTubeCommentThread",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 626,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 15,
"drew": "drew",
"height": 536,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-comments-720.png",
"threw": null
},
{
"kind": "youtube-channel",
"family": "youtube",
"member": "profile",
"component": "YouTubeChannelHeader",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 32,
"drew": "drew",
"height": 458,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 316,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-channel-720.png",
"threw": null
},
{
"kind": "instagram-post",
"family": "instagram",
"member": "one",
"component": "InstagramPost",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 159,
"drew": "drew",
"height": 686,
"nested_scroll": false,
"painted": 36
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 832,
"nested_scroll": false,
"painted": 36
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-post-720.png",
"threw": null
},
{
"kind": "instagram-carousel",
"family": "instagram",
"member": "compose",
"component": "InstagramCarousel",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 26,
"drew": "drew",
"height": 606,
"nested_scroll": false,
"painted": 50
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 616,
"nested_scroll": false,
"painted": 50
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-carousel-720.png",
"threw": null
},
{
"kind": "instagram-reel",
"family": "instagram",
"member": "one",
"component": "InstagramReelCover",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 556,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 588,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-reel-720.png",
"threw": null
},
{
"kind": "instagram-grid",
"family": "instagram",
"member": "list",
"component": "InstagramGrid",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 22,
"drew": "drew",
"height": 295,
"nested_scroll": false,
"painted": 22
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 416,
"nested_scroll": false,
"painted": 22
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/instagram-grid-720.png",
"threw": null
},
{
"kind": "notion-page",
"family": "notion",
"member": "one",
"component": "NotionPage",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 126,
"drew": "drew",
"height": 1153,
"nested_scroll": false,
"painted": 42
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 882,
"nested_scroll": false,
"painted": 42
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-page-720.png",
"threw": null
},
{
"kind": "notion-pages",
"family": "notion",
"member": "list",
"component": "NotionPageList",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 71,
"drew": "drew",
"height": 317,
"nested_scroll": false,
"painted": 27
},
"720": {
"first_paint_ms": 74,
"drew": "drew",
"height": 328,
"nested_scroll": false,
"painted": 27
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-pages-720.png",
"threw": null
},
{
"kind": "linkedin-post-published",
"family": "linkedin",
"member": "one",
"component": "LinkedInPost",
"skill": "snappy-linkedin",
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 668,
"nested_scroll": false,
"painted": 23
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 728,
"nested_scroll": false,
"painted": 23
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/linkedin-post-published-720.png",
"threw": null
},
{
"kind": "blocks-options",
"family": "blocks",
"member": "decision",
"component": "OptionsCard",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 140,
"drew": "drew",
"height": 669,
"nested_scroll": false,
"painted": 34
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 490,
"nested_scroll": false,
"painted": 34
}
},
"buttons": [
"Use this one",
"Later"
],
"press": {
"word": "Use this one",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/blocks-options-720.png",
"threw": null
},
{
"kind": "blocks-audio",
"family": "blocks",
"member": "one",
"component": "AudioPlayer",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 36,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 4
},
"720": {
"first_paint_ms": 25,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 4
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/blocks-audio-720.png",
"threw": null
},
{
"kind": "blocks-repair",
"family": "blocks",
"member": "one",
"component": "RepairState",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 307,
"nested_scroll": false,
"painted": 19
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 269,
"nested_scroll": false,
"painted": 19
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/blocks-repair-720.png",
"threw": null
},
{
"kind": "statechange-feed",
"family": "statechange",
"member": "list",
"component": "StatechangeFeed",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 77,
"drew": "drew",
"height": 2424,
"nested_scroll": false,
"painted": 235
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 2429,
"nested_scroll": false,
"painted": 235
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-feed-720.png",
"threw": null
},
{
"kind": "statechange-post",
"family": "statechange",
"member": "one",
"component": "StatechangePost",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 43,
"drew": "drew",
"height": 401,
"nested_scroll": false,
"painted": 12
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 383,
"nested_scroll": false,
"painted": 12
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-post-720.png",
"threw": null
},
{
"kind": "statechange-thread",
"family": "statechange",
"member": "thread",
"component": "StatechangeThread",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 791,
"nested_scroll": false,
"painted": 54
},
"720": {
"first_paint_ms": 22,
"drew": "drew",
"height": 746,
"nested_scroll": false,
"painted": 54
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-thread-720.png",
"threw": null
},
{
"kind": "statechange-unread",
"family": "statechange",
"member": "list",
"component": "StatechangeUnread",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 14,
"drew": "drew",
"height": 417,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 466,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-unread-720.png",
"threw": null
},
{
"kind": "data-chart",
"family": "data",
"member": "one",
"component": "Chart",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 53,
"drew": "drew",
"height": 400,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 423,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/data-chart-720.png",
"threw": null
},
{
"kind": "data-table",
"family": "data",
"member": "list",
"component": "DataTable",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 1295,
"nested_scroll": false,
"painted": 136
},
"720": {
"first_paint_ms": 28,
"drew": "drew",
"height": 941,
"nested_scroll": false,
"painted": 136
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/data-table-720.png",
"threw": null
},
{
"kind": "code-diff",
"family": "code",
"member": "one",
"component": "CodeDiff",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 61,
"drew": "drew",
"height": 883,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 658,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/code-diff-720.png",
"threw": null
},
{
"kind": "agent-run",
"family": "agent",
"member": "one",
"component": "AgentRun",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 321,
"drew": "drew",
"height": 883,
"nested_scroll": false,
"painted": 50
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 778,
"nested_scroll": false,
"painted": 50
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/agent-run-720.png",
"threw": null
},
{
"kind": "agent-permission",
"family": "agent",
"member": "decision",
"component": "RunPermission",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 379,
"nested_scroll": false,
"painted": 13
},
"720": {
"first_paint_ms": 18,
"drew": "drew",
"height": 353,
"nested_scroll": false,
"painted": 13
}
},
"buttons": [
"Push it",
"Not yet"
],
"press": {
"word": "Push it",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/agent-permission-720.png",
"threw": null
},
{
"kind": "thumbnails-set",
"family": "thumbnails",
"member": "list",
"component": "ThumbnailSet",
"skill": "snappy-thumbnails",
"widths": {
"360": {
"first_paint_ms": 154,
"drew": "drew",
"height": 610,
"nested_scroll": false,
"painted": 38
},
"720": {
"first_paint_ms": 12,
"drew": "drew",
"height": 521,
"nested_scroll": false,
"painted": 38
}
},
"buttons": [
"no imageClean pass · chosenthe number first — 41,000 rows, no repair",
"no imageThe slow sourceone bar taller than the rest",
"no imageBefore / aftertwo runs side by side",
"no imageMara at the boarda face, no number",
"no imageTerminal greenthe replay output, unedited",
"no imagePlain titlewords only, largest type"
],
"press": {
"word": "no imageClean pass · chosenthe number first — 41,000 rows, no repair",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/thumbnails-set-720.png",
"threw": null
},
{
"kind": "deploy-receipt",
"family": "deploy",
"member": "one",
"component": "DeployReceipt",
"skill": "snappy-deploy",
"widths": {
"360": {
"first_paint_ms": 100,
"drew": "drew",
"height": 402,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 16,
"drew": "drew",
"height": 342,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/deploy-receipt-720.png",
"threw": null
},
{
"kind": "schedule-pick",
"family": "schedule",
"member": "decision",
"component": "SchedulePick",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 175,
"drew": "drew",
"height": 454,
"nested_scroll": false,
"painted": 32
},
"720": {
"first_paint_ms": 13,
"drew": "drew",
"height": 472,
"nested_scroll": false,
"painted": 32
}
},
"buttons": [
"Hold this time",
"Later"
],
"press": {
"word": "Hold this time",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/schedule-pick-720.png",
"threw": null
},
{
"kind": "krisp-meeting",
"family": "krisp",
"member": "one",
"component": "KrispMeeting",
"skill": "snappy-krisp",
"widths": {
"360": {
"first_paint_ms": 56,
"drew": "drew",
"height": 504,
"nested_scroll": false,
"painted": 21
},
"720": {
"first_paint_ms": 51,
"drew": "drew",
"height": 319,
"nested_scroll": false,
"painted": 21
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/krisp-meeting-720.png",
"threw": null
},
{
"kind": "krisp-hits",
"family": "krisp",
"member": "list",
"component": "KrispTranscriptHits",
"skill": "snappy-krisp",
"widths": {
"360": {
"first_paint_ms": 23,
"drew": "drew",
"height": 708,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 14,
"drew": "drew",
"height": 479,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/krisp-hits-720.png",
"threw": null
},
{
"kind": "krisp-actions",
"family": "krisp",
"member": "list",
"component": "KrispActionItems",
"skill": "snappy-krisp",
"widths": {
"360": {
"first_paint_ms": 13,
"drew": "drew",
"height": 616,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 21,
"drew": "drew",
"height": 459,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/krisp-actions-720.png",
"threw": null
},
{
"kind": "knowledge-hits",
"family": "knowledge",
"member": "list",
"component": "KnowledgeHits",
"skill": "snappy-knowledge",
"widths": {
"360": {
"first_paint_ms": 52,
"drew": "drew",
"height": 703,
"nested_scroll": false,
"painted": 33
},
"720": {
"first_paint_ms": 12,
"drew": "drew",
"height": 529,
"nested_scroll": false,
"painted": 33
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/knowledge-hits-720.png",
"threw": null
},
{
"kind": "knowledge-contact",
"family": "knowledge",
"member": "profile",
"component": "KnowledgeContact",
"skill": "snappy-knowledge",
"widths": {
"360": {
"first_paint_ms": 10,
"drew": "drew",
"height": 460,
"nested_scroll": false,
"painted": 24
},
"720": {
"first_paint_ms": 17,
"drew": "drew",
"height": 339,
"nested_scroll": false,
"painted": 24
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/knowledge-contact-720.png",
"threw": null
},
{
"kind": "knowledge-company",
"family": "knowledge",
"member": "profile",
"component": "KnowledgeCompany",
"skill": "snappy-knowledge",
"widths": {
"360": {
"first_paint_ms": 18,
"drew": "drew",
"height": 412,
"nested_scroll": false,
"painted": 25
},
"720": {
"first_paint_ms": 28,
"drew": "drew",
"height": 339,
"nested_scroll": false,
"painted": 25
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/knowledge-company-720.png",
"threw": null
},
{
"kind": "transcripts-nugget",
"family": "transcripts",
"member": "one",
"component": "TranscriptNugget",
"skill": "snappy-transcripts",
"widths": {
"360": {
"first_paint_ms": 58,
"drew": "drew",
"height": 335,
"nested_scroll": false,
"painted": 9
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 240,
"nested_scroll": false,
"painted": 9
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/transcripts-nugget-720.png",
"threw": null
},
{
"kind": "transcripts-hits",
"family": "transcripts",
"member": "list",
"component": "TranscriptHits",
"skill": "snappy-transcripts",
"widths": {
"360": {
"first_paint_ms": 28,
"drew": "drew",
"height": 640,
"nested_scroll": false,
"painted": 30
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 502,
"nested_scroll": false,
"painted": 30
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/transcripts-hits-720.png",
"threw": null
},
{
"kind": "imageset-grid",
"family": "imageset",
"member": "decision",
"component": "ImageSetGrid",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 124,
"drew": "drew",
"height": 1485,
"nested_scroll": false,
"painted": 43
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 695,
"nested_scroll": false,
"painted": 43
}
},
"buttons": [
"Use the kept two2 of 4 kept",
"Laterkeeps them staged; nothing leaves this machine"
],
"press": {
"word": "Use the kept two2 of 4 kept",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/imageset-grid-720.png",
"threw": null
},
{
"kind": "walkthrough-steps",
"family": "walkthrough",
"member": "list",
"component": "WalkthroughSteps",
"skill": "snappy-walkthrough",
"widths": {
"360": {
"first_paint_ms": 102,
"drew": "drew",
"height": 689,
"nested_scroll": false,
"painted": 40
},
"720": {
"first_paint_ms": 19,
"drew": "drew",
"height": 653,
"nested_scroll": false,
"painted": 40
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/walkthrough-steps-720.png",
"threw": null
},
{
"kind": "doc-card",
"family": "doc",
"member": "one",
"component": "DocCard",
"skill": null,
"widths": {
"360": {
"first_paint_ms": 38,
"drew": "drew",
"height": 422,
"nested_scroll": false,
"painted": 15
},
"720": {
"first_paint_ms": 24,
"drew": "drew",
"height": 331,
"nested_scroll": false,
"painted": 15
}
},
"buttons": [
"Publishposts to Harbourline — 1,240 members",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Publishposts to Harbourline — 1,240 members",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/doc-card-720.png",
"threw": null
},
{
"kind": "statechange-message",
"family": "statechange",
"member": "compose",
"component": "StatechangeMessage",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 14,
"drew": "drew",
"height": 426,
"nested_scroll": false,
"painted": 18
},
"720": {
"first_paint_ms": 9,
"drew": "drew",
"height": 335,
"nested_scroll": false,
"painted": 18
}
},
"buttons": [
"Sendsends into https://pro.statechange.ai/messages/8891 now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Sendsends into https://pro.statechange.ai/messages/8891 now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-message-720.png",
"threw": null
},
{
"kind": "statechange-decision",
"family": "statechange",
"member": "decision",
"component": "StatechangeDecision",
"skill": "snappy-statechange",
"widths": {
"360": {
"first_paint_ms": 14,
"drew": "drew",
"height": 704,
"nested_scroll": false,
"painted": 42
},
"720": {
"first_paint_ms": 10,
"drew": "drew",
"height": 637,
"nested_scroll": false,
"painted": 42
}
},
"buttons": [
"Postpublishes to Quillworks now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Postpublishes to Quillworks now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/statechange-decision-720.png",
"threw": null
},
{
"kind": "github-comments",
"family": "github",
"member": "thread",
"component": "GitHubCommentThread",
"skill": "snappy-github",
"widths": {
"360": {
"first_paint_ms": 20,
"drew": "drew",
"height": 590,
"nested_scroll": false,
"painted": 34
},
"720": {
"first_paint_ms": 23,
"drew": "drew",
"height": 463,
"nested_scroll": false,
"painted": 34
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/github-comments-720.png",
"threw": null
},
{
"kind": "github-comment",
"family": "github",
"member": "compose",
"component": "GitHubComment",
"skill": "snappy-github",
"widths": {
"360": {
"first_paint_ms": 16,
"drew": "drew",
"height": 885,
"nested_scroll": false,
"painted": 44
},
"720": {
"first_paint_ms": 10,
"drew": "drew",
"height": 684,
"nested_scroll": false,
"painted": 44
}
},
"buttons": [
"Commentposts the comment on quillworks/atlas#218, where the whole team reads it, now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Commentposts the comment on quillworks/atlas#218, where the whole team reads it, now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/github-comment-720.png",
"threw": null
},
{
"kind": "youtube-reply",
"family": "youtube",
"member": "compose",
"component": "YouTubeReply",
"skill": "snappy-youtube",
"widths": {
"360": {
"first_paint_ms": 24,
"drew": "drew",
"height": 688,
"nested_scroll": false,
"painted": 45
},
"720": {
"first_paint_ms": 16,
"drew": "drew",
"height": 573,
"nested_scroll": false,
"painted": 45
}
},
"buttons": [
"Replyposts the reply publicly under comment UgxK1QuillworksAaBb now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Replyposts the reply publicly under comment UgxK1QuillworksAaBb now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/youtube-reply-720.png",
"threw": null
},
{
"kind": "notion-blocks",
"family": "notion",
"member": "thread",
"component": "NotionBlocks",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 19,
"drew": "drew",
"height": 541,
"nested_scroll": false,
"painted": 24
},
"720": {
"first_paint_ms": 10,
"drew": "drew",
"height": 425,
"nested_scroll": false,
"painted": 24
}
},
"buttons": [],
"press": null,
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-blocks-720.png",
"threw": null
},
{
"kind": "notion-append",
"family": "notion",
"member": "compose",
"component": "NotionAppend",
"skill": "snappy-notion",
"widths": {
"360": {
"first_paint_ms": 11,
"drew": "drew",
"height": 807,
"nested_scroll": false,
"painted": 32
},
"720": {
"first_paint_ms": 20,
"drew": "drew",
"height": 642,
"nested_scroll": false,
"painted": 32
}
},
"buttons": [
"Appendwrites the paragraph to the end of \"Harbourline import — decisions\" now",
"Laterkeeps it staged; nothing leaves this machine"
],
"press": {
"word": "Appendwrites the paragraph to the end of \"Harbourline import — decisions\" now",
"reached_execute": false,
"outcome": null,
"server_ms": null
},
"shot": "/private/tmp/claude-501/-Users-robertboulos-Projects-snappy-os-app/b70ebaae-5289-47e7-bef5-ad79bc53c2a5/scratchpad/tool-loop/render-host/shots/notion-append-720.png",
"threw": null
}
],
"families": {
"gmail": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"linkedin": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"slack": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"telegram": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"whatsapp": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"imessage": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"freshbooks": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"skool": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"github": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"stripe": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"sheets": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"media": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"calendar": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"x": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"youtube": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"instagram": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"notion": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"blocks": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"statechange": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"data": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"code": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"agent": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"thumbnails": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"deploy": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"schedule": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"krisp": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"knowledge": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"transcripts": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"imageset": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"walkthrough": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
},
"doc": {
"auto_fit": true,
"action_item_bounds": true,
"handshake_race_free": true,
"dated_real_host_screenshot": true
}
},
"skill_families": {
"snappy-calendar": [
"calendar"
],
"snappy-deploy": [
"deploy"
],
"snappy-faces": [
"gmail",
"linkedin",
"slack",
"telegram",
"whatsapp",
"imessage",
"freshbooks",
"skool",
"github",
"stripe",
"sheets",
"media",
"calendar",
"x",
"youtube",
"instagram",
"notion",
"blocks",
"statechange",
"data",
"code",
"agent",
"thumbnails",
"deploy",
"schedule",
"krisp",
"knowledge",
"transcripts",
"imageset",
"walkthrough",
"doc"
],
"snappy-freshbooks": [
"freshbooks"
],
"snappy-github": [
"github"
],
"snappy-gmail": [
"gmail"
],
"snappy-imessage": [
"imessage"
],
"snappy-knowledge": [
"knowledge"
],
"snappy-krisp": [
"krisp"
],
"snappy-linkedin": [
"linkedin"
],
"snappy-notion": [
"notion"
],
"snappy-skool": [
"skool"
],
"snappy-slack": [
"slack"
],
"snappy-statechange": [
"statechange"
],
"snappy-telegram": [
"telegram"
],
"snappy-thumbnails": [
"thumbnails"
],
"snappy-transcripts": [
"transcripts"
],
"snappy-walkthrough": [
"walkthrough"
],
"snappy-whatsapp": [
"whatsapp"
],
"snappy-youtube": [
"youtube"
]
}
}{
"written_at": "2026-09-09T20:47:52.526Z",
"skills": {
"snappy-gmail": {
"skill": "snappy-gmail",
"kind": "discovery",
"url": "https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest",
"family": "discovery",
"version": "20260903",
"sha256": "a711e5ef72b8453d1885688eab0ee89f2ea1181f17a272f3093bf0f6cf0fc9ee",
"fetched_at": "2026-09-09T20:47:52.220Z",
"operations": {
"list": {
"id": "gmail.users.messages.list",
"present": true
},
"get": {
"id": "gmail.users.messages.get",
"present": true
},
"thread": {
"id": "gmail.users.threads.get",
"present": true
},
"draft": {
"id": "gmail.users.drafts.create",
"present": true
},
"reply": {
"id": "gmail.users.messages.send",
"present": true
},
"send": {
"id": "gmail.users.messages.send",
"present": true
},
"archive": {
"id": "gmail.users.messages.modify",
"present": true
}
},
"operations_total": 79,
"enums": {
"format": [
"full",
"metadata",
"minimal",
"raw"
]
}
},
"snappy-image": {
"skill": "snappy-image",
"kind": "openapi",
"url": "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.json",
"family": "openapi",
"version": "2.3.0",
"sha256": "8f55897d52dcad8a2321e636af59c4081fabf9809fbc0d42949d4d9ea8461837",
"fetched_at": "2026-09-09T20:47:52.488Z",
"operations": {
"generate": {
"id": "createImage",
"present": true
}
},
"operations_total": 289,
"enums": {
"model": [
"dall-e-2",
"dall-e-3",
"gpt-image-1",
"gpt-image-1-mini",
"gpt-image-1.5",
"gpt-image-2",
"gpt-image-2-2026-04-21",
"gpt-image-2.5-flare",
"gpt-image-2.5-flare-2026-09-08",
"gpt-image-2.5-sunburst",
"gpt-image-2.5-sunburst-2026-09-08"
],
"quality": [
"auto",
"hd",
"high",
"low",
"max",
"medium",
"standard",
"xhigh"
],
"response_format": [
"b64_json",
"url"
],
"output_format": [
"jpeg",
"png",
"webp"
],
"size": [
"1024x1024",
"1024x1536",
"1024x1792",
"1536x1024",
"1792x1024",
"256x256",
"512x512",
"auto"
],
"moderation": [
"auto",
"low"
],
"background": [
"auto",
"opaque",
"transparent"
],
"style": [
"natural",
"vivid"
]
}
},
"snappy-slack": {
"skill": "snappy-slack",
"kind": "openapi",
"url": "https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json",
"family": "swagger",
"version": "1.7.0",
"sha256": "742a5c977180a829df8767cf57bc417d99b3713583aee83741efb9c08ca731e7",
"fetched_at": "2026-09-09T20:47:52.526Z",
"operations": {
"channels": {
"id": "conversations_list",
"present": true
},
"messages": {
"id": "conversations_history",
"present": true
},
"search": {
"id": "search_messages",
"present": true
},
"thread": {
"id": "conversations_replies",
"present": true
},
"send": {
"id": "chat_postMessage",
"present": true
},
"reply": {
"id": "chat_postMessage",
"present": true
},
"react": {
"id": "reactions_add",
"present": true
},
"edit": {
"id": "chat_update",
"present": true
},
"delete": {
"id": "chat_delete",
"present": true
}
},
"operations_total": 174,
"enums": {}
}
}
}
{
"written_at": "2026-09-09T20:47:52.526Z",
"skills": {
"snappy-gmail": {
"skill": "snappy-gmail",
"kind": "discovery",
"url": "https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest",
"family": "discovery",
"version": "20260903",
"sha256": "a711e5ef72b8453d1885688eab0ee89f2ea1181f17a272f3093bf0f6cf0fc9ee",
"fetched_at": "2026-09-09T20:47:52.220Z",
"operations": {
"list": {
"id": "gmail.users.messages.list",
"present": true
},
"get": {
"id": "gmail.users.messages.get",
"present": true
},
"thread": {
"id": "gmail.users.threads.get",
"present": true
},
"draft": {
"id": "gmail.users.drafts.create",
"present": true
},
"reply": {
"id": "gmail.users.messages.send",
"present": true
},
"send": {
"id": "gmail.users.messages.send",
"present": true
},
"archive": {
"id": "gmail.users.messages.modify",
"present": true
}
},
"operations_total": 79,
"enums": {
"format": [
"full",
"metadata",
"minimal",
"raw"
]
}
},
"snappy-image": {
"skill": "snappy-image",
"kind": "openapi",
"url": "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.json",
"family": "openapi",
"version": "2.3.0",
"sha256": "8f55897d52dcad8a2321e636af59c4081fabf9809fbc0d42949d4d9ea8461837",
"fetched_at": "2026-09-09T20:47:52.488Z",
"operations": {
"generate": {
"id": "createImage",
"present": true
}
},
"operations_total": 289,
"enums": {
"model": [
"dall-e-2",
"dall-e-3",
"gpt-image-1",
"gpt-image-1-mini",
"gpt-image-1.5",
"gpt-image-2",
"gpt-image-2-2026-04-21",
"gpt-image-2.5-flare",
"gpt-image-2.5-flare-2026-09-08",
"gpt-image-2.5-sunburst",
"gpt-image-2.5-sunburst-2026-09-08"
],
"quality": [
"auto",
"hd",
"high",
"low",
"max",
"medium",
"standard",
"xhigh"
],
"response_format": [
"b64_json",
"url"
],
"output_format": [
"jpeg",
"png",
"webp"
],
"size": [
"1024x1024",
"1024x1536",
"1024x1792",
"1536x1024",
"1792x1024",
"256x256",
"512x512",
"auto"
],
"moderation": [
"auto",
"low"
],
"background": [
"auto",
"opaque",
"transparent"
],
"style": [
"natural",
"vivid"
]
}
},
"snappy-slack": {
"skill": "snappy-slack",
"kind": "openapi",
"url": "https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json",
"family": "swagger",
"version": "1.7.0",
"sha256": "742a5c977180a829df8767cf57bc417d99b3713583aee83741efb9c08ca731e7",
"fetched_at": "2026-09-09T20:47:52.526Z",
"operations": {
"channels": {
"id": "conversations_list",
"present": true
},
"messages": {
"id": "conversations_history",
"present": true
},
"search": {
"id": "search_messages",
"present": true
},
"thread": {
"id": "conversations_replies",
"present": true
},
"send": {
"id": "chat_postMessage",
"present": true
},
"reply": {
"id": "chat_postMessage",
"present": true
},
"react": {
"id": "reactions_add",
"present": true
},
"edit": {
"id": "chat_update",
"present": true
},
"delete": {
"id": "chat_delete",
"present": true
}
},
"operations_total": 174,
"enums": {}
}
}
}