A signature read that failed after one byte looked like a one-byte text
file and was skipped. The pipeline now runs under pipefail, so the file is
listed as unverified instead.
When cut, sort or uniq is missing, Step 7 says the tally is unavailable.
The count does not depend on them.
Step 1 skipped binaries while the role promised to verify the whole project,
so a credential in a spreadsheet, a PDF or an embedded database got a clean
PASS and never showed up in the report.
Step 7 now lists every file that doesn't read as text, plus any directory it
can't enter, and the pipeline stops on that list until a person confirms. On
this repo that's a few dozen files, all images and video, which is why they're
warnings and not failures. It doesn't try to extract or scan them: that needs
nested-archive recursion and size bounds, and I'd rather list a file than
half-scan it.
Detection is a first-byte signature check plus a whole-file NUL/control-byte
test, with file(1) only widening the list. If a detector fails, the file is
listed as unverified.
Filenames are untrusted, so Step 7 writes the paths to a manifest beside the
project directory, for a person to open, and keeps them out of its stdout
and its report section. Beside and not inside, so a re-run's text scans
can't read it back. The script prints the count itself as its last line. A
scan problem, a failed write, a walk that dies early or a run that never
reaches that line gives an unknown count, and the gate treats unknown as a
stop, not as zero.
Step 1 no longer skips *.min.js.
Tests in tests/skills/sanitizer-unreadable.test.js. Editing SKILL.md changes
the skill registry digest, so skill-triggers@1.json carries the new value.
- Problem: main d30588f9 (integrate-reviewed-followups) added a skill and
bumped the skill-triggers registryDigest, conflicting with this branch's
own digest refresh for the plan-orchestrate SKILL.md edit.
- Fix: take main's manifest and recompute registryDigest over the merged
tree via scripts/lib/context-pack-registry.js; triggers, triggersDigest
and generatedAt untouched.
- Verification: environment reproduces both sides' committed digests on
their own trees; merged-tree digest a6b6bce7...; validate-context-profiles
293 skills OK; plan-orchestrate registry fixtures 8/8; full
node tests/run-all.js = 704 passed / 0 failed.
- Problem: the registry fixtures validated SKILL.md against the committed docs/COMMAND-REGISTRY.json but nothing in the file asserted the registry itself was fresh, so a standalone run passed against a stale registry (npm test does run command-registry:check first, but this file is also run on its own).
- Fix: rebuild the registry in memory via the exported generateRegistry() and assert deep equality with the committed JSON so drift fails as a regenerate hint with a field diff instead of passing silently; add one sentence to SKILL.md stating that a command retired from commands/ fails closed with the BLOCKED marker like a no-argument command.
- Verification: node tests/skills/plan-orchestrate-registry.test.js passes with the new check 8 (8/8); sabotage with a mutated registry description fails check 8 with the regenerate hint and a field diff.
- Problem: the emitted command does not consume the advertised agent chain
(each command owns its own execution behavior), and steps mapped to
argument-less commands were emitted as bare commands with no
machine-readable link to the intended step.
- Fix: adopt the small-router role — emit only commands whose documented
argument form can carry the [Plan: <path>#step-<id>] scope marker
(orch-*, /plan), fail closed with a BLOCKED entry and a manual-use note
for every other command, derive quoted argument forms from the command
files instead of an authoritative hand-written table, and drop the
agent-chain machinery entirely. Adds
tests/skills/plan-orchestrate-registry.test.js, which validates every
command the skill names against commands/<name>.md and
docs/COMMAND-REGISTRY.json and fails when they drift.
- Verification: node tests/skills/plan-orchestrate-registry.test.js (7
fixture groups pass; sabotage-checked against chain reintroduction,
wrong scope-carrying claim, and retired-command reference).
Review feedback on #2878. stdout is async when it is a pipe, which is exactly
how tests/run-all.js runs these files, and process.exit() does not wait for
pending writes — so exiting that way can drop the Passed:/Failed: lines the
aggregator totals, defeating the previous commit. The sibling
tests/ci/ito-*-skill.test.js files already use process.exitCode.
Still exits 1 on a broken counter (Passed: 4, Failed: 7) and 0 when clean.
Review feedback on #2878: the static assertions ran at the top level, so a
failure exited the process before the `Passed:`/`Failed:` lines. tests/run-all.js
totals those tokens, so the per-case counts were lost — it still went red via the
non-zero exit, but the granular numbers were not in the totals.
Route every case through the same `runTest()` wrapper the file already used for
the integration cases, and build the case list inside a try so a missing or
renamed `instinct-cli.py` / `start-observer.sh` is a reported failure rather than
a crash.
Reverting the fix now prints `Passed: 4, Failed: 7` and exits 1, naming all seven
broken expectations instead of stopping at the first.
`start-observer.sh status` globbed `*.yaml`, but the observer prompt tells the
analyzer to write `${INSTINCTS_DIR}/<id>.md` and the loader accepts
`.yaml`, `.yml`, and `.md` (ALLOWED_INSTINCT_EXTENSIONS in
scripts/instinct-cli.py). The one command an operator runs to confirm that
learning works therefore reported `Instincts: 0` on a healthy install — which
is indistinguishable from a silently dead observer, exactly the failure the
status check exists to surface.
Match the loader instead of one of its three extensions, and match how it
enumerates them: `Path.iterdir()` is top level only and `is_file()` skips
directories, so `-maxdepth 1 -type f`; `suffix.lower()` makes the comparison
case-insensitive, so `-iname`. `tr` drops the column padding BSD `wc` emits,
which is why the reported output read `Instincts: 0`.
Verified end to end against the shipped script with 3 `.md`, one `.yaml`, one
`.yml`, one `.YAML`, a `notes.txt`, and a nested `.md`: 1 before, 6 after —
the same six files the loader picks up.