From 0798372ee72ec1094abfbfb4efc1b1691452e231 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:38:36 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat(skills):=20add=20the=20=C2=A73=20price?= =?UTF-8?q?d-catalog=20metadata=20to=20skill=20frontmatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every skill now declares cost (free/cheap/subagents), protects (the outcome sentence the init menu will show), requires (substrate and capability preconditions), gate_key (its §4.2 mode-map binding), and ci_job (the authoritative CI job, if any). kit_version moves to 2.0.0-alpha. kit-update's frontmatter lands with the manifest work in the next commit to keep in-flight edits atomic. First slice of the v2.0-alpha rollout phase (metadata & manifest — no behavior change). Ref: SPEC.md §3, §14 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AUZigEcZo8BczjgS7gWUzZ --- .claude/kit.yaml | 2 +- .claude/skills/adopt-existing-project/SKILL.md | 5 +++++ .claude/skills/adr/SKILL.md | 5 +++++ .claude/skills/branch-conflict-check/SKILL.md | 5 +++++ .claude/skills/ci-watch/SKILL.md | 5 +++++ .claude/skills/compound-learnings/SKILL.md | 5 +++++ .claude/skills/config-audit/SKILL.md | 5 +++++ .claude/skills/cost-check/SKILL.md | 5 +++++ .claude/skills/coverage-ratchet/SKILL.md | 5 +++++ .claude/skills/doc-sync/SKILL.md | 5 +++++ .claude/skills/eval-harness/SKILL.md | 5 +++++ .claude/skills/flaky-triage/SKILL.md | 5 +++++ .claude/skills/followup-tracking/SKILL.md | 5 +++++ .claude/skills/incident-capture/SKILL.md | 5 +++++ .claude/skills/kit-doctor/SKILL.md | 5 +++++ .claude/skills/logging-init/SKILL.md | 5 +++++ .claude/skills/migration-check/SKILL.md | 5 +++++ .claude/skills/new-project-bootstrap/SKILL.md | 5 +++++ .claude/skills/nightly-audit/SKILL.md | 5 +++++ .claude/skills/observability-check/SKILL.md | 5 +++++ .claude/skills/parallel-work/SKILL.md | 5 +++++ .claude/skills/perf-budget/SKILL.md | 5 +++++ .claude/skills/pr-babysitter/SKILL.md | 5 +++++ .claude/skills/pre-pr/SKILL.md | 5 +++++ .claude/skills/prompt-regression/SKILL.md | 5 +++++ .claude/skills/release/SKILL.md | 5 +++++ .claude/skills/session-handoff/SKILL.md | 5 +++++ .claude/skills/sync-health/SKILL.md | 5 +++++ .claude/skills/using-the-kit/SKILL.md | 5 +++++ .claude/skills/writing-kit-skills/SKILL.md | 5 +++++ 30 files changed, 146 insertions(+), 1 deletion(-) diff --git a/.claude/kit.yaml b/.claude/kit.yaml index 7e9ce43..f63ffbb 100644 --- a/.claude/kit.yaml +++ b/.claude/kit.yaml @@ -8,7 +8,7 @@ # is false, the migration-check step is skipped and reported N/A. This is how one # kit serves a Python LLM service and a Go CLI without editing every skill. -kit_version: "1.3.1" # stamp; `kit-update` compares this against the source kit +kit_version: "2.0.0-alpha" # stamp; `kit-update` compares this against the source kit preset: "llm-app" # library | service | llm-app | frontend | data-pipeline | custom # --- Gates: how strictly the quality gates enforce (scale-adaptive) --- diff --git a/.claude/skills/adopt-existing-project/SKILL.md b/.claude/skills/adopt-existing-project/SKILL.md index 3c80ef2..5b35c68 100644 --- a/.claude/skills/adopt-existing-project/SKILL.md +++ b/.claude/skills/adopt-existing-project/SKILL.md @@ -1,6 +1,11 @@ --- name: adopt-existing-project description: Non-destructively bring the foundation kit into an existing repo — audit what's already there, report gaps ranked by leverage, merge (never overwrite) kit artifacts, and recommend a phased rollout the team can approve incrementally +cost: cheap +protects: "The kit's guardrails get added to a repo already in flight without overwriting anything you wrote, and every change is shown as a diff you approve before it's applied." +requires: nothing +gate_key: none +ci_job: none --- # Adopt Existing Project diff --git a/.claude/skills/adr/SKILL.md b/.claude/skills/adr/SKILL.md index 60e590d..b3a4755 100644 --- a/.claude/skills/adr/SKILL.md +++ b/.claude/skills/adr/SKILL.md @@ -1,6 +1,11 @@ --- name: adr description: Create an Architecture Decision Record when a non-obvious design choice is made — captures context, options, rationale, and tradeoffs in a durable doc +cost: free +protects: "The reasoning behind a hard design choice gets written down, including the alternatives you rejected, so no one has to guess or re-argue it later." +requires: nothing +gate_key: none +ci_job: none --- # ADR diff --git a/.claude/skills/branch-conflict-check/SKILL.md b/.claude/skills/branch-conflict-check/SKILL.md index 0459fe4..25d16e1 100644 --- a/.claude/skills/branch-conflict-check/SKILL.md +++ b/.claude/skills/branch-conflict-check/SKILL.md @@ -1,6 +1,11 @@ --- name: branch-conflict-check description: Detect file-level overlap between the current branch and every other open PR so merge order gets decided deliberately, not discovered at conflict time +cost: cheap +protects: "You find out which other open pull requests touch the same files as yours before you merge, instead of hitting the conflict by surprise afterward." +requires: "GitHub CLI (gh) authenticated" +gate_key: branch_conflict +ci_job: none --- # Branch Conflict Check diff --git a/.claude/skills/ci-watch/SKILL.md b/.claude/skills/ci-watch/SKILL.md index ffc9d9c..c29c500 100644 --- a/.claude/skills/ci-watch/SKILL.md +++ b/.claude/skills/ci-watch/SKILL.md @@ -1,6 +1,11 @@ --- name: ci-watch description: After opening or pushing a PR, watch its CI run to completion and fix any failure — lint, types, tests, build — then re-push until green, instead of leaving a red PR +cost: cheap +protects: "A pull request's CI run gets watched until it finishes, and any failure gets diagnosed and fixed automatically instead of being left red." +requires: "GitHub CLI (gh) authenticated" +gate_key: none +ci_job: none --- # CI Watch-and-Fix diff --git a/.claude/skills/compound-learnings/SKILL.md b/.claude/skills/compound-learnings/SKILL.md index d6a8ced..7f6e677 100644 --- a/.claude/skills/compound-learnings/SKILL.md +++ b/.claude/skills/compound-learnings/SKILL.md @@ -1,6 +1,11 @@ --- name: compound-learnings description: After solving a non-obvious problem — a gnarly bug, a tricky integration, a surprising workaround — write the solution pattern to docs/solutions/ so future planning and debugging start from it instead of re-deriving it. Each unit of work should make the next one easier. +cost: free +protects: "A hard-won fix to a tricky bug gets written down once, so the next person starts from the answer instead of repeating the same investigation." +requires: "capabilities.docs.enabled" +gate_key: capture +ci_job: none --- # Compound Learnings diff --git a/.claude/skills/config-audit/SKILL.md b/.claude/skills/config-audit/SKILL.md index c925823..a251d46 100644 --- a/.claude/skills/config-audit/SKILL.md +++ b/.claude/skills/config-audit/SKILL.md @@ -1,6 +1,11 @@ --- name: config-audit description: Security audit of the agent-facing config surface — settings.json permissions, hook scripts, CLAUDE.md, skills, and MCP configs — for injection-shaped instructions, over-broad permissions, and leaked secrets. Read-only sibling of kit-doctor; run before committing changes to .claude/ or CLAUDE.md. +cost: free +protects: "The files that steer the agent — settings, hooks, CLAUDE.md, skills, MCP configs — get checked for leaked secrets and hidden instructions before you commit them." +requires: nothing +gate_key: none +ci_job: none --- # Config Audit diff --git a/.claude/skills/cost-check/SKILL.md b/.claude/skills/cost-check/SKILL.md index 896c228..191d664 100644 --- a/.claude/skills/cost-check/SKILL.md +++ b/.claude/skills/cost-check/SKILL.md @@ -1,6 +1,11 @@ --- name: cost-check description: Query the invocation/spend log for current API spend vs daily and monthly budget thresholds and report a projected month-end total +cost: cheap +protects: "You see today's and this month's API spend against budget, with a projected month-end total, before you kick off expensive work." +requires: "invocation_log spend table (SUBSTRATE.md §1); capabilities.llm.enabled" +gate_key: none +ci_job: none --- # Cost Check diff --git a/.claude/skills/coverage-ratchet/SKILL.md b/.claude/skills/coverage-ratchet/SKILL.md index f4bd567..048316b 100644 --- a/.claude/skills/coverage-ratchet/SKILL.md +++ b/.claude/skills/coverage-ratchet/SKILL.md @@ -1,6 +1,11 @@ --- name: coverage-ratchet description: Prevent test coverage from silently eroding — compare the current run against a stored baseline, fail if it dropped, and offer to lock in a higher baseline when it rose +cost: cheap +protects: "Test coverage can only hold steady or improve — a drop below the recorded baseline is caught and blocked instead of quietly eroding over time." +requires: "capabilities.coverage.ratchet_enabled; a recorded coverage baseline" +gate_key: coverage_ratchet +ci_job: none --- # Coverage Ratchet diff --git a/.claude/skills/doc-sync/SKILL.md b/.claude/skills/doc-sync/SKILL.md index 40de04a..9c05246 100644 --- a/.claude/skills/doc-sync/SKILL.md +++ b/.claude/skills/doc-sync/SKILL.md @@ -1,6 +1,11 @@ --- name: doc-sync description: Keep docs, spec, and code in lockstep in the same PR — update affected narrative docs, cite/update the canonical spec, and log any accepted drift as a follow-up +cost: subagents +protects: "Docs and the canonical spec stay in step with the code change in the same pull request, instead of drifting silently over time." +requires: "capabilities.docs.enabled" +gate_key: docs_sync +ci_job: none --- # Doc Sync diff --git a/.claude/skills/eval-harness/SKILL.md b/.claude/skills/eval-harness/SKILL.md index 61992c1..3c2f261 100644 --- a/.claude/skills/eval-harness/SKILL.md +++ b/.claude/skills/eval-harness/SKILL.md @@ -1,6 +1,11 @@ --- name: eval-harness description: Set up and run tiered, version-controlled evaluation fixtures for non-deterministic behavior (LLM calls, classifiers, extractors) — score against pass-gates and catch regressions in CI +cost: subagents +protects: "Changes to a prompt, model, or classifier get scored against version-controlled test cases before they ship, so a quality regression is caught instead of shipping on a vibe." +requires: "eval fixtures + a runner (SUBSTRATE.md §2); capabilities.llm.enabled" +gate_key: none +ci_job: none --- # Eval Harness diff --git a/.claude/skills/flaky-triage/SKILL.md b/.claude/skills/flaky-triage/SKILL.md index 3b0b021..48801ab 100644 --- a/.claude/skills/flaky-triage/SKILL.md +++ b/.claude/skills/flaky-triage/SKILL.md @@ -1,6 +1,11 @@ --- name: flaky-triage description: When a test goes red then green with no intervening code change, confirm it's flaky, record it in the registry, and optionally quarantine it so it stops blocking PRs but stays visible — instead of silently blocking the queue or being silently ignored +cost: cheap +protects: "A test that turns red then green gets confirmed as flaky, logged, and optionally quarantined instead of silently blocking or being ignored." +requires: nothing +gate_key: flaky_triage +ci_job: none --- # Flaky Test Triage diff --git a/.claude/skills/followup-tracking/SKILL.md b/.claude/skills/followup-tracking/SKILL.md index b01180f..8994380 100644 --- a/.claude/skills/followup-tracking/SKILL.md +++ b/.claude/skills/followup-tracking/SKILL.md @@ -1,6 +1,11 @@ --- name: followup-tracking description: Capture deferred decisions, accepted drift, and cross-cutting TODOs in a durable follow-ups log instead of letting them rot in code comments — with a stable-ID entry format and a close/archive lifecycle +cost: free +protects: "Decisions you deferred and gaps you accepted get written down in one durable place instead of disappearing in a merged pull request." +requires: "capabilities.docs.enabled" +gate_key: capture +ci_job: none --- # Follow-up Tracking diff --git a/.claude/skills/incident-capture/SKILL.md b/.claude/skills/incident-capture/SKILL.md index 910854b..f4ec5e9 100644 --- a/.claude/skills/incident-capture/SKILL.md +++ b/.claude/skills/incident-capture/SKILL.md @@ -1,6 +1,11 @@ --- name: incident-capture description: When a component fires fallback_activated (or crashes) repeatedly in a window, auto-open a pre-filled incident note so the 2am page starts with context instead of a blank page. +cost: cheap +protects: "A repeating failure automatically opens an incident note with the timeline already filled in, so whoever responds starts with context instead of a blank page." +requires: "invocation_log (SUBSTRATE.md §1); alert transport (SUBSTRATE.md §3)" +gate_key: none +ci_job: none --- # Incident Capture diff --git a/.claude/skills/kit-doctor/SKILL.md b/.claude/skills/kit-doctor/SKILL.md index 9a0dc92..4db91a2 100644 --- a/.claude/skills/kit-doctor/SKILL.md +++ b/.claude/skills/kit-doctor/SKILL.md @@ -1,6 +1,11 @@ --- name: kit-doctor description: Verify an installed kit is correctly wired — checks toolchain commands, capability toggles, hook executability, settings references, and gh auth. Reports PASS/WARN/FAIL per check. Never modifies anything. +cost: cheap +protects: "Broken wiring in the installed kit — a hook that can't fire, a missing tool, a bad config — gets caught before it costs you a failed run." +requires: nothing +gate_key: none +ci_job: none --- # Kit Doctor diff --git a/.claude/skills/logging-init/SKILL.md b/.claude/skills/logging-init/SKILL.md index 6892d3e..b6f50c1 100644 --- a/.claude/skills/logging-init/SKILL.md +++ b/.claude/skills/logging-init/SKILL.md @@ -1,6 +1,11 @@ --- name: logging-init description: Initialize or upgrade structured logging in a project — probe what already exists, ask 2–3 targeted questions, recommend a stack-mapped setup per docs/LOGGING_STANDARD.md, and wire it with approval, migrating rather than replacing what's there. Run during bootstrap/adoption, or whenever a repo is logging with print/console.log or an ad-hoc logger. +cost: cheap +protects: "Scattered print statements or an ad-hoc logger get upgraded to structured logging with correlation context and timing, without breaking what already works." +requires: nothing +gate_key: none +ci_job: none --- # Logging Init diff --git a/.claude/skills/migration-check/SKILL.md b/.claude/skills/migration-check/SKILL.md index 35393c7..8997cb3 100644 --- a/.claude/skills/migration-check/SKILL.md +++ b/.claude/skills/migration-check/SKILL.md @@ -1,6 +1,11 @@ --- name: migration-check description: Catch dangerous DB schema migrations before they hit the primary DB or any replica — verify single head, reversibility, and no unguarded destructive ops +cost: cheap +protects: "A database migration that would break production or a replica — multiple heads, a fake rollback, an unguarded destructive change — gets caught before it ships." +requires: "capabilities.migrations.enabled" +gate_key: migration_check +ci_job: none --- # Migration Check diff --git a/.claude/skills/new-project-bootstrap/SKILL.md b/.claude/skills/new-project-bootstrap/SKILL.md index f356888..9454fb1 100644 --- a/.claude/skills/new-project-bootstrap/SKILL.md +++ b/.claude/skills/new-project-bootstrap/SKILL.md @@ -1,6 +1,11 @@ --- name: new-project-bootstrap description: Lay a strong foundation in a new or existing repo — detect the stack, write the project profile (kit.yaml), install worktree isolation, autoformat, CI, CLAUDE.md, docs standard, follow-ups log, and the observability/eval/doc-sync scaffolding from the foundation kit +cost: cheap +protects: "A new project starts with the safety nets teams usually add too late — isolated work, logging that never fails silently, and a CI gate that stays green." +requires: nothing +gate_key: none +ci_job: none --- # New Project Bootstrap diff --git a/.claude/skills/nightly-audit/SKILL.md b/.claude/skills/nightly-audit/SKILL.md index d87b57c..4c2af7b 100644 --- a/.claude/skills/nightly-audit/SKILL.md +++ b/.claude/skills/nightly-audit/SKILL.md @@ -1,6 +1,11 @@ --- name: nightly-audit description: Run the project's drift and health checks on a cron and post a compact morning digest to chat — turning "run it if you remember" into a standing signal. +cost: subagents +protects: "Docs, spec, dependencies, and follow-ups get checked every morning and only the problems worth acting on reach chat, instead of drift piling up unnoticed." +requires: "a scheduler (SUBSTRATE.md §4); alert transport (SUBSTRATE.md §3)" +gate_key: none +ci_job: none --- # Nightly Audit diff --git a/.claude/skills/observability-check/SKILL.md b/.claude/skills/observability-check/SKILL.md index 251fff1..e3355b0 100644 --- a/.claude/skills/observability-check/SKILL.md +++ b/.claude/skills/observability-check/SKILL.md @@ -1,6 +1,11 @@ --- name: observability-check description: Verify a change is observable before merge — structured logging at decision points, no silent failures, correlation context, and dashboards/alerts for new failure modes +cost: subagents +protects: "A change gets checked for the logging a 2am on-call responder would need, before it merges instead of after an outage." +requires: nothing +gate_key: observability_check +ci_job: none --- # Observability Check diff --git a/.claude/skills/parallel-work/SKILL.md b/.claude/skills/parallel-work/SKILL.md index b307a19..4a3fd75 100644 --- a/.claude/skills/parallel-work/SKILL.md +++ b/.claude/skills/parallel-work/SKILL.md @@ -1,6 +1,11 @@ --- name: parallel-work description: Isolate concurrent Claude sessions in git worktrees so multiple agents never cross wires on main — create, work, PR, and auto-clean per-branch worktrees +cost: cheap +protects: "Concurrent agents working in the same repo can't overwrite or corrupt each other's changes, because each stream of work gets its own isolated branch and worktree." +requires: nothing +gate_key: worktree_isolation +ci_job: none --- # Parallel Work — Worktree Isolation for Concurrent Agents diff --git a/.claude/skills/perf-budget/SKILL.md b/.claude/skills/perf-budget/SKILL.md index 3007c09..168ef49 100644 --- a/.claude/skills/perf-budget/SKILL.md +++ b/.claude/skills/perf-budget/SKILL.md @@ -1,6 +1,11 @@ --- name: perf-budget description: Catch hot-path and external/model-call latency regressions before they reach production — compare p95 per operation against configured budgets and a stored baseline, fail the PR on regression, and offer to ratchet the baseline down on improvement +cost: cheap +protects: "Hot-path and model-call latency regressions get caught before they reach production, instead of showing up as a slower app for real users." +requires: "capabilities.perf.enabled; a latency source — invocation_log (SUBSTRATE.md §1) or a benchmark_cmd" +gate_key: perf_budget +ci_job: none --- # Performance Budget diff --git a/.claude/skills/pr-babysitter/SKILL.md b/.claude/skills/pr-babysitter/SKILL.md index 29a6fcf..2429043 100644 --- a/.claude/skills/pr-babysitter/SKILL.md +++ b/.claude/skills/pr-babysitter/SKILL.md @@ -1,6 +1,11 @@ --- name: pr-babysitter description: Watch the open-PR queue on an interval, rebase stale-but-green PRs onto main, surface blockers to a human, and keep CI moving — without auto-merging anything +cost: cheap +protects: "Stale-but-green pull requests stay mergeable and the queue keeps moving, without a human having to manually rebase and re-check every PR." +requires: "GitHub CLI (gh) authenticated; a scheduler for loop mode (SUBSTRATE.md §4)" +gate_key: none +ci_job: none --- # PR Babysitter diff --git a/.claude/skills/pre-pr/SKILL.md b/.claude/skills/pre-pr/SKILL.md index 59aee8b..12440d3 100644 --- a/.claude/skills/pre-pr/SKILL.md +++ b/.claude/skills/pre-pr/SKILL.md @@ -1,6 +1,11 @@ --- name: pre-pr description: Run the full pre-PR gate — lint, types, tests, migration heads, docs, spec citations, follow-ups — so CI passes on first push instead of going red +cost: subagents +protects: "A pull request that would fail CI on lint, types, tests, migrations, security, docs, or spec drift gets caught and fixed locally, so it arrives green instead of blocking the queue." +requires: nothing +gate_key: lint_types_tests +ci_job: "lint, typecheck, test" --- # Pre-PR Gate diff --git a/.claude/skills/prompt-regression/SKILL.md b/.claude/skills/prompt-regression/SKILL.md index 88164b4..6385eb4 100644 --- a/.claude/skills/prompt-regression/SKILL.md +++ b/.claude/skills/prompt-regression/SKILL.md @@ -1,6 +1,11 @@ --- name: prompt-regression description: When a PR touches a prompt template or model config, run the affected task_type's eval fixtures on both branches and report score deltas so prompt changes are evidence-based +cost: subagents +protects: "Prompt or model changes ship with a before/after eval score instead of a vibe." +requires: "eval fixtures + a runner (SUBSTRATE.md §2); capabilities.llm.enabled" +gate_key: prompt_regression +ci_job: none --- # Prompt Regression diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 2336f6b..68f208a 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -1,6 +1,11 @@ --- name: release description: Turn merged PRs and conventional commits since the last tag into a versioned changelog entry and a tagged release — semver bump derived automatically from commit types +cost: cheap +protects: "Cutting a release produces an accurate versioned changelog and tag straight from what actually merged, instead of a hand-written changelog that drifts from reality." +requires: "GitHub CLI (gh) authenticated" +gate_key: none +ci_job: none --- # Release diff --git a/.claude/skills/session-handoff/SKILL.md b/.claude/skills/session-handoff/SKILL.md index 69b2974..46c6967 100644 --- a/.claude/skills/session-handoff/SKILL.md +++ b/.claude/skills/session-handoff/SKILL.md @@ -1,6 +1,11 @@ --- name: session-handoff description: Write a tight durable state summary so a fresh session or another agent can resume the current task without re-deriving context +cost: free +protects: "Work in progress survives a context switch or handoff, because what's done, what's next, and why gets written down before it would otherwise be lost." +requires: nothing +gate_key: none +ci_job: none --- # Session Handoff diff --git a/.claude/skills/sync-health/SKILL.md b/.claude/skills/sync-health/SKILL.md index 8bb3009..b7bb71d 100644 --- a/.claude/skills/sync-health/SKILL.md +++ b/.claude/skills/sync-health/SKILL.md @@ -1,6 +1,11 @@ --- name: sync-health description: For a primary + replica setup, verify replication isn't lagging or silently dropping rows — the data-integrity drift that's invisible until the numbers don't match. +cost: cheap +protects: "Replication drift between a primary and its replica — lagging writes or silently dropped rows — gets caught and alerted on before the data has quietly diverged for hours." +requires: "capabilities.replica.enabled with primary and replica connections configured" +gate_key: sync_health +ci_job: none --- # Sync Health diff --git a/.claude/skills/using-the-kit/SKILL.md b/.claude/skills/using-the-kit/SKILL.md index c871e2f..1efd178 100644 --- a/.claude/skills/using-the-kit/SKILL.md +++ b/.claude/skills/using-the-kit/SKILL.md @@ -1,6 +1,11 @@ --- name: using-the-kit description: The kit's dispatcher — consult this before starting any task and before responding. If there is even a 1% chance a kit skill or gate applies to what you are about to do, you MUST invoke it. Contains the trigger index and the red-flags table of rationalizations to reject. +cost: free +protects: "The right safeguard actually runs at the moment it matters, instead of being silently skipped because nobody remembered it existed." +requires: nothing +gate_key: none +ci_job: none --- # Using the Kit diff --git a/.claude/skills/writing-kit-skills/SKILL.md b/.claude/skills/writing-kit-skills/SKILL.md index 4433269..c0059b5 100644 --- a/.claude/skills/writing-kit-skills/SKILL.md +++ b/.claude/skills/writing-kit-skills/SKILL.md @@ -1,6 +1,11 @@ --- name: writing-kit-skills description: Author a new kit-compatible skill — frontmatter conventions, sharp trigger descriptions, kit.yaml profile tagging, capability gates, report formats, and how to register the skill so it actually fires. Use this to extend the kit instead of forking it. +cost: free +protects: "A newly written skill actually gets discovered and triggered when it's needed, instead of becoming dead documentation nobody invokes." +requires: nothing +gate_key: none +ci_job: none --- # Writing Kit Skills From c88a60d8b61fb7b71f10a8dcc710ff8ca6474422 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:44:54 +0000 Subject: [PATCH 2/3] feat(kit): generated catalog, file-hash manifest, and doctor checks (v2.0-alpha) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes rollout phase 1 of the v2 spec — metadata & manifest, no behavior change: - scripts/gen-catalog.sh (Tier-0 bash): regenerates the README workflow catalog from skill frontmatter between catalog markers, validates the §3 metadata (cost class, §4.2 gate-key set, duplicate claims), and prints the §4.3 strictness default maps via --modes; kit-ci fails on drift so the frontmatter stays the single source of truth. - scripts/gen-manifest.sh + .claude/kit-manifest.sha256 (PT10): the per-version path→sha256 manifest of kit-managed files. Bootstrap, adopt, and kit-update now record it into projects, and kit-update step 3 classifies SAFE vs NEEDS REVIEW against the recorded baseline instead of a git-blame heuristic — with never-SAFE rules for paths missing from the manifest and for settings.json (security-sensitive, always reviewed via the settings.template.json path mapping). - kit-doctor checks 12–15: §3 metadata validity (severity version-gated at the first v2 version), ci_job consistency against installed CI workflows (§2.3), gate-key coverage of the full §4.2 set, and manifest-recorded. - kit-ci: catalog + manifest drift checks, scripts/*.sh in the syntax gate, and the hook's own allowlist pragma on the planted secret-scan test vector so branch scans against a stale trunk don't false-positive. - followups: PT10 closed; PT2 progress note (requires: labels shipped, substrate itself lands in v2.1). Ref: SPEC.md §3, §4.2–§4.3, §12.1, §14 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AUZigEcZo8BczjgS7gWUzZ --- .claude/kit-manifest.sha256 | 45 +++ .../skills/adopt-existing-project/SKILL.md | 12 + .claude/skills/kit-doctor/SKILL.md | 191 ++++++++++++ .claude/skills/kit-update/SKILL.md | 166 +++++++++-- .claude/skills/new-project-bootstrap/SKILL.md | 7 + .github/workflows/kit-ci.yml | 10 +- README.md | 93 +++--- docs/followups.md | 11 +- scripts/gen-catalog.sh | 273 ++++++++++++++++++ scripts/gen-manifest.sh | 78 +++++ 10 files changed, 812 insertions(+), 74 deletions(-) create mode 100644 .claude/kit-manifest.sha256 create mode 100755 scripts/gen-catalog.sh create mode 100755 scripts/gen-manifest.sh diff --git a/.claude/kit-manifest.sha256 b/.claude/kit-manifest.sha256 new file mode 100644 index 0000000..27f8cef --- /dev/null +++ b/.claude/kit-manifest.sha256 @@ -0,0 +1,45 @@ +80b6185152e796cebc9956b30824d3dddf182224bec1963913139e60a0c91fa0 .claude/agents/codebase-onboarder.md +ada89d3eb105fe29615340000dcf994216bd211cf3c0c64845eb14c43e6c19e8 .claude/agents/config-consistency-checker.md +14e8407b8d77ed2ad613c64c006de744425071bab7e871f137efdaa4eee1883a .claude/agents/dependency-auditor.md +e1ec3ca1b03f6f3c740352f39196b2d7d43a4d74c0c75a80728d6ba6324410ba .claude/agents/docs-updater.md +4c5282cec9a47b15c964f7f25a66ade44d90a684a3d55dad570e852c6173c205 .claude/agents/observability-reviewer.md +902400f9b722e36b682b3b71998ebec3f0652f72780b37b38595b1e66db3e3bb .claude/agents/security-reviewer.md +b99dbcbf44e97c5979fba07a4dc2968067c8e3afdcd3dcac66aacd1b38d24ef5 .claude/agents/spec-drift-checker.md +7e07b72e781002e8c6803b191ca214ef6b11cf36a6ccd3514abc3e399eb4712e .claude/agents/test-gap-analyzer.md +6c98997058bfa2a3f75d551736590c5c88dc4fab380f1c3fd491c434d5f8b6b4 .claude/hooks/autoformat.sh +03b740c471f680476e5a6ad0198acaaa8a850a8310ac3dd3c2d03815c4186ae1 .claude/hooks/post-merge-prune.sh +43d42b2f1b729bf638fd565d24650baabf71ac09b9240c41f2b7f2e610c1d146 .claude/hooks/protect-credential-files.sh +8cc36ce1bc518072ee1b77499ccbe94b8c27206b3c364816ca987ddd3ebe2ab4 .claude/hooks/prune-merged-worktrees.sh +4011ecf15434e40cc520c898136ac2c21b6cd27c211213d02d59cad7207b1165 .claude/hooks/require-worktree.sh +dee6e936adf425353e144c51ea3eb384507774ecf859180190245583031ba9e2 .claude/hooks/secret-scan-diff.sh +9756f65ecf046d69218077f81fddc6712ecb2dfee7861a994f83b55e0a00edcd .claude/settings.template.json +a4632f926ee5c275e7b3745a73bda9e927ca1a1df9e52891ae9f2d65aa6e273e .claude/skills/adopt-existing-project/SKILL.md +76f46ddb3114ede75b5e21a52b0441c6dc1b0af4f9f4b2f92b95713f97d3b0fd .claude/skills/adr/SKILL.md +2d4323c85e2521c79f06cabd875d9aadb85ad8fc4f26ede252b5d05130c4db4e .claude/skills/branch-conflict-check/SKILL.md +4649d46cda61592e481e41d9e5b09a23a5cccf894f6e5f447825a97b2ff51618 .claude/skills/ci-watch/SKILL.md +9305d90e9ca439dcffba91a36a5a32f408b5b55ba0c0ca150d41e12630d2a114 .claude/skills/compound-learnings/SKILL.md +d05c26b70efcab220122f4c634a441735af374c51bfcb7d966fb65c18cb7839f .claude/skills/config-audit/SKILL.md +8750ef95e594e7046bb3dc6e854bfcc60322c87eb17862a28e61b01f15b2c39f .claude/skills/cost-check/SKILL.md +23196d8932489afddba8b72f426176ab6afcfbd8141cc3842149773141cbcd47 .claude/skills/coverage-ratchet/SKILL.md +5c565dd2ce1d3ec1f335a10923547645432c913e62247d7f50be57f4c395c5d5 .claude/skills/doc-sync/SKILL.md +038b9626f315878830fbb090f4c198a48b8e7680e345b028c1a7c2f052ed16f3 .claude/skills/eval-harness/SKILL.md +49a8069fcd90dde102f30f15be28ec6e61ef2863acfd60372d6b377756796ada .claude/skills/flaky-triage/SKILL.md +40f248357ebda29809bbd6eb82da094c0919f9b206af7a71d669cc267a066683 .claude/skills/followup-tracking/SKILL.md +c8e38f1303fefece34d834f3e1f2c93c610aa691f69f63da4347b7d775bd236a .claude/skills/incident-capture/SKILL.md +c55d265f147dc95550a84b8570e0a4b1495b7a38b95a734d66e6fbff3b885d18 .claude/skills/kit-doctor/SKILL.md +6bb358976b12b28479c2cdaa037d53b1f38c02e862c180d273562791fbc901b9 .claude/skills/kit-update/SKILL.md +3051307ae10051aa86a8c80f120da670145b7bc7de9ff7b78cb4046e5f693f16 .claude/skills/logging-init/SKILL.md +0989102bf03735b4632c9312a7a1dadbf3157dc0a07783740cb7cdac7b626c20 .claude/skills/migration-check/SKILL.md +283c28dea6d1cafe8f7a0283684b9621e903892350f54974503554ee94921d9a .claude/skills/new-project-bootstrap/SKILL.md +55889d9fcc7caa8d36c8fca66b870796e2af68bde845595abde501d9de2a43e1 .claude/skills/nightly-audit/SKILL.md +acc4d05e9fd79370151df093dfb9a1a62fc1061b6b0c8fba701ea0d574220936 .claude/skills/observability-check/SKILL.md +7027cf5ad86693439ebed8cdcfe9f0cd02bb40cf7d5090cc9faa550bad28ee1d .claude/skills/parallel-work/SKILL.md +8afa61a8ab0f5373ef20b9ec7bcfae8fa05a5389944e90a04db4778fd65bf154 .claude/skills/perf-budget/SKILL.md +15c00de2e87a851ac0c8fe633b98d8bb73955e6457e346cb74700d9f713160ab .claude/skills/pr-babysitter/SKILL.md +4f6c747b618f5a0c95f049741436ee6db01e20c2d80fa2c5ffe8a1441d2a4ae4 .claude/skills/pre-pr/SKILL.md +14c9142d7b391eabbad7088b7af3205aaccf3e19a1c71cd050a3d4e46d979f33 .claude/skills/prompt-regression/SKILL.md +1488030c69a9cc044113f20a682fc1ece6b4725c3ad244831a8498cfef35b997 .claude/skills/release/SKILL.md +a551286870434467287d9928e5b9401e6610cb1c1ad229ed12788d872c4285c6 .claude/skills/session-handoff/SKILL.md +3d11efd1cb089beb94ad8e966f69407bc89710c91aa667c64c4281b6ee4cea7e .claude/skills/sync-health/SKILL.md +443939504bfd48e77be345412bdc6bd746f0db3ab760f05768c89705ddeb703b .claude/skills/using-the-kit/SKILL.md +28d46efe27a5f97961c1c3b25d5e9a9d529685c54e8e4234ff39c69ae8a45213 .claude/skills/writing-kit-skills/SKILL.md diff --git a/.claude/skills/adopt-existing-project/SKILL.md b/.claude/skills/adopt-existing-project/SKILL.md index 5b35c68..3c021f4 100644 --- a/.claude/skills/adopt-existing-project/SKILL.md +++ b/.claude/skills/adopt-existing-project/SKILL.md @@ -286,6 +286,17 @@ else fi ``` +#### Record the kit manifest +After the approved hooks/skills/agents/settings land, copy the source kit's +`.claude/kit-manifest.sha256` into the project's `.claude/`. It stamps the kit +version adopted here so `kit-update` can later distinguish a locally-modified +kit file from an untouched one instead of guessing (SPEC §12.1). Files the team +chose to keep their own version of will hash-mismatch this baseline and +correctly surface as NEEDS REVIEW on the next update — that is intended. +```bash +cp "$KIT/.claude/kit-manifest.sha256" .claude/kit-manifest.sha256 +``` + #### Docs and follow-ups — add, never clobber - `docs/DOCS_STANDARD.md`: copy if absent. - `docs/followups.md`: copy if absent; if present, append a `## Kit Adoption` section with the gap items as open follow-ups. @@ -399,6 +410,7 @@ correct command. Installing the wrong formatter silently reformats code. ### Proposed Changes (dry-run — nothing written yet) - .claude/settings.json: - .claude/hooks/: +- .claude/kit-manifest.sha256: - .github/workflows/ci.yml: - CLAUDE.md: - .claude/kit.yaml: diff --git a/.claude/skills/kit-doctor/SKILL.md b/.claude/skills/kit-doctor/SKILL.md index 4db91a2..b265b2a 100644 --- a/.claude/skills/kit-doctor/SKILL.md +++ b/.claude/skills/kit-doctor/SKILL.md @@ -339,6 +339,193 @@ done [ -d .claude/scratch ] || echo "OK - no scratch dir" ``` +### 12. Skill metadata present and valid (SPEC.md §3) + +Every `.claude/skills/*/SKILL.md` frontmatter (the block between the first two +`---` lines) must carry the full §3 field set — `name`, `description`, `cost`, +`protects`, `requires`, `gate_key`, `ci_job` — each single-line and non-empty. +`cost` must be one of `free`, `cheap`, `subagents`; `gate_key` must be a key in +the §4.2 set or the literal `none`. This is the metadata the init menu, the +README catalog, and checks 13–14 all read, so a malformed field silently breaks +generation downstream. + +**Severity is version-gated (SPEC §12.1).** Read `kit_version` from +`.claude/kit.yaml`. While it is below the first v2 version — it starts with `0.` +or `1.` — the project predates the v2 metadata rollout, so any metadata problem +is **WARN**. At `2.x` or later it is **FAIL**. The threshold is hardcoded here. + +```bash +GATE_SET="secrets_scan credential_files worktree_isolation lint_types_tests \ +integration_tests migration_check coverage_ratchet perf_budget security_review \ +test_gap docs_sync spec_drift prompt_regression branch_conflict \ +observability_check flaky_triage sync_health ui_build build_artifact capture" + +kit_version=$(sed -n 's/^kit_version:[[:space:]]*"\{0,1\}\([0-9][^" ]*\).*/\1/p' \ + .claude/kit.yaml | head -1) +case "$kit_version" in + 0.*|1.*) sev=WARN ;; # pre-v2: metadata not rolled out yet + *) sev=FAIL ;; # v2+: metadata is contractual +esac + +problems=0 +for f in .claude/skills/*/SKILL.md; do + fm=$(awk 'NR==1 && /^---/{inside=1; next} /^---/{if(inside) exit} inside' "$f") + for key in name description cost protects requires gate_key ci_job; do + val=$(printf '%s\n' "$fm" | sed -n "s/^$key:[[:space:]]*//p" | head -1) + [ -z "$val" ] && { echo "$sev $f: missing or empty field '$key'"; problems=$((problems+1)); } + done + cost=$(printf '%s\n' "$fm" | sed -n 's/^cost:[[:space:]]*//p' | head -1 | tr -d '"') + case "$cost" in + free|cheap|subagents|"") ;; + *) echo "$sev $f: cost '$cost' not in {free,cheap,subagents}"; problems=$((problems+1)) ;; + esac + gk=$(printf '%s\n' "$fm" | sed -n 's/^gate_key:[[:space:]]*//p' | head -1 | tr -d '"') + if [ -n "$gk" ] && [ "$gk" != none ]; then + ok=0; for k in $GATE_SET; do [ "$k" = "$gk" ] && ok=1; done + [ "$ok" -eq 0 ] && { echo "$sev $f: gate_key '$gk' not in the §4.2 set ∪ {none}"; problems=$((problems+1)); } + fi +done +[ "$problems" -eq 0 ] && echo "OK - all skill metadata present and valid" +``` + +Emit the check at **WARN** or **FAIL** per the version gate above; **PASS** when +`problems` is zero. + +### 13. ci_job consistency (SPEC.md §3, §2.3) + +Two invariants tie skill metadata to CI. First, every `ci_job` value a skill +declares must name a real job. Collect each `ci_job` ≠ `none` (a value may be a +double-quoted comma-separated list like `"lint, typecheck, test"` — split on +commas and trim), and confirm each named job exists as a top-level `jobs:` child +in the installed CI workflows (`.github/workflows/*.yml`). When run inside the +source kit repo itself — detectable by the presence of `templates/ci.template.yml` +— also search that template, since it is the workflow projects actually copy. +A missing job is **FAIL**; no CI workflow present at all is **WARN**. + +```bash +workflows=$(ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null) +template="" +[ -f templates/ci.template.yml ] && template="templates/ci.template.yml" # source-kit clause + +if [ -z "$workflows" ] && [ -z "$template" ]; then + echo "WARN no CI workflow files found — ci_job consistency cannot be verified" +else + job_src=$(cat $workflows $template 2>/dev/null) + missing=0 + for f in .claude/skills/*/SKILL.md; do + cj=$(sed -n 's/^ci_job:[[:space:]]*//p' "$f" | head -1 | tr -d '"') + [ -z "$cj" ] || [ "$cj" = none ] && continue + IFS=',' read -ra jobs <<< "$cj" + for j in "${jobs[@]}"; do + j=$(printf '%s' "$j" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [ -z "$j" ] && continue + if ! printf '%s\n' "$job_src" | grep -qE "^ ${j}:"; then + echo "FAIL $(basename "$(dirname "$f")"): ci_job '$j' not a job key in installed CI workflows or template" + missing=$((missing+1)) + fi + done + done + [ "$missing" -eq 0 ] && echo "OK - every ci_job value maps to a CI job key" +fi +``` + +Second, per §2.3/§4.4: any gate whose skill has `ci_job != none` is CI-backed +and must therefore be `enforce` in `gates.modes.` — **but only if a +`modes:` block exists**. Pre-§4 installs have no mode map yet; there is nothing +to check, so report `N/A (no mode map — strictness defaults apply per +docs/PROFILE.md)`. + +```bash +if grep -qE '^ modes:' .claude/kit.yaml; then + for f in .claude/skills/*/SKILL.md; do + cj=$(sed -n 's/^ci_job:[[:space:]]*//p' "$f" | head -1 | tr -d '"') + [ -z "$cj" ] || [ "$cj" = none ] && continue + gk=$(sed -n 's/^gate_key:[[:space:]]*//p' "$f" | head -1 | tr -d '"') + mode=$(sed -n "s/^ ${gk}:[[:space:]]*//p" .claude/kit.yaml | head -1) + [ "$mode" != enforce ] && \ + echo "FAIL gate '$gk' (ci_job=$cj) is '$mode' in gates.modes — must be enforce (SPEC §2.3)" + done +else + echo "N/A (no mode map — strictness defaults apply per docs/PROFILE.md)" +fi +``` + +Missing job → **FAIL**; no workflow → **WARN**; a CI-backed gate not `enforce` +→ **FAIL**; no `modes:` block → the mode half is **N/A** (the ci_job-existence +half still runs). + +### 14. Gate-key coverage (SPEC.md §4.2) + +The §4.2 key set must be owned, exactly once, by exactly the surfaces that +implement it. Take the union of (a) every skill `gate_key` ≠ `none` and (b) the +static list of keys owned by non-skill surfaces — hooks own `secrets_scan`, +`credential_files`, `worktree_isolation`; agents own `security_review`, +`test_gap`, `spec_drift`; pre-pr steps own `integration_tests`, `ui_build`, +`build_artifact`. That union must equal the full §4.2 set exactly. + +- A §4.2 key owned by nothing → **FAIL** ("gate key has no owner"). +- A `gate_key` claimed by more than one skill → **FAIL**, EXCEPT `capture` + (legitimately shared by followup-tracking + compound-learnings, §6.3) and + `worktree_isolation` (owned by both the hook and the parallel-work skill by + design). +- A key owned but absent from the §4.2 set → **FAIL** (a stray/renamed key). + +```bash +NONSKILL="secrets_scan credential_files worktree_isolation security_review \ +test_gap spec_drift integration_tests ui_build build_artifact" + +skill_keys=$(for f in .claude/skills/*/SKILL.md; do + gk=$(sed -n 's/^gate_key:[[:space:]]*//p' "$f" | head -1 | tr -d '"') + [ -n "$gk" ] && [ "$gk" != none ] && echo "$gk" +done) + +# Duplicate ownership across skills (capture + worktree_isolation exempt) +printf '%s\n' "$skill_keys" | sort | uniq -c | while read -r c k; do + [ -z "$k" ] && continue + if [ "$c" -gt 1 ]; then + case "$k" in + capture|worktree_isolation) ;; + *) echo "FAIL gate_key '$k' claimed by $c skills (only capture and worktree_isolation may be shared)" ;; + esac + fi +done + +union=$(printf '%s\n%s\n' "$skill_keys" "$(printf '%s\n' $NONSKILL)" | sed '/^$/d' | sort -u) +for k in $GATE_SET; do + printf '%s\n' "$union" | grep -qx "$k" || echo "FAIL gate key '$k' has no owner" +done +printf '%s\n' "$union" | while read -r k; do + printf '%s\n' $GATE_SET | grep -qx "$k" || echo "FAIL gate key '$k' owned but not in the §4.2 set" +done +``` + +(`$GATE_SET` is the list defined in check 12; if you run this check standalone, +redefine it.) **PASS** when the union equals the §4.2 set and no illegitimate +duplicate owner appears; any line above is a **FAIL**. + +### 15. Kit manifest recorded (SPEC.md §12.1) + +`.claude/kit-manifest.sha256` is the per-version file-hash manifest kit-update +reads to classify changes as SAFE vs NEEDS-REVIEW (PT10). It must exist and +every line must be `sha256sum` format — 64 hex chars, two spaces, a path. + +```bash +manifest=.claude/kit-manifest.sha256 +if [ ! -f "$manifest" ]; then + echo "WARN no kit-manifest.sha256 — kit-update classification degrades to a git heuristic until a manifest is recorded — see SPEC.md §12.1" +else + bad=$(grep -vnE '^[0-9a-f]{64} .' "$manifest") + if [ -n "$bad" ]; then + echo "FAIL malformed manifest line(s):"; printf '%s\n' "$bad" + else + echo "OK - manifest present, all $(wc -l < "$manifest") line(s) well-formed" + fi +fi +``` + +Missing manifest → **WARN** (kit-update still works, just falls back to a git +heuristic). Any malformed line → **FAIL**. + ## Output Emit exactly this fenced block with real results. Fill in the righthand column; @@ -360,6 +547,10 @@ never leave a check blank. | gh auth | PASS/WARN/FAIL/N/A | — | | settings.json valid JSON | PASS/WARN/FAIL | — | | No stale progress ledgers | PASS/WARN | — | +| Skill metadata (§3) | PASS/WARN/FAIL | — | +| ci_job consistency | PASS/WARN/FAIL/N/A | — | +| Gate-key coverage (§4.2) | PASS/FAIL | — | +| Kit manifest recorded | PASS/WARN/FAIL | — | Overall: PASS / WARN (N warnings, 0 failures) / FAIL (N failures) ``` diff --git a/.claude/skills/kit-update/SKILL.md b/.claude/skills/kit-update/SKILL.md index 2091f93..e3d7064 100644 --- a/.claude/skills/kit-update/SKILL.md +++ b/.claude/skills/kit-update/SKILL.md @@ -1,6 +1,11 @@ --- name: kit-update description: Propagate improvements from the source kit to a project that adopted it. Diffs kit-managed files, classifies changes as safe/needs-review/new, never clobbers local customizations silently, bumps kit_version after applying. +cost: cheap +protects: "Improvements from the source kit reach an adopted project without silently overwriting anything the team customized." +requires: "a source-kit checkout on disk" +gate_key: none +ci_job: none --- # Kit Update @@ -60,56 +65,151 @@ that and stop. Do not proceed. ### Step 2 — Enumerate kit-managed files -Kit-managed files are everything under these paths in the source kit: +Kit-managed files are everything under these paths in the source kit — the same +set the manifest (`scripts/gen-manifest.sh`) covers: ``` .claude/skills/*/SKILL.md -.claude/agents/*/AGENT.md (if present) +.claude/agents/*.md .claude/hooks/*.sh .claude/settings.template.json ``` -Build the list: +Build the list (relative paths, one per line): ```bash -find "$KIT_ROOT/.claude/skills" -name 'SKILL.md' \ - "$KIT_ROOT/.claude/hooks" -name '*.sh' \ - "$KIT_ROOT/.claude" -name 'settings.template.json' \ - 2>/dev/null +( cd "$KIT_ROOT" && { + find .claude/skills -name 'SKILL.md' + find .claude/agents -maxdepth 1 -name '*.md' + find .claude/hooks -name '*.sh' + [ -f .claude/settings.template.json ] && echo .claude/settings.template.json + } 2>/dev/null | sort ) ``` -Also include any `agents/` directory if it exists. Exclude `kit.yaml` itself — -that is per-project configuration, not a kit-managed template. +Exclude `kit.yaml` itself — that is per-project configuration, not a kit-managed +template (per SPEC §12.1, kit-update manages files, not config; the mode map is +`/kit-menu`'s job). Note that `.claude/kit-manifest.sha256` is itself +kit-managed, but it is **not** diffed or prompted like a content file — it is +handled by Step 5's recording rule (copied wholesale to stamp the adopted +version). Do not add it to the per-file classification list in Step 3. ### Step 3 — Classify each file (dry-run) -For each kit file, compute its relative path (strip the `$KIT_ROOT` prefix) and -check whether the project has a copy at the same relative path: +Classification is **manifest-based**. The project carries a recorded +`.claude/kit-manifest.sha256` — the `sha256sum`-format list of kit-managed +paths and their hashes **as of the kit version the project last adopted** +(copied in at install/update time; see Step 5 and SPEC §12.1). That recorded +baseline is what makes the SAFE-vs-NEEDS-REVIEW split real: it is the third +point of a 3-way comparison between the project's current copy, the version the +project adopted, and the source kit HEAD. + +For each kit-managed relative path `$REL`, gather three facts: + +1. **source hash** — sha256 of `$KIT_ROOT/$REL` (the kit HEAD). +2. **project hash** — sha256 of `$PROJECT_ROOT/$REL` (absent if the file is + missing in the project). +3. **recorded hash** — the hash for `$REL` in the project's recorded + `.claude/kit-manifest.sha256` (absent if the path is not listed). + +Then classify: | Scenario | Classification | Action | |---|---|---| -| File exists in project and is **byte-for-byte identical to the previous kit version** (or identical to the current source) | SKIP | Nothing to do | -| File exists in project, project copy **differs from the previous kit version** but not from the source kit HEAD | SKIP | Already up to date | -| File exists in project, **project copy matches the previous kit version** (not locally modified) → source kit changed it | SAFE | Apply automatically (dry-run: show diff) | -| File exists in project, **project copy differs from both** the previous kit version and the source kit | NEEDS REVIEW | Show 3-way diff; ask before touching | -| File **does not exist in project** | NEW | Offer to add it | - -To determine "locally modified" without storing the previous kit snapshot: diff -the project's copy against the source kit. If they differ, treat as NEEDS REVIEW. -If they are identical, treat as already up to date. +| project hash **==** source hash | SKIP | Up to date — project copy already equals kit HEAD | +| file **absent** in project | NEW | Offer to add it | +| project hash **== recorded** hash and source **differs** (project never touched it since adopting) | SAFE | Apply automatically (dry-run: show diff) | +| project hash **!= recorded** hash and source differs (locally modified) | NEEDS REVIEW | Show 3-way diff; ask before touching | +| `$REL` **not in** the recorded manifest (adopted pre-manifest, or a project-authored file shadowing a new kit path) and source differs | NEEDS REVIEW | Never SAFE — there is no baseline to prove the project copy is untouched | + +The two rules that keep this honest: a file is **SAFE only** when the project's +current hash exactly matches what the manifest recorded (proving the project +never edited it), and a path **missing from the recorded manifest is never +SAFE** — without a recorded baseline we cannot distinguish an untouched adopted +file from a divergent local one, so it goes to review. + +**Path mapping — settings.** The manifest tracks the source path +`.claude/settings.template.json`, but installs write it to the project as +`.claude/settings.json` (bootstrap copies it; adoption merges into an existing +one). For this one file, hash the project's `.claude/settings.json` and compare +it against the recorded hash of `.claude/settings.template.json` — and never +classify it SAFE: when the source template changed since the recorded version, +it is **always NEEDS REVIEW**. Settings are security-sensitive configuration +(hook wiring, permissions); changes are merged by hand and re-checked with +`config-audit`, never auto-applied. A project with no `.claude/settings.json` +at all is NEW. ```bash +# sha256 of a file, portable (Linux coreutils / macOS): prints just the hash +kit_sha256() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} + +MANIFEST="$PROJECT_ROOT/.claude/kit-manifest.sha256" + +# recorded hash for an exact relative path from the project's manifest +recorded_hash() { + # match the path column exactly (sha256sum format: "<2 spaces>") + awk -v p="$1" '$2 == p {print $1; exit}' "$MANIFEST" +} + # For each kit-managed relative path $REL: +SRC_HASH="$(kit_sha256 "$KIT_ROOT/$REL")" +if [ ! -e "$PROJECT_ROOT/$REL" ]; then + echo "NEW" +else + PROJ_HASH="$(kit_sha256 "$PROJECT_ROOT/$REL")" + REC_HASH="$(recorded_hash "$REL")" + if [ "$PROJ_HASH" = "$SRC_HASH" ]; then + echo "SKIP" # already equals kit HEAD + elif [ -z "$REC_HASH" ]; then + echo "NEEDS REVIEW" # no recorded baseline for this path + elif [ "$PROJ_HASH" = "$REC_HASH" ]; then + echo "SAFE" # untouched since adoption; source changed + else + echo "NEEDS REVIEW" # locally modified + fi +fi +``` + +For NEEDS REVIEW files, produce the 3-way diff for the report by showing the +source-vs-project diff (`diff "$PROJECT_ROOT/$REL" "$KIT_ROOT/$REL"`); note in +the prompt whether the divergence is from a local edit (recorded hash present +but mismatched) or from a missing baseline (path not in the manifest). + +**Bash only — no Python here** (SPEC §10, Tier-0: kit machinery is bash + git). + +#### Fallback — no recorded manifest (heuristic) + +If the project has **no** `.claude/kit-manifest.sha256` (adopted before the +manifest shipped), fall back to the git-blame heuristic below, and **prefix the +report with a WARN**: + +``` +WARN: no .claude/kit-manifest.sha256 in this project — classification is a +git-blame heuristic and will over-report NEEDS REVIEW (e.g. after squash +merges). It becomes exact once a manifest is recorded on the next apply. +See SPEC §12.1 / follow-up PT10. +``` + +Heuristic: diff the project's copy against the source kit. + +```bash +# For each kit-managed relative path $REL (fallback only): diff "$KIT_ROOT/$REL" "$PROJECT_ROOT/$REL" > /dev/null 2>&1 # exit 0 → identical → SKIP # exit 1 → differs → SAFE if project never touched it, else NEEDS REVIEW # file missing in project → NEW ``` -To distinguish SAFE from NEEDS REVIEW: check git blame / `git diff HEAD -- -"$REL"` in the project. If git shows the project-side file was modified after -the kit was adopted (any local commit changes it), classify as NEEDS REVIEW. -Otherwise SAFE. +To distinguish SAFE from NEEDS REVIEW without a manifest: check +`git diff HEAD -- "$REL"` / git blame in the project. If git shows the +project-side file was modified after the kit was adopted (any local commit +changes it), classify as NEEDS REVIEW; otherwise SAFE. This is the heuristic the +manifest replaces — never trust it as strongly as a recorded hash. ### Step 4 — Print the dry-run report @@ -156,6 +256,23 @@ Do not apply without a clear instruction to proceed. When confirmed: per-file explicit answer. 3. **NEW files** — ask: "Add this file?" For each new file, wait for confirmation. 4. **SKIP files** — do nothing. +5. **Record the manifest** — after all per-file decisions are made, copy the + **source kit's** `.claude/kit-manifest.sha256` into the project at the same + path, overwriting any previously recorded manifest. This stamps the version + the project just adopted and is what makes the next update's Step 3 + classification exact (SPEC §12.1). + + ```bash + cp "$KIT_ROOT/.claude/kit-manifest.sha256" \ + "$PROJECT_ROOT/.claude/kit-manifest.sha256" + ``` + + Note — this is by design, not a bug: any file where the user chose "keep + project version" now hash-mismatches the freshly recorded manifest (the + manifest lists the *new* source hash; the project kept the *old* content). + On the next update that file correctly re-classifies **NEEDS REVIEW** rather + than being silently overwritten as SAFE. Record the manifest even if some + files were declined; a recorded baseline is always more accurate than none. ### Step 6 — Bump kit_version @@ -225,6 +342,7 @@ Added: Skipped (already up to date): .claude/hooks/require-worktree.sh +manifest: recorded (.claude/kit-manifest.sha256 → ) kit_version: Next: run kit-doctor to verify the updated install. ``` diff --git a/.claude/skills/new-project-bootstrap/SKILL.md b/.claude/skills/new-project-bootstrap/SKILL.md index 9454fb1..3d1f968 100644 --- a/.claude/skills/new-project-bootstrap/SKILL.md +++ b/.claude/skills/new-project-bootstrap/SKILL.md @@ -141,11 +141,17 @@ cp -r "$KIT/.claude/hooks" "$DEST/.claude/hooks" cp "$KIT/.claude/settings.template.json" "$DEST/.claude/settings.json" cp -r "$KIT/.claude/skills/"* "$DEST/.claude/skills/" cp -r "$KIT/.claude/agents/"* "$DEST/.claude/agents/" +cp "$KIT/.claude/kit-manifest.sha256" "$DEST/.claude/kit-manifest.sha256" chmod +x "$DEST/.claude/hooks/"*.sh mkdir -p "$DEST/.claude/worktrees" "$DEST/.claude/scratch" printf '%s\n' '.claude/worktrees/' '.claude/scratch/' >> "$DEST/.gitignore" ``` +Record the manifest by copying `.claude/kit-manifest.sha256` from the source kit +(the `cp` above): it stamps the exact kit version this project adopted, which is +what lets `kit-update` later tell a locally-modified kit file from an untouched +one instead of guessing (SPEC §12.1). + Adjust the formatter: hook wiring in `settings.json` stays as-is (all logic lives in the scripts); edit the per-extension case in `.claude/hooks/autoformat.sh` to match the toolchain (ruff / prettier / gofmt — @@ -210,6 +216,7 @@ Then, for the language that applies, adapt the remaining patterns from `template - Hooks: worktree-isolation, secret-guard, secret-scan-diff, autoformat, merge-prune — installed - Skills: — installed - Agents: — installed +- Manifest: .claude/kit-manifest.sha256 recorded (baseline for kit-update) - CLAUDE.md: authored ( lines) - Docs: DOCS_STANDARD + followups + taxonomy scaffolded - CI: ci.yml installed () diff --git a/.github/workflows/kit-ci.yml b/.github/workflows/kit-ci.yml index c563d0d..d2628d6 100644 --- a/.github/workflows/kit-ci.yml +++ b/.github/workflows/kit-ci.yml @@ -56,7 +56,7 @@ jobs: - name: Shell hooks are syntactically valid run: | fail=0 - for s in .claude/hooks/*.sh .claude/skills/*/*.sh; do + for s in .claude/hooks/*.sh .claude/skills/*/*.sh scripts/*.sh; do [ -f "$s" ] || continue if bash -n "$s"; then echo "ok $s"; else echo "FAIL $s"; fail=1; fi done @@ -70,6 +70,12 @@ jobs: done exit $fail + - name: Skill metadata + README catalog are current (SPEC.md §3) + run: bash scripts/gen-catalog.sh --check + + - name: Kit manifest is current (SPEC.md §12.1) + run: bash scripts/gen-manifest.sh --check + - name: Hooks actually block (behavioral) run: | set -e @@ -117,7 +123,7 @@ jobs: # 6. secret-scan catches a planted key on a branch (direct invocation) cd "$TMP" git checkout -qb feat/leak - printf 'AWS_KEY = "%s"\n' "AKIAABCDEFGHIJKLMNOP" > src/creds.py + printf 'AWS_KEY = "%s"\n' "AKIAABCDEFGHIJKLMNOP" > src/creds.py # pragma: allowlist secret (test vector) git add src/creds.py && git commit -qm leak rc=0; bash "$HOOKS/secret-scan-diff.sh" /dev/null || rc=$? check "secret-scan blocks planted AWS key" 2 "$rc" diff --git a/README.md b/README.md index a06b4d3..a81728e 100644 --- a/README.md +++ b/README.md @@ -141,52 +141,53 @@ templates/ci.template.yml Lint + types + tests gate that projects copy to work makes the next one easier — and skills fire on the 1% rule (`using-the-kit`), so none of this depends on remembering to ask. -## The workflow catalog by purpose - -Every workflow below ships in the kit. Grouped by the problem it solves: - -**Concurrency & the PR queue** — keep a fleet of agents from colliding. -`parallel-work` (worktree isolation) · `branch-conflict-check` (warn when two -open PRs touch the same files) · `pr-babysitter` (rebase/re-run/ping the open-PR -queue without auto-merging) · `pre-pr` + `ci-watch` (green locally, kept green). - -**Quality gates** — catch expensive mistakes before merge. -`migration-check` (destructive/irreversible schema ops) · `test-gap-analyzer` -(untested new paths, risk-ranked) · `coverage-ratchet` (coverage can't drop) · -`perf-budget` (p95 can't regress) · `prompt-regression` (eval deltas on prompt/ -model changes) · `security-reviewer` (injection, auth bypass, credential/token -leaks) · `config-consistency-checker` (dangling config refs) · `secret-scan-diff.sh` -(keys leaked into source) · `flaky-triage` (flaky tests confirmed + quarantined). - -**Observability & evaluation** — make behavior debuggable and measurable. -`logging-init` (initialize/upgrade structured logging from any starting point — -probes what exists, asks three questions, wires additively per stack) · -`observability-check` + `observability-reviewer` (no silent failures, logging at -decision points — enforcing the per-process event catalog in -`docs/LOGGING_STANDARD.md`: jobs, consumers, pipelines, lifecycle, not just API -calls) · `eval-harness` (tiered fixtures) · `cost-check` + `cost_guard.py` -(spend vs budget, pause-before-call) · `logging_setup.py` / `fallback_alert.py` -(the reference patterns, incl. the generic `log_operation` timing helper). - -**Docs, spec & memory** — stop drift and lost context. -`doc-sync` + `docs-updater` · `spec-drift-checker` · `adr` (decision records) · -`followup-tracking` · `compound-learnings` (solved-problem patterns that feed -future planning) · `session-handoff` (baton note between sessions). - -**Release & operations** — standing signals and clean releases. -`release` (conventional commits → changelog + semver + tag) · `nightly-audit` -(cron the drift/health checks → morning digest) · `incident-capture` (auto-open an -incident note on repeated fallbacks) · `sync-health` (primary+replica lag/parity) · -`dependency-auditor` · `healthwatch.py` (heartbeat sidecar) · `codebase-onboarder`. - -**Adapt & maintain the kit itself** — make it fit the project and stay current. -`new-project-bootstrap` / `adopt-existing-project` (greenfield vs brownfield -install) · the `kit.yaml` profile + `presets/` (one file drives the toolchain and -capability toggles every skill reads) · `using-the-kit` (the dispatcher: 1%-rule -trigger index so skills actually fire) · `kit-doctor` (verify the wiring) · -`config-audit` (verify the config surface is *safe*, not just wired) · -`kit-update` (pull source-kit improvements without clobbering local changes) · -`writing-kit-skills` (extend the kit without forking it). +## The workflow catalog + +Every workflow below ships in the kit, priced by its worst-case cost class (SPEC.md §3). + + +_Generated from skill frontmatter by `scripts/gen-catalog.sh` — edit the skills' frontmatter, not this table (kit-ci fails on drift)._ + +### Gates (mode-mapped via `gates.modes.`) + +| Skill | Protects | Cost | Requires | Gate key | CI job | +|---|---|---|---|---|---| +| branch-conflict-check | You find out which other open pull requests touch the same files as yours before you merge, instead of hitting the conflict by surprise afterward. | cheap | GitHub CLI (gh) authenticated | `branch_conflict` | — | +| compound-learnings | A hard-won fix to a tricky bug gets written down once, so the next person starts from the answer instead of repeating the same investigation. | free | capabilities.docs.enabled | `capture` | — | +| coverage-ratchet | Test coverage can only hold steady or improve — a drop below the recorded baseline is caught and blocked instead of quietly eroding over time. | cheap | capabilities.coverage.ratchet_enabled; a recorded coverage baseline | `coverage_ratchet` | — | +| doc-sync | Docs and the canonical spec stay in step with the code change in the same pull request, instead of drifting silently over time. | subagents | capabilities.docs.enabled | `docs_sync` | — | +| flaky-triage | A test that turns red then green gets confirmed as flaky, logged, and optionally quarantined instead of silently blocking or being ignored. | cheap | — | `flaky_triage` | — | +| followup-tracking | Decisions you deferred and gaps you accepted get written down in one durable place instead of disappearing in a merged pull request. | free | capabilities.docs.enabled | `capture` | — | +| migration-check | A database migration that would break production or a replica — multiple heads, a fake rollback, an unguarded destructive change — gets caught before it ships. | cheap | capabilities.migrations.enabled | `migration_check` | — | +| observability-check | A change gets checked for the logging a 2am on-call responder would need, before it merges instead of after an outage. | subagents | — | `observability_check` | — | +| parallel-work | Concurrent agents working in the same repo can't overwrite or corrupt each other's changes, because each stream of work gets its own isolated branch and worktree. | cheap | — | `worktree_isolation` | — | +| perf-budget | Hot-path and model-call latency regressions get caught before they reach production, instead of showing up as a slower app for real users. | cheap | capabilities.perf.enabled; a latency source — invocation_log (SUBSTRATE.md §1) or a benchmark_cmd | `perf_budget` | — | +| pre-pr | A pull request that would fail CI on lint, types, tests, migrations, security, docs, or spec drift gets caught and fixed locally, so it arrives green instead of blocking the queue. | subagents | — | `lint_types_tests` | lint, typecheck, test | +| prompt-regression | Prompt or model changes ship with a before/after eval score instead of a vibe. | subagents | eval fixtures + a runner (SUBSTRATE.md §2); capabilities.llm.enabled | `prompt_regression` | — | +| sync-health | Replication drift between a primary and its replica — lagging writes or silently dropped rows — gets caught and alerted on before the data has quietly diverged for hours. | cheap | capabilities.replica.enabled with primary and replica connections configured | `sync_health` | — | + +### Workflows & conventions (never mode-gated) + +| Skill | Protects | Cost | Requires | +|---|---|---|---| +| adopt-existing-project | The kit's guardrails get added to a repo already in flight without overwriting anything you wrote, and every change is shown as a diff you approve before it's applied. | cheap | — | +| adr | The reasoning behind a hard design choice gets written down, including the alternatives you rejected, so no one has to guess or re-argue it later. | free | — | +| ci-watch | A pull request's CI run gets watched until it finishes, and any failure gets diagnosed and fixed automatically instead of being left red. | cheap | GitHub CLI (gh) authenticated | +| config-audit | The files that steer the agent — settings, hooks, CLAUDE.md, skills, MCP configs — get checked for leaked secrets and hidden instructions before you commit them. | free | — | +| cost-check | You see today's and this month's API spend against budget, with a projected month-end total, before you kick off expensive work. | cheap | invocation_log spend table (SUBSTRATE.md §1); capabilities.llm.enabled | +| eval-harness | Changes to a prompt, model, or classifier get scored against version-controlled test cases before they ship, so a quality regression is caught instead of shipping on a vibe. | subagents | eval fixtures + a runner (SUBSTRATE.md §2); capabilities.llm.enabled | +| incident-capture | A repeating failure automatically opens an incident note with the timeline already filled in, so whoever responds starts with context instead of a blank page. | cheap | invocation_log (SUBSTRATE.md §1); alert transport (SUBSTRATE.md §3) | +| kit-doctor | Broken wiring in the installed kit — a hook that can't fire, a missing tool, a bad config — gets caught before it costs you a failed run. | cheap | — | +| kit-update | Improvements from the source kit reach an adopted project without silently overwriting anything the team customized. | cheap | a source-kit checkout on disk | +| logging-init | Scattered print statements or an ad-hoc logger get upgraded to structured logging with correlation context and timing, without breaking what already works. | cheap | — | +| new-project-bootstrap | A new project starts with the safety nets teams usually add too late — isolated work, logging that never fails silently, and a CI gate that stays green. | cheap | — | +| nightly-audit | Docs, spec, dependencies, and follow-ups get checked every morning and only the problems worth acting on reach chat, instead of drift piling up unnoticed. | subagents | a scheduler (SUBSTRATE.md §4); alert transport (SUBSTRATE.md §3) | +| pr-babysitter | Stale-but-green pull requests stay mergeable and the queue keeps moving, without a human having to manually rebase and re-check every PR. | cheap | GitHub CLI (gh) authenticated; a scheduler for loop mode (SUBSTRATE.md §4) | +| release | Cutting a release produces an accurate versioned changelog and tag straight from what actually merged, instead of a hand-written changelog that drifts from reality. | cheap | GitHub CLI (gh) authenticated | +| session-handoff | Work in progress survives a context switch or handoff, because what's done, what's next, and why gets written down before it would otherwise be lost. | free | — | +| using-the-kit | The right safeguard actually runs at the moment it matters, instead of being silently skipped because nobody remembered it existed. | free | — | +| writing-kit-skills | A newly written skill actually gets discovered and triggered when it's needed, instead of becoming dead documentation nobody invokes. | free | — | + ## Adapting it — the profile diff --git a/docs/followups.md b/docs/followups.md index 950c797..a34bf18 100644 --- a/docs/followups.md +++ b/docs/followups.md @@ -20,7 +20,10 @@ lead, non-engineer, Go CLI dev, LLM startup team). ### PT2 — The assumed LLM substrate is unnamed and unshipped - **Spec:** README "Observability & evaluation" catalog group -- **Status:** open +- **Status:** open — partially addressed in v2.0-alpha: every skill now labels + its preconditions up front (`requires:` frontmatter, SPEC.md §3); the + substrate itself (DDL, eval conftest, webhook sender, schema unification) + ships in v2.1 per SPEC.md §9 - **Gap:** Five skills query an `invocation_log` table nothing creates (and two disagree on its timestamp column name); eval-harness/prompt-regression assume a pytest eval runner that doesn't exist; `alerts.channel` has no transport @@ -103,7 +106,11 @@ lead, non-engineer, Go CLI dev, LLM startup team). ### PT10 — kit-update needs a real base-version manifest - **Spec:** kit-update skill, step 3 -- **Status:** open +- **Status:** closed (v2.0-alpha — `scripts/gen-manifest.sh` generates + `.claude/kit-manifest.sha256` (kept current by kit-ci), install/adopt/update + record it into the project, and kit-update step 3 classifies against the + recorded baseline; settings.json gets an explicit never-SAFE mapping rule. + SPEC.md §12.1) - **Gap:** SAFE-vs-NEEDS-REVIEW classification degrades to a git-blame heuristic because no shipped-file-hash manifest exists; after squash merges, everything looks locally modified. Ship a per-version manifest diff --git a/scripts/gen-catalog.sh b/scripts/gen-catalog.sh new file mode 100755 index 0000000..bcbc436 --- /dev/null +++ b/scripts/gen-catalog.sh @@ -0,0 +1,273 @@ +#!/usr/bin/env bash +# gen-catalog.sh — regenerate the README workflow catalog and print the +# strictness default maps from SKILL.md frontmatter. +# +# SPEC citation: SPEC.md §3 (generation contract — one source of truth, +# mechanically enforced), §4.2 (the gate key set), §4.3 (strictness → +# default maps). Tier-0 per SPEC.md §10: bash + git + coreutils only; must +# run on macOS bash 3.2 and Linux. +# +# Usage: +# scripts/gen-catalog.sh validate + rewrite the README catalog in +# place between +# scripts/gen-catalog.sh --check validate + exit 1 if the README catalog +# drifted from the frontmatter +# scripts/gen-catalog.sh --modes print the §4.3 strictness default-map table +# +# Env: KIT_ROOT overrides the repo root (default: git rev-parse --show-toplevel). + +set -euo pipefail + +KIT_ROOT="${KIT_ROOT:-$(git rev-parse --show-toplevel)}" +cd "$KIT_ROOT" + +README="README.md" +BEGIN_MARK='' +END_MARK='' + +# The §4.2 key set, pre-sorted LC_ALL=C. Gates with no key do not exist. +GATE_KEYS="branch_conflict build_artifact capture coverage_ratchet \ +credential_files docs_sync flaky_triage integration_tests lint_types_tests \ +migration_check observability_check perf_budget prompt_regression \ +secrets_scan security_review spec_drift sync_health test_gap ui_build \ +worktree_isolation" + +# Trend gates demoted to suggest at prototype strictness (§4.3). +TREND_GATES="coverage_ratchet perf_budget prompt_regression" +# Deterministic-protective gates pinned enforce at every level (§2.2, §4.3). +PINNED_GATES="secrets_scan credential_files migration_check" + +DATA=$(mktemp "${TMPDIR:-/tmp}/gen-catalog.data.XXXXXX") +SCRATCH=$(mktemp "${TMPDIR:-/tmp}/gen-catalog.scratch.XXXXXX") +trap 'rm -f "$DATA" "$SCRATCH"' EXIT + +# --------------------------------------------------------------------------- +# Frontmatter parsing (§3 generation contract: single-line scalars, values are +# bare tokens or double-quoted strings, optionally followed by a # comment). +# --------------------------------------------------------------------------- + +# Print the frontmatter body of $1 (between the first two '---' lines). +frontmatter_of() { + head -n 1 "$1" | grep -q '^---[[:space:]]*$' || return 0 + sed -n '2,$p' "$1" | sed -n '1,/^---[[:space:]]*$/p' | sed '$d' +} + +# field_from — print the cleaned scalar value. +field_from() { + line=$(printf '%s\n' "$1" | grep "^$2:" | head -n 1) || true + if [ -z "$line" ]; then + printf '' + return 0 + fi + val=${line#*:} + val=$(printf '%s' "$val" | sed 's/^[[:space:]]*//') + case "$val" in + \"*) + # Double-quoted string; anything after the closing quote is dropped. + val=$(printf '%s' "$val" | sed 's/^"\(.*\)".*$/\1/') + ;; + *) + # Bare token; strip trailing comment and whitespace. + val=${val%%#*} + val=$(printf '%s' "$val" | sed 's/[[:space:]]*$//') + ;; + esac + printf '%s' "$val" +} + +key_in_set() { # key_in_set + for k in $2; do + [ "$1" = "$k" ] && return 0 + done + return 1 +} + +# --------------------------------------------------------------------------- +# Collect + validate (validation runs in every mode). +# Data file: namedescriptioncostprotectsrequiresgate_keyci_job +# --------------------------------------------------------------------------- + +violations=0 +violation() { + echo "gen-catalog: $1" >&2 + violations=1 +} + +found_any=0 +for f in .claude/skills/*/SKILL.md; do + [ -f "$f" ] || continue + found_any=1 + fm=$(frontmatter_of "$f") + name=$(field_from "$fm" name) + description=$(field_from "$fm" description) + cost=$(field_from "$fm" cost) + protects=$(field_from "$fm" protects) + requires=$(field_from "$fm" requires) + gate_key=$(field_from "$fm" gate_key) + ci_job=$(field_from "$fm" ci_job) + + for pair in "name=$name" "description=$description" "cost=$cost" \ + "protects=$protects" "requires=$requires" \ + "gate_key=$gate_key" "ci_job=$ci_job"; do + fname=${pair%%=*} + fval=${pair#*=} + if [ -z "$fval" ]; then + violation "$f: missing or empty frontmatter field '$fname' (SPEC.md §3)" + fi + done + + if [ -n "$cost" ]; then + case "$cost" in + free|cheap|subagents) : ;; + *) violation "$f: cost '$cost' is not one of free|cheap|subagents (SPEC.md §3)" ;; + esac + fi + if [ -n "$gate_key" ] && [ "$gate_key" != "none" ]; then + if ! key_in_set "$gate_key" "$GATE_KEYS"; then + violation "$f: gate_key '$gate_key' is not in the SPEC.md §4.2 key set (or 'none')" + fi + fi + + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$name" "$description" "$cost" "$protects" "$requires" "$gate_key" "$ci_job" >> "$DATA" +done + +if [ "$found_any" -eq 0 ]; then + echo "gen-catalog: no .claude/skills/*/SKILL.md found under $KIT_ROOT" >&2 + exit 1 +fi + +# A gate_key other than 'none' and 'capture' must be claimed by exactly one +# skill ('capture' is explicitly shared, SPEC.md §6.3). +dups=$(cut -f6 "$DATA" | grep -v '^none$' | grep -v '^capture$' | grep -v '^$' \ + | LC_ALL=C sort | uniq -d) || true +if [ -n "$dups" ]; then + for d in $dups; do + claimants=$(LC_ALL=C sort "$DATA" | cut -f1,6 | grep " $d\$" | cut -f1 | tr '\n' ' ') + violation "gate_key '$d' is claimed by more than one skill: $claimants(only 'capture' may be shared, SPEC.md §6.3)" + done +fi + +if [ "$violations" -ne 0 ]; then + echo "gen-catalog: validation failed — fix the frontmatter above (SPEC.md §3)" >&2 + exit 1 +fi + +LC_ALL=C sort -o "$DATA" "$DATA" + +# --------------------------------------------------------------------------- +# Catalog rendering (the marker interior). +# --------------------------------------------------------------------------- + +render_interior() { + echo "_Generated from skill frontmatter by \`scripts/gen-catalog.sh\` — edit the skills' frontmatter, not this table (kit-ci fails on drift)._" + echo "" + echo '### Gates (mode-mapped via `gates.modes.`)' + echo "" + echo "| Skill | Protects | Cost | Requires | Gate key | CI job |" + echo "|---|---|---|---|---|---|" + while IFS=' ' read -r name description cost protects requires gate_key ci_job; do + [ "$gate_key" = "none" ] && continue + [ "$requires" = "nothing" ] && requires='—' + [ "$ci_job" = "none" ] && ci_job='—' + printf '| %s | %s | %s | %s | `%s` | %s |\n' \ + "$name" "$protects" "$cost" "$requires" "$gate_key" "$ci_job" + done < "$DATA" + echo "" + echo "### Workflows & conventions (never mode-gated)" + echo "" + echo "| Skill | Protects | Cost | Requires |" + echo "|---|---|---|---|" + while IFS=' ' read -r name description cost protects requires gate_key ci_job; do + [ "$gate_key" = "none" ] || continue + [ "$requires" = "nothing" ] && requires='—' + printf '| %s | %s | %s | %s |\n' "$name" "$protects" "$cost" "$requires" + done < "$DATA" +} + +render_readme() { # print the full regenerated README to stdout + if ! grep -q "^$BEGIN_MARK\$" "$README" || ! grep -q "^$END_MARK\$" "$README"; then + echo "gen-catalog: $README is missing the '$BEGIN_MARK' / '$END_MARK' markers — cannot place the catalog (SPEC.md §3)" >&2 + exit 1 + fi + sed -n "1,/^$BEGIN_MARK\$/p" "$README" + render_interior + sed -n "/^$END_MARK\$/,\$p" "$README" +} + +# --------------------------------------------------------------------------- +# --modes: the §4.3 strictness default-map table, derived mechanically. +# --------------------------------------------------------------------------- + +# Static cost class for keys not owned by any skill (they belong to hooks / +# agents / pre-pr steps). +static_cost() { + case "$1" in + secrets_scan|credential_files|worktree_isolation) echo "free" ;; + security_review|test_gap|spec_drift) echo "subagents" ;; + integration_tests|ui_build|build_artifact) echo "cheap" ;; + *) echo "cheap" ;; + esac +} + +render_modes() { + echo "| Gate key | prototype | standard | production |" + echo "|---|---|---|---|" + for key in $GATE_KEYS; do + # Worst-case cost across owning skills; static table when unowned. + owned_costs=$(cut -f3,6 "$DATA" | grep " $key\$" | cut -f1) || true + owned_ci=$(cut -f6,7 "$DATA" | grep "^$key " | cut -f2 | grep -v '^none$') || true + if [ -n "$owned_costs" ]; then + if printf '%s\n' "$owned_costs" | grep -q '^subagents$'; then + cost="subagents" + else + cost="cheap" + fi + else + cost=$(static_cost "$key") + fi + + production="enforce" + if [ "$cost" = "subagents" ]; then standard="suggest"; else standard="enforce"; fi + prototype="$standard" + if key_in_set "$key" "$TREND_GATES"; then prototype="suggest"; fi + + # Pinned overrides win at every level (§4.3 invariants): + # deterministic-protective gates, and CI-backed gates (§2.3). + if key_in_set "$key" "$PINNED_GATES" || [ -n "$owned_ci" ]; then + prototype="enforce"; standard="enforce"; production="enforce" + fi + + printf '| %s | %s | %s | %s |\n' "$key" "$prototype" "$standard" "$production" + done + echo "" + echo "_Preset overlays (SPEC.md §4.3): \`llm-app\` pins \`prompt_regression\` to enforce; \`library\`/solo presets default \`worktree_isolation\` to suggest._" + echo "_generated by scripts/gen-catalog.sh --modes — the normative copy lives in docs/PROFILE.md once §4 ships_" +} + +# --------------------------------------------------------------------------- +# Mode dispatch. +# --------------------------------------------------------------------------- + +case "${1:-}" in + "") + render_readme > "$SCRATCH" + cat "$SCRATCH" > "$README" + echo "gen-catalog: wrote catalog into $README" + ;; + --check) + render_readme > "$SCRATCH" + if ! diff -u "$README" "$SCRATCH" >&2; then + echo "gen-catalog: catalog drifted — run scripts/gen-catalog.sh" >&2 + exit 1 + fi + echo "gen-catalog: catalog is current" + ;; + --modes) + render_modes + ;; + *) + echo "gen-catalog: unknown argument '$1' (usage: gen-catalog.sh [--check|--modes])" >&2 + exit 2 + ;; +esac diff --git a/scripts/gen-manifest.sh b/scripts/gen-manifest.sh new file mode 100755 index 0000000..0351fbe --- /dev/null +++ b/scripts/gen-manifest.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# gen-manifest.sh — write/verify the per-version kit file-hash manifest (PT10). +# +# SPEC citation: SPEC.md §12.1 — kit-update needs a "path → sha256" manifest +# recorded at install/update time so SAFE/NEEDS-REVIEW classification is real. +# Tier-0 per SPEC.md §10: bash + git + coreutils only; must run on macOS +# bash 3.2 (shasum) and Linux (sha256sum). +# +# Usage: +# scripts/gen-manifest.sh write .claude/kit-manifest.sha256 +# scripts/gen-manifest.sh --check exit 1 if the committed manifest drifted +# +# Env: KIT_ROOT overrides the repo root (default: git rev-parse --show-toplevel). + +set -euo pipefail + +KIT_ROOT="${KIT_ROOT:-$(git rev-parse --show-toplevel)}" +cd "$KIT_ROOT" + +MANIFEST=".claude/kit-manifest.sha256" + +if command -v sha256sum >/dev/null 2>&1; then + HASH="sha256sum" +elif command -v shasum >/dev/null 2>&1; then + HASH="shasum -a 256" +else + echo "gen-manifest: neither sha256sum nor shasum found on PATH" >&2 + exit 1 +fi + +TMP=$(mktemp "${TMPDIR:-/tmp}/gen-manifest.XXXXXX") +trap 'rm -f "$TMP"' EXIT + +# The kit-managed set kit-update enumerates (SPEC.md §12.1). The manifest +# itself is never included. Paths are repo-root-relative, sorted LC_ALL=C. +list_covered() { + for f in .claude/skills/*/SKILL.md \ + .claude/agents/*.md \ + .claude/hooks/*.sh \ + .claude/settings.template.json; do + if [ -f "$f" ]; then + printf '%s\n' "$f" + fi + done | LC_ALL=C sort +} + +files=$(list_covered) +if [ -z "$files" ]; then + echo "gen-manifest: no kit-managed files found under $KIT_ROOT — wrong root?" >&2 + exit 1 +fi + +printf '%s\n' "$files" | while IFS= read -r f; do + $HASH "$f" +done > "$TMP" + +case "${1:-}" in + "") + cat "$TMP" > "$MANIFEST" + n=$(grep -c . "$MANIFEST") + echo "gen-manifest: wrote $MANIFEST ($n files)" + ;; + --check) + if [ ! -f "$MANIFEST" ]; then + echo "gen-manifest: $MANIFEST is missing — manifest drifted — run scripts/gen-manifest.sh" >&2 + exit 1 + fi + if ! diff -u "$MANIFEST" "$TMP" >&2; then + echo "gen-manifest: manifest drifted — run scripts/gen-manifest.sh" >&2 + exit 1 + fi + echo "gen-manifest: manifest is current" + ;; + *) + echo "gen-manifest: unknown argument '$1' (usage: gen-manifest.sh [--check])" >&2 + exit 2 + ;; +esac From 08e070fdc2f330a6fd26a498e3c41dead4996cef Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:45:52 +0000 Subject: [PATCH 3/3] docs(solutions): capture the stale-local-trunk secret-scan false positive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref: SPEC.md §14 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AUZigEcZo8BczjgS7gWUzZ --- ...026-07-06-secret-scan-stale-local-trunk.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/solutions/2026-07-06-secret-scan-stale-local-trunk.md diff --git a/docs/solutions/2026-07-06-secret-scan-stale-local-trunk.md b/docs/solutions/2026-07-06-secret-scan-stale-local-trunk.md new file mode 100644 index 0000000..65eb0ca --- /dev/null +++ b/docs/solutions/2026-07-06-secret-scan-stale-local-trunk.md @@ -0,0 +1,43 @@ +# secret-scan-diff.sh false-positives when the local trunk ref is stale + +- **Date:** 2026-07-06 +- **Area:** `.claude/hooks/secret-scan-diff.sh` / git branch-diff scope +- **Symptom:** `BLOCKED: Possible secret(s) detected in diff.` naming a line + you did not touch on this branch — e.g. + `[AWS access key ID] .github/workflows/kit-ci.yml: printf 'AWS_KEY = "%s"\n' "AKIAABCDEFGHIJKLMNOP"` — + and appending the `# pragma: allowlist secret` opt-out appears to have no + effect (the reported line still shows without the pragma). + +## Root cause + +With nothing staged, the hook scans `git diff ...HEAD -U0`, where +`` is the **local** ref named by `trunk_branch` in `.claude/kit.yaml`. +In a fresh clone checked out on a feature branch (CI runners, remote agent +sessions), local `main` can point far behind `origin/main`, so the scanned +range includes every already-merged upstream commit — and any key-shaped +string one of them added (here: the planted test vector in kit-ci.yml's +behavioral hook test) is reported as if the branch added it. The pragma edit +"not working" is the same confusion: the hook scans *committed* added lines, +so a working-tree edit — and the pragma-less historical copy of the line — +is what it keeps seeing. + +## Solution + +Sync the local trunk ref, then re-run: + +```bash +git fetch origin main && git branch -f main origin/main +bash .claude/hooks/secret-scan-diff.sh