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.
- package/CHANGELOG.md +3784 -0
- package/LICENSE +21 -0
- package/README.md +37 -7
- package/VERSION +1 -0
- package/adopt.sh +1670 -0
- package/bin/cli.js +497 -0
- package/kit/AGENT_TEMPLATE.md +92 -0
- package/kit/CLAUDE.md +228 -0
- package/kit/README.md +107 -0
- package/kit/agents/crew-backend-expert.md +95 -0
- package/kit/agents/crew-commit-agent.md +96 -0
- package/kit/agents/crew-database-expert.md +84 -0
- package/kit/agents/crew-devops-expert.md +82 -0
- package/kit/agents/crew-frontend-expert.md +85 -0
- package/kit/agents/crew-performance-expert.md +78 -0
- package/kit/agents/crew-planner.md +60 -0
- package/kit/agents/crew-privacy-agent.md +71 -0
- package/kit/agents/crew-review-agent.md +95 -0
- package/kit/agents/crew-security-expert.md +73 -0
- package/kit/agents/crew-session-manager.md +77 -0
- package/kit/agents/crew-test-expert.md +64 -0
- package/kit/eval/doctor.sh +465 -0
- package/kit/eval/gate-report.sh +137 -0
- package/kit/eval/golden-holdout.txt +53 -0
- package/kit/eval/golden-routing.txt +199 -0
- package/kit/eval/lib/crew-env.sh +14 -0
- package/kit/eval/lib/settings-json.awk +154 -0
- package/kit/eval/lib/star.sh +70 -0
- package/kit/eval/parser-conformance.sh +612 -0
- package/kit/eval/preflight.sh +186 -0
- package/kit/eval/routing-eval.sh +209 -0
- package/kit/eval/scan-skill.sh +162 -0
- package/kit/eval/smoke-test.sh +6693 -0
- package/kit/eval/update-guard.sh +81 -0
- package/kit/eval/utilization.sh +130 -0
- package/kit/hooks/board-sync.sh +106 -0
- package/kit/hooks/board.sh +962 -0
- package/kit/hooks/commit-msg +49 -0
- package/kit/hooks/context-usage.sh +353 -0
- package/kit/hooks/floor-blocklist.txt +124 -0
- package/kit/hooks/guard-bash.sh +1431 -0
- package/kit/hooks/guard-commit-scan.sh +511 -0
- package/kit/hooks/guard-write.sh +513 -0
- package/kit/hooks/pre-commit +577 -0
- package/kit/hooks/route-hint.sh +185 -0
- package/kit/hooks/secret-blocklist.txt +82 -0
- package/kit/hooks/session-guard.sh +149 -0
- package/kit/hooks/session-rehydrate.sh +41 -0
- package/kit/hooks/session-stats.sh +252 -0
- package/kit/hooks/session-update-check.sh +236 -0
- package/kit/hooks/skill-trust.sh +150 -0
- package/kit/hooks/trace-blocklist.txt +53 -0
- package/kit/settings.json +132 -0
- package/kit/skills/a11y/SKILL.md +44 -0
- package/kit/skills/adr/SKILL.md +74 -0
- package/kit/skills/api-design/SKILL.md +50 -0
- package/kit/skills/automode-policy/SKILL.md +91 -0
- package/kit/skills/automode-policy/references/policy.json +13 -0
- package/kit/skills/automode-policy/scripts/apply.sh +84 -0
- package/kit/skills/automode-policy/scripts/check.sh +102 -0
- package/kit/skills/backend-architecture/SKILL.md +103 -0
- package/kit/skills/brainstorm/SKILL.md +47 -0
- package/kit/skills/ci-pipeline/SKILL.md +38 -0
- package/kit/skills/commit-message/SKILL.md +82 -0
- package/kit/skills/commit-message/references/examples.md +8 -0
- package/kit/skills/confidence-check/SKILL.md +86 -0
- package/kit/skills/crew-board/SKILL.md +55 -0
- package/kit/skills/crew-brainstorm/SKILL.md +19 -0
- package/kit/skills/crew-code-review/SKILL.md +151 -0
- package/kit/skills/crew-code-review/references/history-search.md +19 -0
- package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
- package/kit/skills/crew-code-review/references/receiving.md +23 -0
- package/kit/skills/crew-doctor/SKILL.md +36 -0
- package/kit/skills/crew-gates/SKILL.md +25 -0
- package/kit/skills/crew-handoff/SKILL.md +14 -0
- package/kit/skills/crew-plan/SKILL.md +18 -0
- package/kit/skills/crew-review/SKILL.md +23 -0
- package/kit/skills/crew-ship/SKILL.md +19 -0
- package/kit/skills/crew-skill/SKILL.md +61 -0
- package/kit/skills/crew-studio/SKILL.md +129 -0
- package/kit/skills/crew-update/SKILL.md +43 -0
- package/kit/skills/db-migration/SKILL.md +120 -0
- package/kit/skills/db-migration/references/backup-restore.md +33 -0
- package/kit/skills/db-migration/references/raw-sql.md +30 -0
- package/kit/skills/db-migration/references/tool-matrix.md +35 -0
- package/kit/skills/dependency-audit/SKILL.md +61 -0
- package/kit/skills/dependency-upgrade/SKILL.md +82 -0
- package/kit/skills/deploy/SKILL.md +100 -0
- package/kit/skills/deploy/references/proxy-ssl.md +56 -0
- package/kit/skills/deploy/references/rollback.md +18 -0
- package/kit/skills/deploy/references/scripts.md +9 -0
- package/kit/skills/deploy/references/topology-a.md +128 -0
- package/kit/skills/deploy/references/topology-b.md +44 -0
- package/kit/skills/docs-writer/SKILL.md +53 -0
- package/kit/skills/docs-writer/references/coauthoring.md +34 -0
- package/kit/skills/eval-grader/SKILL.md +75 -0
- package/kit/skills/eval-grader/references/method.md +51 -0
- package/kit/skills/frontend/SKILL.md +56 -0
- package/kit/skills/frontend/references/verify-contract.md +41 -0
- package/kit/skills/frontend-design/SKILL.md +71 -0
- package/kit/skills/frontend-design/references/design-review.md +34 -0
- package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
- package/kit/skills/handoff/SKILL.md +71 -0
- package/kit/skills/handoff/references/cold-reader.md +51 -0
- package/kit/skills/i18n-integrity/SKILL.md +31 -0
- package/kit/skills/incident-runbook/SKILL.md +40 -0
- package/kit/skills/incident-runbook/references/postmortem.md +16 -0
- package/kit/skills/iterate/SKILL.md +40 -0
- package/kit/skills/mcp-builder/SKILL.md +54 -0
- package/kit/skills/mcp-builder/references/building.md +71 -0
- package/kit/skills/observability/SKILL.md +50 -0
- package/kit/skills/performance/SKILL.md +38 -0
- package/kit/skills/performance/references/bottlenecks.md +10 -0
- package/kit/skills/privacy-compliance/SKILL.md +75 -0
- package/kit/skills/red-team/SKILL.md +48 -0
- package/kit/skills/reflect/SKILL.md +57 -0
- package/kit/skills/release/SKILL.md +44 -0
- package/kit/skills/security-scan/SKILL.md +99 -0
- package/kit/skills/security-scan/references/ai-agents.md +107 -0
- package/kit/skills/security-scan/references/fronts.md +99 -0
- package/kit/skills/security-scan/references/prompting.md +23 -0
- package/kit/skills/security-scan/references/reporting.md +75 -0
- package/kit/skills/security-scan/references/verify.md +131 -0
- package/kit/skills/sonarqube-check/SKILL.md +108 -0
- package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
- package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
- package/kit/skills/spec-planning/SKILL.md +92 -0
- package/kit/skills/systematic-debugging/SKILL.md +57 -0
- package/kit/skills/systematic-debugging/references/techniques.md +44 -0
- package/kit/skills/teamboard/SKILL.md +88 -0
- package/kit/skills/teamboard/references/setup.md +37 -0
- package/kit/skills/testing/SKILL.md +49 -0
- package/kit/skills/testing/references/flaky-triage.md +51 -0
- package/kit/skills/threat-model/SKILL.md +57 -0
- package/kit/skills/threat-model/references/interview.md +51 -0
- package/kit/skills/threat-model/references/schema.md +42 -0
- package/kit/skills/token-budget/SKILL.md +51 -0
- package/kit/skills/trace-scan/SKILL.md +43 -0
- package/kit/skills/worktree/SKILL.md +40 -0
- package/kit/studio/README.md +266 -0
- package/kit/studio/ensure-node.sh +471 -0
- package/kit/studio/package.json +14 -0
- package/kit/studio/server/hooks/studio-gate.sh +169 -0
- package/kit/studio/server/index.js +902 -0
- package/kit/studio/server/lib/crew-env.js +11 -0
- package/kit/studio/server/lib/fleet.js +146 -0
- package/kit/studio/server/lib/graph.js +533 -0
- package/kit/studio/server/lib/instance.js +135 -0
- package/kit/studio/server/lib/kit-telemetry.js +182 -0
- package/kit/studio/server/lib/kit.js +158 -0
- package/kit/studio/server/lib/palette.js +118 -0
- package/kit/studio/server/lib/peers.js +72 -0
- package/kit/studio/server/lib/permissions.js +150 -0
- package/kit/studio/server/lib/projects.js +294 -0
- package/kit/studio/server/lib/roster.js +191 -0
- package/kit/studio/server/lib/session.js +309 -0
- package/kit/studio/server/lib/terminal.js +91 -0
- package/kit/studio/server/lib/transcript.js +100 -0
- package/kit/studio/web/app.js +1098 -0
- package/kit/studio/web/canvas.js +1179 -0
- package/kit/studio/web/chat.js +543 -0
- package/kit/studio/web/index.html +86 -0
- package/kit/studio/web/md.js +110 -0
- package/kit/studio/web/storage-migrate.js +23 -0
- package/kit/studio/web/style.css +1453 -0
- package/package.json +46 -8
- package/start.sh +748 -0
- 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
|