feat(doctor): tamper-evident skills manifest + freshness guard (#159)

skills/ ships fat-markdown files that agents execute as instructions, but
nothing detected silent edits to them (#159). This adds lightweight tamper
EVIDENCE (a committed sha256 manifest), not a signature system:

- skills/skills.lock.json — committed manifest mapping every bundled file
  under skills/ to its sha256 (sorted relative paths, deterministic JSON,
  trailing newline; excludes itself from its own hash set).
- src/core/skills-integrity.ts — pure, unit-testable core: compute /
  render / verify(dir, manifest) -> { modified, missing, extra }.
- scripts/generate-skills-manifest.ts — regenerator
  (bun run scripts/generate-skills-manifest.ts).
- gbrain doctor check `skills_manifest_integrity` (SKILL group) — WARNS on
  drift, never fails or blocks; fail-safe ok/skip when no manifest is
  present at the resolved skills dir (user workspaces, compiled-binary
  installs far from the repo).
- scripts/check-skills-manifest-fresh.sh — CI freshness guard modeled on
  check-eval-glossary-fresh.sh; wired as `check:skills-manifest` in
  package.json and into bun run verify (run-verify-parallel.sh).
- test/skills-integrity.test.ts — clean / modified / missing / extra /
  self-exclusion / deterministic-render, over tmp-dir fixtures.

Docs: regeneration workflow in docs/TESTING.md + a KEY_FILES.md entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-27 16:19:41 -07:00
co-authored by Claude Fable 5
parent 032af6e5f7
commit f489243159
11 changed files with 423 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# CI guard for skills/skills.lock.json freshness (#159).
#
# Mirrors scripts/check-eval-glossary-fresh.sh: regenerate the manifest into
# a tmp file, diff against the committed version, fail the build if they
# drift. Tamper-evidence, not a signature system — the point is that any
# change under skills/ ships with an explicit manifest diff.
#
# Run: bash scripts/check-skills-manifest-fresh.sh
# Wired through `bun run verify` (scripts/run-verify-parallel.sh) so PRs that
# edit skills/ without regenerating the manifest are caught before review.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
COMMITTED="$REPO_ROOT/skills/skills.lock.json"
TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT
if [ ! -f "$COMMITTED" ]; then
echo "ERROR: $COMMITTED not found." >&2
echo "Run: bun run scripts/generate-skills-manifest.ts" >&2
exit 1
fi
cd "$REPO_ROOT"
# Render directly via bun + a one-liner that exposes the module function.
bun -e "import { renderSkillsManifest } from './src/core/skills-integrity.ts'; process.stdout.write(renderSkillsManifest('skills'));" > "$TMP"
if ! diff -q "$COMMITTED" "$TMP" >/dev/null 2>&1; then
echo "ERROR: skills/skills.lock.json is stale." >&2
echo "" >&2
echo "Diff between committed and freshly-generated:" >&2
echo "" >&2
diff -u "$COMMITTED" "$TMP" >&2 || true
echo "" >&2
echo "To regenerate: bun run scripts/generate-skills-manifest.ts" >&2
exit 1
fi
echo "✓ skills/skills.lock.json is fresh"
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bun
/**
* Regenerates skills/skills.lock.json — the tamper-evidence manifest mapping
* every bundled file under skills/ to its sha256 (#159). Not a signature
* system: it turns silent skill edits into explicit diffs. `gbrain doctor`
* warns (never fails) on drift; scripts/check-skills-manifest-fresh.sh keeps
* the committed manifest in sync in CI.
*
* Run after any change under skills/:
* bun run scripts/generate-skills-manifest.ts
*/
import { writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
SKILLS_MANIFEST_FILENAME,
renderSkillsManifest,
} from '../src/core/skills-integrity.ts';
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
const skillsDir = join(repoRoot, 'skills');
const outPath = join(skillsDir, SKILLS_MANIFEST_FILENAME);
writeFileSync(outPath, renderSkillsManifest(skillsDir));
console.log(`Wrote ${outPath}`);
+1
View File
@@ -49,6 +49,7 @@ CHECKS=(
"check:cli-exec"
"check:system-of-record"
"check:eval-glossary"
"check:skills-manifest"
"check:no-pii-agent-voice"
"check:synthetic-corpus-privacy"
"check:skill-brain-first"