crewforth 0.0.1 → 3.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +3784 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -7
  4. package/VERSION +1 -0
  5. package/adopt.sh +1670 -0
  6. package/bin/cli.js +497 -0
  7. package/kit/AGENT_TEMPLATE.md +92 -0
  8. package/kit/CLAUDE.md +228 -0
  9. package/kit/README.md +107 -0
  10. package/kit/agents/crew-backend-expert.md +95 -0
  11. package/kit/agents/crew-commit-agent.md +96 -0
  12. package/kit/agents/crew-database-expert.md +84 -0
  13. package/kit/agents/crew-devops-expert.md +82 -0
  14. package/kit/agents/crew-frontend-expert.md +85 -0
  15. package/kit/agents/crew-performance-expert.md +78 -0
  16. package/kit/agents/crew-planner.md +60 -0
  17. package/kit/agents/crew-privacy-agent.md +71 -0
  18. package/kit/agents/crew-review-agent.md +95 -0
  19. package/kit/agents/crew-security-expert.md +73 -0
  20. package/kit/agents/crew-session-manager.md +77 -0
  21. package/kit/agents/crew-test-expert.md +64 -0
  22. package/kit/eval/doctor.sh +465 -0
  23. package/kit/eval/gate-report.sh +137 -0
  24. package/kit/eval/golden-holdout.txt +53 -0
  25. package/kit/eval/golden-routing.txt +199 -0
  26. package/kit/eval/lib/crew-env.sh +14 -0
  27. package/kit/eval/lib/settings-json.awk +154 -0
  28. package/kit/eval/lib/star.sh +70 -0
  29. package/kit/eval/parser-conformance.sh +612 -0
  30. package/kit/eval/preflight.sh +186 -0
  31. package/kit/eval/routing-eval.sh +209 -0
  32. package/kit/eval/scan-skill.sh +162 -0
  33. package/kit/eval/smoke-test.sh +6693 -0
  34. package/kit/eval/update-guard.sh +81 -0
  35. package/kit/eval/utilization.sh +130 -0
  36. package/kit/hooks/board-sync.sh +106 -0
  37. package/kit/hooks/board.sh +962 -0
  38. package/kit/hooks/commit-msg +49 -0
  39. package/kit/hooks/context-usage.sh +353 -0
  40. package/kit/hooks/floor-blocklist.txt +124 -0
  41. package/kit/hooks/guard-bash.sh +1431 -0
  42. package/kit/hooks/guard-commit-scan.sh +511 -0
  43. package/kit/hooks/guard-write.sh +513 -0
  44. package/kit/hooks/pre-commit +577 -0
  45. package/kit/hooks/route-hint.sh +185 -0
  46. package/kit/hooks/secret-blocklist.txt +82 -0
  47. package/kit/hooks/session-guard.sh +149 -0
  48. package/kit/hooks/session-rehydrate.sh +41 -0
  49. package/kit/hooks/session-stats.sh +252 -0
  50. package/kit/hooks/session-update-check.sh +236 -0
  51. package/kit/hooks/skill-trust.sh +150 -0
  52. package/kit/hooks/trace-blocklist.txt +53 -0
  53. package/kit/settings.json +132 -0
  54. package/kit/skills/a11y/SKILL.md +44 -0
  55. package/kit/skills/adr/SKILL.md +74 -0
  56. package/kit/skills/api-design/SKILL.md +50 -0
  57. package/kit/skills/automode-policy/SKILL.md +91 -0
  58. package/kit/skills/automode-policy/references/policy.json +13 -0
  59. package/kit/skills/automode-policy/scripts/apply.sh +84 -0
  60. package/kit/skills/automode-policy/scripts/check.sh +102 -0
  61. package/kit/skills/backend-architecture/SKILL.md +103 -0
  62. package/kit/skills/brainstorm/SKILL.md +47 -0
  63. package/kit/skills/ci-pipeline/SKILL.md +38 -0
  64. package/kit/skills/commit-message/SKILL.md +82 -0
  65. package/kit/skills/commit-message/references/examples.md +8 -0
  66. package/kit/skills/confidence-check/SKILL.md +86 -0
  67. package/kit/skills/crew-board/SKILL.md +55 -0
  68. package/kit/skills/crew-brainstorm/SKILL.md +19 -0
  69. package/kit/skills/crew-code-review/SKILL.md +151 -0
  70. package/kit/skills/crew-code-review/references/history-search.md +19 -0
  71. package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
  72. package/kit/skills/crew-code-review/references/receiving.md +23 -0
  73. package/kit/skills/crew-doctor/SKILL.md +36 -0
  74. package/kit/skills/crew-gates/SKILL.md +25 -0
  75. package/kit/skills/crew-handoff/SKILL.md +14 -0
  76. package/kit/skills/crew-plan/SKILL.md +18 -0
  77. package/kit/skills/crew-review/SKILL.md +23 -0
  78. package/kit/skills/crew-ship/SKILL.md +19 -0
  79. package/kit/skills/crew-skill/SKILL.md +61 -0
  80. package/kit/skills/crew-studio/SKILL.md +129 -0
  81. package/kit/skills/crew-update/SKILL.md +43 -0
  82. package/kit/skills/db-migration/SKILL.md +120 -0
  83. package/kit/skills/db-migration/references/backup-restore.md +33 -0
  84. package/kit/skills/db-migration/references/raw-sql.md +30 -0
  85. package/kit/skills/db-migration/references/tool-matrix.md +35 -0
  86. package/kit/skills/dependency-audit/SKILL.md +61 -0
  87. package/kit/skills/dependency-upgrade/SKILL.md +82 -0
  88. package/kit/skills/deploy/SKILL.md +100 -0
  89. package/kit/skills/deploy/references/proxy-ssl.md +56 -0
  90. package/kit/skills/deploy/references/rollback.md +18 -0
  91. package/kit/skills/deploy/references/scripts.md +9 -0
  92. package/kit/skills/deploy/references/topology-a.md +128 -0
  93. package/kit/skills/deploy/references/topology-b.md +44 -0
  94. package/kit/skills/docs-writer/SKILL.md +53 -0
  95. package/kit/skills/docs-writer/references/coauthoring.md +34 -0
  96. package/kit/skills/eval-grader/SKILL.md +75 -0
  97. package/kit/skills/eval-grader/references/method.md +51 -0
  98. package/kit/skills/frontend/SKILL.md +56 -0
  99. package/kit/skills/frontend/references/verify-contract.md +41 -0
  100. package/kit/skills/frontend-design/SKILL.md +71 -0
  101. package/kit/skills/frontend-design/references/design-review.md +34 -0
  102. package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
  103. package/kit/skills/handoff/SKILL.md +71 -0
  104. package/kit/skills/handoff/references/cold-reader.md +51 -0
  105. package/kit/skills/i18n-integrity/SKILL.md +31 -0
  106. package/kit/skills/incident-runbook/SKILL.md +40 -0
  107. package/kit/skills/incident-runbook/references/postmortem.md +16 -0
  108. package/kit/skills/iterate/SKILL.md +40 -0
  109. package/kit/skills/mcp-builder/SKILL.md +54 -0
  110. package/kit/skills/mcp-builder/references/building.md +71 -0
  111. package/kit/skills/observability/SKILL.md +50 -0
  112. package/kit/skills/performance/SKILL.md +38 -0
  113. package/kit/skills/performance/references/bottlenecks.md +10 -0
  114. package/kit/skills/privacy-compliance/SKILL.md +75 -0
  115. package/kit/skills/red-team/SKILL.md +48 -0
  116. package/kit/skills/reflect/SKILL.md +57 -0
  117. package/kit/skills/release/SKILL.md +44 -0
  118. package/kit/skills/security-scan/SKILL.md +99 -0
  119. package/kit/skills/security-scan/references/ai-agents.md +107 -0
  120. package/kit/skills/security-scan/references/fronts.md +99 -0
  121. package/kit/skills/security-scan/references/prompting.md +23 -0
  122. package/kit/skills/security-scan/references/reporting.md +75 -0
  123. package/kit/skills/security-scan/references/verify.md +131 -0
  124. package/kit/skills/sonarqube-check/SKILL.md +108 -0
  125. package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
  126. package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
  127. package/kit/skills/spec-planning/SKILL.md +92 -0
  128. package/kit/skills/systematic-debugging/SKILL.md +57 -0
  129. package/kit/skills/systematic-debugging/references/techniques.md +44 -0
  130. package/kit/skills/teamboard/SKILL.md +88 -0
  131. package/kit/skills/teamboard/references/setup.md +37 -0
  132. package/kit/skills/testing/SKILL.md +49 -0
  133. package/kit/skills/testing/references/flaky-triage.md +51 -0
  134. package/kit/skills/threat-model/SKILL.md +57 -0
  135. package/kit/skills/threat-model/references/interview.md +51 -0
  136. package/kit/skills/threat-model/references/schema.md +42 -0
  137. package/kit/skills/token-budget/SKILL.md +51 -0
  138. package/kit/skills/trace-scan/SKILL.md +43 -0
  139. package/kit/skills/worktree/SKILL.md +40 -0
  140. package/kit/studio/README.md +266 -0
  141. package/kit/studio/ensure-node.sh +471 -0
  142. package/kit/studio/package.json +14 -0
  143. package/kit/studio/server/hooks/studio-gate.sh +169 -0
  144. package/kit/studio/server/index.js +902 -0
  145. package/kit/studio/server/lib/crew-env.js +11 -0
  146. package/kit/studio/server/lib/fleet.js +146 -0
  147. package/kit/studio/server/lib/graph.js +533 -0
  148. package/kit/studio/server/lib/instance.js +135 -0
  149. package/kit/studio/server/lib/kit-telemetry.js +182 -0
  150. package/kit/studio/server/lib/kit.js +158 -0
  151. package/kit/studio/server/lib/palette.js +118 -0
  152. package/kit/studio/server/lib/peers.js +72 -0
  153. package/kit/studio/server/lib/permissions.js +150 -0
  154. package/kit/studio/server/lib/projects.js +294 -0
  155. package/kit/studio/server/lib/roster.js +191 -0
  156. package/kit/studio/server/lib/session.js +309 -0
  157. package/kit/studio/server/lib/terminal.js +91 -0
  158. package/kit/studio/server/lib/transcript.js +100 -0
  159. package/kit/studio/web/app.js +1098 -0
  160. package/kit/studio/web/canvas.js +1179 -0
  161. package/kit/studio/web/chat.js +543 -0
  162. package/kit/studio/web/index.html +86 -0
  163. package/kit/studio/web/md.js +110 -0
  164. package/kit/studio/web/storage-migrate.js +23 -0
  165. package/kit/studio/web/style.css +1453 -0
  166. package/package.json +46 -8
  167. package/start.sh +748 -0
  168. package/bin/crewforth.js +0 -10
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: crew-test-expert
3
+ color: yellow
4
+ description: |
5
+ Test expert. Use proactively after new handler/endpoint/agent behavior is added: writes and runs
6
+ unit/integration tests and guarantees the DoD's "tests are green".
7
+ tools: Read, Grep, Glob, Edit, Write, Bash, PowerShell
8
+ metadata:
9
+ stage: audit
10
+ skills: [testing]
11
+ ---
12
+
13
+ # Test Expert
14
+
15
+ <!-- routing-eval reads the next line; why it sits in the body: AGENT_TEMPLATE.md -->
16
+ Trigger phrases: "write tests", "run tests", "coverage", "are the tests green", "unit test", "integration test"
17
+
18
+ ## Expertise stance (senior SDET)
19
+ - **Test behavior, not implementation**: tests that don't break on refactor.
20
+ - The happy path isn't enough: **boundary/negative/concurrency** scenarios.
21
+ - Tests are fast, isolated, **deterministic**, with self-documenting names.
22
+ - **Risk coverage over metrics**: prioritize critical paths.
23
+ - **A flaky test is a bug**; don't tolerate it, fix it at the root.
24
+
25
+ ## When
26
+ When a new business handler, endpoint, validator, or native agent behavior is added.
27
+
28
+ ## How (applies the `testing` skill)
29
+ The "how" lives in the `testing` skill; this agent applies it.
30
+ - Per handler: happy path + validation failure + authorization (IDOR/404) scenarios.
31
+ - Short-lived code/OTP: expiration, single-use, brute-force limit scenarios.
32
+ - Deterministic tests; external dependencies mocked/faked.
33
+ - Red-green: failing test first, then implementation (goal-driven principle). After the last edit, run the suite once and
34
+ report command + exit code + pass/fail counts; do not re-run it on code nobody has touched since.
35
+
36
+ ## Coordination (cross-agent)
37
+ - Source of the tested behavior → align with **crew-backend-expert** / **crew-frontend-expert**.
38
+ - Security scenarios (IDOR/authorization/404) → turn **crew-security-expert** findings into tests.
39
+ - Path that processes personal data → verify scope with **crew-privacy-agent**.
40
+ - A test that needs real volume or a timing budget to mean anything → **crew-performance-expert** owns the measurement.
41
+ - At closure, report findings to **crew-review-agent** — the LAST reviewer, once every audit above is clean.
42
+
43
+ ## DoD
44
+ - The project's own test command is green — the one its manifest/CI already uses (`dotnet test`, `npm test`,
45
+ `pytest`, `go test ./...`, …). Detect it, never assume it: a hard-coded runner is how this agent ends up
46
+ reporting on a stack the project does not use.
47
+ - Critical paths covered; no empty/meaningless tests.
48
+
49
+ ## Constraints
50
+ - Don't break the product code to make a test pass; test the real behavior.
51
+
52
+ ## Output & context (token)
53
+ To the main thread: number of tests added + scenarios covered + **green/red** result. Full test log → in a file.
54
+
55
+ ## Errors/escalation
56
+ If the tests won't go green, **stop and report the reason** without breaking the product code; don't count a flaky test as 'passed'.
57
+
58
+ ## Example delegation
59
+ - ✅ Writing tests for a new handler/flow
60
+ - ❌ Product code implementation (to the relevant expert)
61
+
62
+ ## Prohibitions (absolute)
63
+ CLAUDE.md §4 applies: no AI trace or vendor template name in test code / names ·
64
+ commit/push only with explicit approval.
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env bash
2
+ # Install doctor — verify a LIVE kit install in a CONSUMER repo is actually active. This is the counterpart to
3
+ # smoke-test.sh: smoke-test checks Crewforth's SOURCE (dev-side, in this repo); doctor checks a real install on a
4
+ # user's machine — the things that silently make the gates inert: a hook left non-executable, core.hooksPath not
5
+ # set (so the commit trace/secret scan never runs), settings.json missing (so the tool-level gates never fire).
6
+ # Zero-dep, bash-only, Git-Bash safe. Run from the project root (or pass the path): bash .claude/eval/doctor.sh
7
+ set -uo pipefail
8
+ # The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
9
+ _crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
10
+ [ -f "$_crew_d/lib/crew-env.sh" ] && . "$_crew_d/lib/crew-env.sh"; unset _crew_d
11
+ ROOT="${1:-.}"
12
+ cd "$ROOT" 2>/dev/null || { echo "doctor: cannot enter '$ROOT'"; exit 2; }
13
+
14
+ # Nothing here needs input, and everything here spawns subprocesses that INHERIT this script's stdin. A hook
15
+ # reads its payload with `cat`, so one invocation without a pipe would sit waiting for a terminal that is never
16
+ # going to type — which is exactly what a "ran 120s and produced nothing" report looks like. Detaching stdin once
17
+ # makes that class of hang impossible instead of relying on every call site remembering to redirect.
18
+ exec </dev/null
19
+
20
+ FAIL=0
21
+ ok(){ echo " ✅ $1"; }
22
+ bad(){ echo " ❌ $1"; echo " ↳ fix: $2"; FAIL=$((FAIL+1)); }
23
+ warn(){ echo " ⚠️ $1"; }
24
+ # `skip` lives up here with the other reporters, not down in the readiness block where it used to be
25
+ # defined: the health checks call it too, and a helper defined after its first caller is a silent
26
+ # no-op — the line never prints and the run shows `skip: command not found` on stderr, where nobody
27
+ # looks. Found by RUNNING doctor against a fixture install; grepping for the call sites said wired.
28
+ skip(){ echo " · $1"; }
29
+
30
+ echo "== Crewforth — install doctor =="
31
+
32
+ # 0) Is Crewforth even here?
33
+ [ -d .claude ] || { echo " ❌ no .claude/ in '$PWD' — is Crewforth installed here?"; echo " ↳ fix: npx crewforth adopt"; exit 1; }
34
+
35
+ # 1) VERSION (marks a full install; also what /crew-update compares)
36
+ if [ -f .claude/VERSION ]; then ok "VERSION present ($(head -1 .claude/VERSION | tr -cd '0-9A-Za-z.-'))"
37
+ else bad "VERSION missing" "reinstall or update Crewforth (npx crewforth update)"; fi
38
+
39
+ # 1b) Is that version the current one? Read-only, from the cache session-update-check.sh maintains — doctor makes
40
+ # no network call of its own, so this stays honest offline (no cache -> nothing said) and instant everywhere.
41
+ # The session notice fires once per release; this is the surface that still answers when it was missed.
42
+ if [ -f .claude/VERSION ] && [ -f .claude/.state/update-check ]; then
43
+ read -r DLATEST _ < .claude/.state/update-check 2>/dev/null || DLATEST=""
44
+ DLATEST="$(printf '%s' "${DLATEST:-}" | tr -cd '0-9A-Za-z.-')"
45
+ DCUR="$(head -1 .claude/VERSION 2>/dev/null | tr -cd '0-9A-Za-z.-')"
46
+ if [ -n "$DLATEST" ] && awk -v a="$DLATEST" -v b="$DCUR" 'BEGIN{split(a,x,".");split(b,y,".");
47
+ for(i=1;i<=3;i++){if(x[i]+0>y[i]+0)exit 0; if(x[i]+0<y[i]+0)exit 1} exit 1}'; then
48
+ warn "Crewforth v$DCUR installed, v$DLATEST published — update with /crew-update"
49
+ fi
50
+ fi
51
+
52
+ # 2) Hooks present + executable. The .sh set is a glob (extras ok); pre-commit + commit-msg are REQUIRED — they
53
+ # ARE the §4.1/§4.2 trace/secret gate, so a MISSING one is a failure, not a silent skip.
54
+ NX=""; GONE=""
55
+ for h in .claude/hooks/*.sh; do [ -e "$h" ] || continue; [ -x "$h" ] || NX="$NX $(basename "$h")"; done
56
+ for h in .claude/hooks/pre-commit .claude/hooks/commit-msg; do
57
+ if [ ! -e "$h" ]; then GONE="$GONE $(basename "$h")"; elif [ ! -x "$h" ]; then NX="$NX $(basename "$h")"; fi
58
+ done
59
+ [ -z "$GONE" ] && ok "required git hooks present (pre-commit, commit-msg)" \
60
+ || bad "MISSING git hook(s):$GONE — the commit trace/secret scan is absent" "reinstall or update Crewforth"
61
+ [ -z "$NX" ] && ok "all hooks are executable" \
62
+ || bad "not executable:$NX" "chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg"
63
+
64
+ # 2b) Behaviour probe — a hook that is present + executable can still be NEUTERED (its body replaced with `exit 0`).
65
+ # Drive guard-bash with a command it MUST block; if it does not exit 2, the §4.5 gate is disarmed.
66
+ if [ -x .claude/hooks/guard-bash.sh ]; then
67
+ # CREW_GATE_LOG=/dev/null: this probe drives the real gate, so without it every `/crew-doctor` writes a
68
+ # synthetic "git push --force blocked" line into the evidence log — and the gate report would then be
69
+ # counting the diagnostics instead of what the model reached for. A measurement tool that contaminates the
70
+ # thing it measures is worse than none.
71
+ if printf '%s' '{"tool_name":"Bash","permission_mode":"auto","tool_input":{"command":"git push --force"}}' | CREW_GATE_LOG=/dev/null bash .claude/hooks/guard-bash.sh >/dev/null 2>&1; then
72
+ bad "guard-bash.sh did NOT block a force-push — the §4.5 gate is neutered/disarmed" "restore guard-bash.sh from Crewforth (npx crewforth update)"
73
+ else ok "guard-bash.sh blocks a force-push (gate live, not neutered)"; fi
74
+
75
+ # 2c) The §4.6 review gate, probed the same way — and probed on the ONE case whose verdict cannot depend on
76
+ # this project's state. "git commit" alone is no probe: with a matching review-pass.json it legitimately
77
+ # passes §4.6, and under a mode that cannot prompt §4.4 blocks it anyway, so exit 2 would prove nothing
78
+ # about §4.6. A commit pointed at another worktree is refused by §4.6 whatever else is true here, and the
79
+ # verdict is read from the MESSAGE rather than the exit code for the same reason. Mode `default` so that a
80
+ # disarmed gate answers "ask" and exits 0 instead of colliding with §4.4's fail-closed branch. Nothing
81
+ # runs: this is a PreToolUse payload, not a command.
82
+ PROBE46="$(printf '%s' '{"tool_name":"Bash","permission_mode":"default","tool_input":{"command":"git -C /nonexistent-crew-probe commit -m probe"}}' \
83
+ | CREW_GATE_LOG=/dev/null bash .claude/hooks/guard-bash.sh 2>&1 >/dev/null)"
84
+ case "$PROBE46" in
85
+ *"4.6"*) ok "guard-bash.sh enforces the §4.6 review gate (gate live, not neutered)" ;;
86
+ *) bad "guard-bash.sh did NOT enforce §4.6 — a commit can land with no review of its diff" \
87
+ "restore guard-bash.sh from Crewforth (and check crew-review-agent still writes .claude/review-pass.json)" ;;
88
+ esac
89
+ fi
90
+
91
+ # 3) core.hooksPath — without it the §4.1/§4.2 commit trace + secret/bloat scan never runs
92
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
93
+ HP="$(git config --get core.hooksPath 2>/dev/null || true)"
94
+ case "$HP" in
95
+ */.claude/hooks|.claude/hooks) ok "core.hooksPath -> $HP (commit-time gates active)" ;;
96
+ "") bad "core.hooksPath is unset — commit trace/secret/bloat gates are INACTIVE" "git config core.hooksPath .claude/hooks" ;;
97
+ *) bad "core.hooksPath -> $HP (not Crewforth's hooks)" "git config core.hooksPath .claude/hooks" ;;
98
+ esac
99
+ else
100
+ warn "not a git repo — commit gates need: git init && git config core.hooksPath .claude/hooks"
101
+ fi
102
+
103
+ # 4) settings.json wires the tool-level gates, and each event maps to a NON-EMPTY hook array (an empty [] wires
104
+ # nothing). PreToolUse/UserPromptSubmit/Stop are required; SessionStart (rehydration) is a warn if absent.
105
+ S=.claude/settings.json
106
+ if [ -f "$S" ]; then
107
+ # ONE READER on every machine: Crewforth's own awk JSON reader, the same file adopt.sh merges with. There were two
108
+ # branches here — jq where jq ran, a name-and-bracket shape check where it did not — and the second could not
109
+ # tell valid JSON from invalid at all, so a stock Windows box got a weaker doctor than a Mac. Now both get the
110
+ # parse: validity, then each event's array length.
111
+ SJ=.claude/eval/lib/settings-json.awk
112
+ if [ ! -f "$SJ" ]; then
113
+ bad "Crewforth's JSON reader is missing ($SJ) — settings.json cannot be checked" "update Crewforth"
114
+ elif awk -v op=validate -f "$SJ" "$S" 2>/dev/null; then
115
+ ok "settings.json is valid JSON"
116
+ EMPTY=""
117
+ for ev in PreToolUse UserPromptSubmit Stop; do
118
+ n="$(awk -v op=len -v path="hooks.$ev" -f "$SJ" "$S" 2>/dev/null)"
119
+ case "$n" in ''|0) EMPTY="$EMPTY $ev" ;; esac
120
+ done
121
+ [ -z "$EMPTY" ] && ok "settings.json wires PreToolUse / UserPromptSubmit / Stop (non-empty)" \
122
+ || bad "settings.json hook events empty or missing:$EMPTY — those gates won't fire" "restore settings.json from Crewforth (npx crewforth update)"
123
+ sn="$(awk -v op=len -v path=hooks.SessionStart -f "$SJ" "$S" 2>/dev/null)"
124
+ case "$sn" in ''|0) warn "SessionStart not wired — session rehydration after /compact or /clear is inactive (update Crewforth)" ;; *) ok "SessionStart wired (session rehydration active)" ;; esac
125
+ else bad "settings.json is invalid JSON" "fix the syntax by hand — restoring Crewforth's file would drop any hooks you added"; fi
126
+ # `${CLAUDE_PROJECT_DIR}` inside a hook command is the shape that breaks on Windows, and it breaks invisibly.
127
+ # Claude Code substitutes that placeholder into the command STRING before any shell sees it; on Windows the
128
+ # value is `C:\Repos\app` and the separators are gone by the time bash reads it. The reported path was
129
+ # `C:ReposApp/.claude/hooks/...` — every hook failed to launch and every gate was absent, while settings.json
130
+ # looked perfectly correct on inspection. Crewforth now uses a RELATIVE path (hooks run in the project
131
+ # directory), with a `cd` off the bare `$CLAUDE_PROJECT_DIR` as a belt for a session started in a subdirectory.
132
+ # Bare `$VAR` is not the placeholder syntax, so Claude Code leaves it for the shell to expand.
133
+ #
134
+ # Reported on every platform, not only Windows: a repo is shared across machines, and the wiring is wrong on
135
+ # all of them the moment one teammate is on Windows.
136
+ if grep -q '\${CLAUDE_PROJECT_DIR' "$S" 2>/dev/null; then
137
+ bad "settings.json wires hooks through the \${CLAUDE_PROJECT_DIR} placeholder — on Windows its separators are stripped before bash runs, so NO hook launches and every gate is silently absent" \
138
+ "update Crewforth (npx crewforth adopt) — hook commands become: cd \"\$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/<name>.sh"
139
+ else
140
+ ok "hook wiring carries no path placeholder (nothing for Windows to mangle)"
141
+ fi
142
+ else
143
+ bad "settings.json missing — the tool-level gates (commit approval, guards, context) are INACTIVE" "reinstall Crewforth"
144
+ fi
145
+
146
+ # 4a) Skill listing budget. Claude Code loads a listing of every skill's name + description into context each
147
+ # session; the budget is 1% of the context window, and over it descriptions get truncated or dropped
148
+ # "which can strip the keywords Claude needs to match your request" (skills docs). The failure is invisible:
149
+ # the skills are all still installed and still listed BY NAME, they just stop matching. Reported, never
150
+ # enforced — a project is free to ship many skills, it just needs to know the trade and the two documented
151
+ # ways out (raise skillListingBudgetFraction, or set low-priority skills to "name-only" in skillOverrides).
152
+ if [ -d .claude/skills ]; then
153
+ # ONE awk over every SKILL.md, not two per skill: 41 skills made this 82 spawns, which on Git Bash is several
154
+ # seconds on its own. Counts what `awk c==1 | awk '/^(name|description):/,0' | wc -c` counted — the frontmatter
155
+ # from its first name:/description: line on, in bytes. LC_ALL=C because wc -c counts bytes and gawk's length()
156
+ # counts characters under a UTF-8 locale: measured on Git Bash (gawk 5.4, LC_ALL=en_US.UTF-8), dropping it read
157
+ # the shipped set 38 short — its em dashes alone move the number. macOS awk counts bytes either way.
158
+ SKF=""; for f in .claude/skills/*/SKILL.md; do [ -e "$f" ] && SKF=1 && break; done
159
+ LISTING=0
160
+ [ -n "$SKF" ] && LISTING=$(LC_ALL=C awk '
161
+ FNR==1 { c=0; on=0 }
162
+ /^---$/ { c++; next }
163
+ c==1 { if (!on && /^(name|description):/) on=1; if (on) n += length($0) + 1 }
164
+ END { print n+0 }' .claude/skills/*/SKILL.md)
165
+ CW="${CONTEXT_WINDOW:-1000000}"; BUDGET=$((CW/100))
166
+ if [ "$LISTING" -le "$BUDGET" ]; then
167
+ ok "skill listing fits the budget (${LISTING} <= ${BUDGET} chars at 1% of a ${CW}-token window)"
168
+ else
169
+ warn "skill listing ${LISTING} chars EXCEEDS the ~${BUDGET} budget for a ${CW}-token window — Claude Code will"
170
+ warn " truncate or drop descriptions, and a skill whose description is gone stops matching requests."
171
+ warn " Fixes: raise \"skillListingBudgetFraction\" in settings, or set rarely-used skills to \"name-only\""
172
+ warn " in \"skillOverrides\". Re-check with a smaller CONTEXT_WINDOW=200000 to see your real model's budget."
173
+ warn " Which skills? bash .claude/eval/utilization.sh — it reports the ones nothing in this project reached."
174
+ fi
175
+ fi
176
+
177
+ # 4b) Is delegation itself switched off? The documented way to stop Claude using ANY subagent is to deny the `Agent`
178
+ # tool in permissions.deny. A project that does that keeps twelve agents on disk that can never run, and the only
179
+ # symptom is that every task quietly happens on the main thread — which reads as "Crewforth does nothing" rather
180
+ # than as a setting. Checked at every scope the CLI merges, because one line in ~/.claude/settings.json disables
181
+ # delegation for every project on the machine. Denying it may be deliberate; this names it, it does not judge.
182
+ DENYSRC=""
183
+ for f in .claude/settings.json .claude/settings.local.json "$HOME/.claude/settings.json"; do
184
+ [ -f "$f" ] || continue
185
+ # A deny entry for the delegation tool, in any of its spellings, with or without an argument pattern. Read
186
+ # with Crewforth's JSON reader, so "is Agent/Task in permissions.deny" is answered from the parse on every OS.
187
+ # It used to need python3: on Windows — a Store stub, or nothing — the check degraded to "both words appear in
188
+ # the file", a prompt instead of a verdict. An unreadable file is still NOT a silent pass (NOPY below).
189
+ if [ ! -f "${SJ:-.claude/eval/lib/settings-json.awk}" ] || ! awk -v op=validate -f "${SJ:-.claude/eval/lib/settings-json.awk}" "$f" 2>/dev/null; then
190
+ NOPY=1; MAYBEDENY="${MAYBEDENY:-} $f"; continue
191
+ fi
192
+ awk -v op=strings -v path=permissions.deny -f "${SJ:-.claude/eval/lib/settings-json.awk}" "$f" 2>/dev/null \
193
+ | grep -qE '^(Agent|Task)([^A-Za-z0-9_]|$)' && DENYSRC="$DENYSRC $f"
194
+ done
195
+ if [ "${NOPY:-0}" = 1 ]; then
196
+ warn "delegation check could not read:${MAYBEDENY} — not valid JSON (or Crewforth's reader is missing)."
197
+ warn " open it and check that Agent/Task is not under permissions.deny. If it is, no subagent can ever run."
198
+ fi
199
+ # `A && B && ok … || bad …` cannot express three outcomes. With no usable python3 the && chain is false, so the
200
+ # || arm fired and reported `the Agent tool is DENIED in:` — an empty list, a failure that had not been found,
201
+ # and a verdict of ❌ on a healthy install. It stayed invisible while `command -v python3` was the test, because
202
+ # on Windows the Store stub answered yes and NOPY was never set. Three states, three branches.
203
+ if [ -n "$DENYSRC" ]; then
204
+ bad "the Agent tool is DENIED in:$DENYSRC — no subagent can ever run, so every agent on disk is dead weight" \
205
+ "remove the Agent/Task entry from permissions.deny, or accept that this project runs main-thread-only"
206
+ elif [ "${NOPY:-0}" != 1 ]; then
207
+ ok "delegation is enabled (the Agent tool is not denied)"
208
+ fi
209
+
210
+ # 5) Agent-name references resolve to an installed agent — checked across CLAUDE.md AND every local doc it points to
211
+ # (its @imports and docs/*.md references). A brownfield takeover renames the project's agents to `crew-` ids, but
212
+ # CLAUDE.md — or an orchestration doc it delegates to, e.g. "detail: docs/AGENTS.md" — may still name the OLD bare
213
+ # agent. That name matches no installed agent, so delegation to it silently fails. Following CLAUDE.md's reference
214
+ # chain catches the pointed-to docs too, while unreferenced prose (design/audit docs, code comments) is ignored —
215
+ # so it stays complete without the false positives a blanket repo scan would raise.
216
+ if [ -f CLAUDE.md ] && ls .claude/agents/*.md >/dev/null 2>&1; then
217
+ # Two pull-only agents are invoked explicitly (a commit needs approval; session health is emitted by a hook),
218
+ # NOT auto-delegated — so a bare reference to them does not break delegation; it is only a naming inconsistency.
219
+ PULL_AGENTS=" crew-commit-agent crew-session-manager "
220
+ # Scan set = CLAUDE.md + the local .md files it references (one level: its @imports and any `docs/…md` path).
221
+ SCAN="CLAUDE.md"
222
+ for r in $(grep -oE '@?[A-Za-z0-9_./-]+\.md' CLAUDE.md 2>/dev/null | sed 's/^@//' | sort -u); do
223
+ [ -f "$r" ] && [ "$r" != "CLAUDE.md" ] && SCAN="$SCAN $r"
224
+ done
225
+ # TWO awk passes, not a nested shell loop. This check used to run `sed|head|tr` per agent and then a
226
+ # `grep|cut|tr|sed` for every (agent × scanned file) pair — 12 agents against a handful of docs is already
227
+ # ~250 process spawns. On Linux/macOS that is invisible; on Windows, where Git Bash pays 62-135 ms per spawn
228
+ # instead of ~1.7ms, doctor stopped dead right here and looked hung to the user who ran it. Same disease the
229
+ # route-hint hook had, same cure: let awk do the looping. Two spawns, whatever the component count.
230
+ CREW_AGENT_BASES="$(awk '
231
+ FNR==1 { files[++nf]=FILENAME }
232
+ !got[FILENAME] && /^name:[[:space:]]*/ {
233
+ n=$0; sub(/^name:[[:space:]]*/,"",n); gsub(/[^a-zA-Z0-9-]/,"",n) # same charset tr -cd kept
234
+ if (n != "") { nm[FILENAME]=n; got[FILENAME]=1 }
235
+ }
236
+ END {
237
+ for (i=1;i<=nf;i++) {
238
+ f=files[i]; n=(f in nm) ? nm[f] : ""
239
+ if (n=="") { n=f; sub(/\.md$/,"",n); sub(/.*\//,"",n) } # fallback: the file name
240
+ if (n ~ /^crew-/) { b=n; sub(/^crew-/,"",b); print b "\t" n; print b "-csk\t" n } # the 2.x name is stale too
241
+ }
242
+ }' .claude/agents/*.md)"
243
+ export CREW_AGENT_BASES
244
+ STALE=""; STALE_PULL=""
245
+ while IFS="$(printf '\t')" read -r base name f lines; do
246
+ [ -n "$base" ] || continue
247
+ entry="
248
+ ↳ \"$base\" → \"$name\" ($f line(s): $lines)"
249
+ case "$PULL_AGENTS" in *" $name "*) STALE_PULL="$STALE_PULL$entry" ;; *) STALE="$STALE$entry" ;; esac
250
+ done <<EOF
251
+ $(awk '
252
+ BEGIN {
253
+ n = split(ENVIRON["CREW_AGENT_BASES"], rows, "\n"); k=0
254
+ for (i=1;i<=n;i++) { if (rows[i]=="") continue; split(rows[i], a, "\t"); k++; base[k]=a[1]; full[k]=a[2] }
255
+ nb=k
256
+ }
257
+ FNR==1 { order[++nf]=FILENAME }
258
+ {
259
+ # bare `base` NOT touching a `-` on either side (so not crew-base, nor base-local) and not glued into a longer word — the identical
260
+ # boundary the grep used. Agent ids are [a-z-] only, so nothing here needs regex escaping.
261
+ for (i=1;i<=nb;i++)
262
+ if ($0 ~ ("(^|[^a-zA-Z0-9_-]|@agent-)" base[i] "([^a-zA-Z0-9_-]|$)"))
263
+ hit[i, FILENAME] = (hit[i, FILENAME]=="" ? FNR : hit[i, FILENAME] "," FNR)
264
+ }
265
+ # Emitted in agent order, then scanned-file order, so the report reads the same as it always did rather
266
+ # than in awk hash order.
267
+ END {
268
+ for (i=1;i<=nb;i++)
269
+ for (j=1;j<=nf;j++)
270
+ if ((i, order[j]) in hit) print base[i] "\t" full[i] "\t" order[j] "\t" hit[i, order[j]]
271
+ }' $SCAN)
272
+ EOF
273
+ if [ -n "$STALE" ]; then
274
+ bad "CLAUDE.md (or a doc it references) names auto-delegated agent(s) that no installed agent matches — delegation to them silently fails" \
275
+ "rename each bare reference to its \`crew-\` id:$STALE"
276
+ fi
277
+ [ -n "$STALE_PULL" ] && warn "CLAUDE.md (or a referenced doc) names pull-only agent(s) by their old bare id — invoked explicitly, so delegation still works; rename for consistency:$STALE_PULL"
278
+ [ -z "$STALE$STALE_PULL" ] && ok "agent references resolve to installed agents (CLAUDE.md + referenced docs)"
279
+ fi
280
+
281
+ # 6) Does the discipline actually REACH the model? `.claude/DISCIPLINE.md` sitting on disk is inert unless
282
+ # `./CLAUDE.md` pulls it in — Claude Code reads CLAUDE.md, not Crewforth's own files. This is the one failure
283
+ # every check above is blind to: the hooks fire, the gates are live, and yet §1–§3 (routing, DoD, session
284
+ # management) never enter the context, so the model works without any of the discipline it is measured on.
285
+ # Two shapes load it: the `@import` line, or the pre-1.1 layout that pasted the discipline inline (stale,
286
+ # but loaded). Neither present -> absent, and that is a failure, not a style note.
287
+ if [ -f .claude/DISCIPLINE.md ]; then
288
+ if [ ! -f CLAUDE.md ]; then
289
+ bad "CLAUDE.md missing — .claude/DISCIPLINE.md is never loaded (routing / DoD / session rules absent)" \
290
+ "create CLAUDE.md with this as its own line: @.claude/DISCIPLINE.md"
291
+ elif grep -qE '^[[:space:]]*@\.claude/DISCIPLINE\.md[[:space:]]*$' CLAUDE.md; then
292
+ ok "CLAUDE.md imports .claude/DISCIPLINE.md (the discipline reaches the model)"
293
+ elif grep -q '^## Four working principles' CLAUDE.md && grep -qE '^### 4\.[45] ' CLAUDE.md; then
294
+ warn "CLAUDE.md carries the discipline INLINE (pre-1.1 layout) — it loads, but Crewforth updates never reach it; migrate to the '@.claude/DISCIPLINE.md' import line"
295
+ else
296
+ bad "CLAUDE.md does not import .claude/DISCIPLINE.md — the discipline is on disk but never loaded" \
297
+ "add this as its own line at the top of CLAUDE.md: @.claude/DISCIPLINE.md"
298
+ fi
299
+ fi
300
+
301
+ echo "---"
302
+ # The verdict is the line people read, and some read only it. The preflight block
303
+ # below reports a missing node, but it prints AFTER this — so on a machine that
304
+ # cannot start the panel the last word a reader takes away was "healthy". The
305
+ # verdict is not wrong (every gate is wired and holds without node) so it keeps
306
+ # its tick, and carries what it costs beside it.
307
+ # Resolved here rather than beside the preflight report below, because the verdict
308
+ # needs to ask it a question and the verdict prints first.
309
+ PREFLIGHT=".claude/eval/preflight.sh"
310
+ [ -f "$PREFLIGHT" ] || PREFLIGHT="$(dirname "$0")/preflight.sh"
311
+ PANEL_NOTE=""
312
+ if [ -d .claude/studio ] && ! bash "$PREFLIGHT" --has node 2>/dev/null; then
313
+ PANEL_NOTE=" · panel needs Node 18+ — .claude/studio/ensure-node.sh --plan fetches one"
314
+ fi
315
+ # A 2.x variable name still works until 4.0 (lib/crew-env.sh reads it); name its 3.0 spelling so the user can switch.
316
+ for _v in $(compgen -e); do
317
+ case "$_v" in CSK_CORRECT_STACK) ;; CSK_*)
318
+ case "${_crew_legacy:-}" in
319
+ *" ${_v#CSK_} "*) warn "$_v is set — its 3.0 name is CREW_${_v#CSK_} (the old name works until 4.0)" ;;
320
+ *) warn "$_v is set but no longer read — set CREW_${_v#CSK_} instead" ;;
321
+ esac ;;
322
+ esac
323
+ done
324
+ if [ "$FAIL" -eq 0 ]; then echo "DOCTOR: healthy ✅$PANEL_NOTE"
325
+ # Healthy verdict: the star line, once per kit version — the marker is shared with the installers, so the
326
+ # doctor run that /crew-update makes right after an update stays quiet. Text/URL/silence: lib/star.sh.
327
+ [ -f "$(dirname "$0")/lib/star.sh" ] && bash "$(dirname "$0")/lib/star.sh" --once .
328
+ else echo "DOCTOR: $FAIL issue(s) ❌ — apply the fixes above$PANEL_NOTE"; fi
329
+
330
+ # 8b) The shell matcher. Claude Code's hooks reference is explicit: inspect shell commands with
331
+ # `Bash|PowerShell`, because wherever the PowerShell tool is enabled it IS the shell — and it is on by
332
+ # default for claude.ai and Console accounts on Windows. An install from before 2.5.0 watches only `Bash`,
333
+ # so every PowerShell command walks past the §4.5 rules with nothing firing. Nothing about the session
334
+ # looks wrong, which is why this is a check and not a release note.
335
+ if [ -f .claude/settings.json ]; then
336
+ if grep -q '"matcher"[[:space:]]*:[[:space:]]*"[^"]*PowerShell' .claude/settings.json; then
337
+ ok "shell gates watch both Bash and PowerShell"
338
+ elif grep -q '"matcher"[[:space:]]*:[[:space:]]*"Bash"' .claude/settings.json; then
339
+ bad "shell gates watch only Bash — PowerShell commands bypass every §4.5 rule" \
340
+ "update Crewforth (npx crewforth update), or set the PreToolUse matcher to \"Bash|PowerShell\""
341
+ fi
342
+ fi
343
+ # 9) The auto-mode classifier. Since 2026-08-14 auto mode is the default permission mode on Pro/Max/Team, so a
344
+ # classifier answers permission prompts the user used to answer. Two things can be wrong and neither shows
345
+ # up in a session. Only ONE of them is a real gate finding: a custom autoMode block that dropped the
346
+ # built-ins by omitting "$defaults" — 66 soft blocks gone, silently. Whether Crewforth's own rules are present
347
+ # is reported but NOT treated as a failure: they were measured on 2026-08-24 not to enforce (see the skill).
348
+ if [ -x .claude/skills/automode-policy/scripts/check.sh ] || [ -f .claude/skills/automode-policy/scripts/check.sh ]; then
349
+ AMOUT="$(bash .claude/skills/automode-policy/scripts/check.sh 2>&1)"; AMRC=$?
350
+ case "$AMRC" in
351
+ 0) ok "auto-mode classifier config: built-ins intact, Crewforth rules present (config, not a gate)" ;;
352
+ 2) bad "auto-mode classifier BUILT-INS DROPPED — an autoMode array lacks \"\$defaults\"" \
353
+ "restore it in ~/.claude/settings.json; see .claude/skills/automode-policy/SKILL.md" ;;
354
+ 3) skip "auto-mode classifier config: Crewforth rules absent (measured not to enforce — see the skill)" ;;
355
+ # Same vocabulary as the other three branches on purpose. It used to read "auto-mode policy check
356
+ # skipped", and the suite's assertion — written on a machine that HAS the claude CLI — never saw this
357
+ # branch. CI has no CLI, so every run took it and the case failed on a wording difference, not a defect.
358
+ *) skip "auto-mode classifier config: not checked (no claude CLI, or auto mode unavailable here)" ;;
359
+ esac
360
+ [ "$AMRC" = 2 ] && printf '%s\n' "$AMOUT" | grep 'auto-mode config'
361
+ else
362
+ warn "auto-mode policy check skipped (install predates the automode-policy skill; run the updater)"
363
+ fi
364
+ # 10) Gate activity. The suite proves the gates CAN fire; this reports whether anything actually tripped them.
365
+ # Recording is on by default (rule names only, never the command), so "no log" here means no gate has
366
+ # fired yet — a measured zero, not a gap. Never a failure either way.
367
+ if [ -f .claude/eval/gate-report.sh ]; then
368
+ GOUT="$(bash .claude/eval/gate-report.sh 2>/dev/null)"; GRC=$?
369
+ case "$GRC" in
370
+ 0) GL="$(printf '%s' "$GOUT" | grep -E 'decision\(s\)|no gate has fired' | head -1 | sed 's/^ *//')"
371
+ [ -n "$GL" ] && ok "gate activity: $GL" || ok "gate activity recorded (see /crew-gates)" ;;
372
+ 3) skip "gate activity NOT MEASURED — nowhere to record (see /crew-gates)" ;;
373
+ *) skip "gate activity unreadable (see /crew-gates)" ;;
374
+ esac
375
+ fi
376
+ # --- Agentic readiness (ADVISORY) -------------------------------------------------------------------------
377
+ # Everything above answers "are Crewforth's gates live?". This answers a different question the gates cannot see:
378
+ # "is this PROJECT set up so an agent can actually work well in it?" A flawless install still starves its
379
+ # agents when the CLAUDE.md project section is left as the template, there is no sandbox to run in, and no
380
+ # project-specific skill carries the domain. These are project maturity, not install health, so they NEVER
381
+ # change the exit code — doctor's verdict stays a statement about the install.
382
+ echo
383
+ # The toolchain the install actually landed on. It runs here as well as in the installers because the machine
384
+ # changes after install day — a wiped PATH, a new laptop, a corporate image that removed jq — and Crewforth's
385
+ # fallbacks mean none of that announces itself. Advisory: it never changes the verdict above.
386
+ # doctor has already cd'd into the project, so the installed copy is the one to run; fall back to the copy
387
+ # sitting beside this script for the case where doctor is run straight out of the Crewforth source.
388
+ [ -f "$PREFLIGHT" ] && bash "$PREFLIGHT"
389
+
390
+ echo "Readiness (advisory — does not affect the verdict above):"
391
+ RDY=0; RTOT=0
392
+ rdy(){ RTOT=$((RTOT+1)); RDY=$((RDY+1)); echo " ✅ $1"; }
393
+ gap(){ RTOT=$((RTOT+1)); echo " ➖ $1"; echo " ↳ $2"; }
394
+
395
+ # R1) Is the CLAUDE.md project section filled in, or still the shipped template? An unfilled section means every
396
+ # agent works stack-blind — it is the single most common way a correct install still underperforms.
397
+ if [ -f CLAUDE.md ]; then
398
+ if grep -qE '<PROJECT NAME>|<One sentence:|<Fill in per the project' CLAUDE.md; then
399
+ gap "CLAUDE.md project section is still the template (placeholders left in)" \
400
+ "fill in Project / Stack / Project skills — agents read the stack from there"
401
+ else rdy "CLAUDE.md project section is filled in"; fi
402
+ fi
403
+
404
+ # R2) A project-specific skill — Crewforth ships the generic 'how's; the domain ones (payment-contract,
405
+ # notification-rules, a backend-pattern skill) are the project's to add. Needs the install manifest to tell
406
+ # kit-shipped from project-owned; without it (pre-1.8 install) the signal is unknowable, so it is skipped
407
+ # rather than guessed — a wrong "you have no project skills" is worse than no line at all.
408
+ MAN=.claude/kit-manifest.txt
409
+ if [ -f "$MAN" ]; then
410
+ OWN=0
411
+ # The CR strip is the whole reason this reads through `tr` rather than grepping the file directly, and it is
412
+ # not defensive: MEASURED on a CRLF manifest with one project skill installed, this counted TWO. `grep -qxF`
413
+ # wants a whole-line match, `skills/handoff\r` is not `skills/handoff`, so every KIT skill read as
414
+ # project-owned. A Windows checkout with core.autocrlf produces exactly that manifest. `skill-trust.sh`
415
+ # already strips it for the same reason and the same file — this was the copy that did not, which is why the
416
+ # two answers disagreed on the same install.
417
+ MANTXT="$(tr -d '\r' < "$MAN")"
418
+ # A whole-line match by shell pattern, the way skill-trust.sh reads the same file: a `basename` and a `grep`
419
+ # per skill was 82 spawns for the shipped set — the bulk of doctor's cost on Git Bash.
420
+ NL='
421
+ '
422
+ for d in .claude/skills/*/; do
423
+ [ -d "$d" ] || continue
424
+ s="${d%/}"; s="${s##*/}"
425
+ case "$NL$MANTXT$NL" in *"${NL}skills/$s$NL"*) ;; *) OWN=$((OWN+1)) ;; esac
426
+ done
427
+ [ "$OWN" -gt 0 ] && rdy "$OWN project-specific skill(s) alongside Crewforth's" \
428
+ || gap "no project-specific skill — only Crewforth's generic ones are installed" \
429
+ "put the domain 'how's in .claude/skills/ (format: .claude/AGENT_TEMPLATE.md)"
430
+ else
431
+ skip "project-skill signal skipped (no .claude/kit-manifest.txt — install predates it; run the updater)"
432
+ fi
433
+
434
+ # R3) A sandbox to run in. Agentic work executes commands; a devcontainer is what makes that bounded rather
435
+ # than trusting every command against the host.
436
+ if [ -f .devcontainer/devcontainer.json ]; then rdy "devcontainer present (agentic execution is sandboxed)"
437
+ else gap "no .devcontainer/devcontainer.json — agent commands run directly against your machine" \
438
+ "add a devcontainer, or keep approval-mode gates on for anything destructive (§4.5)"; fi
439
+
440
+ # R4) MCP servers — the project's own tools/data reaching the model. Either the project-level .mcp.json or an
441
+ # mcpServers block in Crewforth's settings counts.
442
+ if [ -f .mcp.json ] || grep -q '"mcpServers"' .claude/settings.json 2>/dev/null; then
443
+ rdy "MCP servers configured (project tools/data reach the model)"
444
+ else gap "no MCP server configured — the model has no project-specific tool access" \
445
+ "add .mcp.json when a tool/data source would help (the mcp-builder skill covers writing one)"; fi
446
+
447
+ # R5) Context freshness. CLAUDE.md is read once per session and is the only project-wide instruction the model
448
+ # gets; if the code moved a long way since it was last touched, it is describing a project that no longer
449
+ # exists. Counted as commits (not days) that changed something OUTSIDE .claude since CLAUDE.md's mtime —
450
+ # mtime, not git history, because a default install gitignores CLAUDE.md and it has no history to read.
451
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -f CLAUDE.md ]; then
452
+ MT="$(stat -c %Y CLAUDE.md 2>/dev/null || stat -f %m CLAUDE.md 2>/dev/null)"
453
+ MT="$(printf '%s' "${MT:-}" | tr -cd '0-9')"
454
+ MAXC="${CREW_FRESHNESS_MAX:-40}"
455
+ if [ -n "$MT" ]; then
456
+ CH="$(git rev-list --count HEAD --since="@$MT" -- . ':(exclude).claude' 2>/dev/null | tr -cd '0-9')"
457
+ CH="${CH:-0}"
458
+ [ "$CH" -le "$MAXC" ] && rdy "CLAUDE.md is current ($CH commit(s) of drift since it was last touched)" \
459
+ || gap "CLAUDE.md is stale — $CH commits changed the project since it was last touched (limit $MAXC)" \
460
+ "re-read it against the code and update Stack / Project skills (the claude-md-improver flow)"
461
+ else skip "freshness signal skipped (cannot read CLAUDE.md mtime on this platform)"; fi
462
+ fi
463
+
464
+ [ "$RTOT" -gt 0 ] && echo " → readiness $RDY/$RTOT"
465
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1