crewforth 0.0.1 → 3.0.0-rc.2

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 (169) hide show
  1. package/CHANGELOG.md +3805 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -7
  4. package/VERSION +1 -0
  5. package/adopt.sh +1739 -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 +639 -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/skill-listing.awk +43 -0
  29. package/kit/eval/lib/star.sh +70 -0
  30. package/kit/eval/parser-conformance.sh +612 -0
  31. package/kit/eval/preflight.sh +186 -0
  32. package/kit/eval/routing-eval.sh +209 -0
  33. package/kit/eval/scan-skill.sh +162 -0
  34. package/kit/eval/smoke-test.sh +6736 -0
  35. package/kit/eval/update-guard.sh +81 -0
  36. package/kit/eval/utilization.sh +130 -0
  37. package/kit/hooks/board-sync.sh +106 -0
  38. package/kit/hooks/board.sh +962 -0
  39. package/kit/hooks/commit-msg +49 -0
  40. package/kit/hooks/context-usage.sh +353 -0
  41. package/kit/hooks/floor-blocklist.txt +124 -0
  42. package/kit/hooks/guard-bash.sh +1433 -0
  43. package/kit/hooks/guard-commit-scan.sh +511 -0
  44. package/kit/hooks/guard-write.sh +513 -0
  45. package/kit/hooks/pre-commit +577 -0
  46. package/kit/hooks/route-hint.sh +185 -0
  47. package/kit/hooks/secret-blocklist.txt +82 -0
  48. package/kit/hooks/session-guard.sh +149 -0
  49. package/kit/hooks/session-rehydrate.sh +41 -0
  50. package/kit/hooks/session-stats.sh +252 -0
  51. package/kit/hooks/session-update-check.sh +236 -0
  52. package/kit/hooks/skill-trust.sh +163 -0
  53. package/kit/hooks/trace-blocklist.txt +53 -0
  54. package/kit/settings.json +132 -0
  55. package/kit/skills/a11y/SKILL.md +44 -0
  56. package/kit/skills/adr/SKILL.md +74 -0
  57. package/kit/skills/api-design/SKILL.md +50 -0
  58. package/kit/skills/automode-policy/SKILL.md +91 -0
  59. package/kit/skills/automode-policy/references/policy.json +13 -0
  60. package/kit/skills/automode-policy/scripts/apply.sh +84 -0
  61. package/kit/skills/automode-policy/scripts/check.sh +102 -0
  62. package/kit/skills/backend-architecture/SKILL.md +103 -0
  63. package/kit/skills/brainstorm/SKILL.md +47 -0
  64. package/kit/skills/ci-pipeline/SKILL.md +38 -0
  65. package/kit/skills/commit-message/SKILL.md +82 -0
  66. package/kit/skills/commit-message/references/examples.md +8 -0
  67. package/kit/skills/confidence-check/SKILL.md +86 -0
  68. package/kit/skills/crew-board/SKILL.md +55 -0
  69. package/kit/skills/crew-brainstorm/SKILL.md +19 -0
  70. package/kit/skills/crew-code-review/SKILL.md +151 -0
  71. package/kit/skills/crew-code-review/references/history-search.md +19 -0
  72. package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
  73. package/kit/skills/crew-code-review/references/receiving.md +23 -0
  74. package/kit/skills/crew-doctor/SKILL.md +46 -0
  75. package/kit/skills/crew-gates/SKILL.md +25 -0
  76. package/kit/skills/crew-handoff/SKILL.md +14 -0
  77. package/kit/skills/crew-plan/SKILL.md +18 -0
  78. package/kit/skills/crew-review/SKILL.md +26 -0
  79. package/kit/skills/crew-ship/SKILL.md +19 -0
  80. package/kit/skills/crew-skill/SKILL.md +62 -0
  81. package/kit/skills/crew-studio/SKILL.md +129 -0
  82. package/kit/skills/crew-update/SKILL.md +43 -0
  83. package/kit/skills/db-migration/SKILL.md +120 -0
  84. package/kit/skills/db-migration/references/backup-restore.md +33 -0
  85. package/kit/skills/db-migration/references/raw-sql.md +30 -0
  86. package/kit/skills/db-migration/references/tool-matrix.md +35 -0
  87. package/kit/skills/dependency-audit/SKILL.md +61 -0
  88. package/kit/skills/dependency-upgrade/SKILL.md +82 -0
  89. package/kit/skills/deploy/SKILL.md +100 -0
  90. package/kit/skills/deploy/references/proxy-ssl.md +56 -0
  91. package/kit/skills/deploy/references/rollback.md +18 -0
  92. package/kit/skills/deploy/references/scripts.md +9 -0
  93. package/kit/skills/deploy/references/topology-a.md +128 -0
  94. package/kit/skills/deploy/references/topology-b.md +44 -0
  95. package/kit/skills/docs-writer/SKILL.md +53 -0
  96. package/kit/skills/docs-writer/references/coauthoring.md +34 -0
  97. package/kit/skills/eval-grader/SKILL.md +75 -0
  98. package/kit/skills/eval-grader/references/method.md +51 -0
  99. package/kit/skills/frontend/SKILL.md +56 -0
  100. package/kit/skills/frontend/references/verify-contract.md +41 -0
  101. package/kit/skills/frontend-design/SKILL.md +71 -0
  102. package/kit/skills/frontend-design/references/design-review.md +34 -0
  103. package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
  104. package/kit/skills/handoff/SKILL.md +71 -0
  105. package/kit/skills/handoff/references/cold-reader.md +51 -0
  106. package/kit/skills/i18n-integrity/SKILL.md +31 -0
  107. package/kit/skills/incident-runbook/SKILL.md +40 -0
  108. package/kit/skills/incident-runbook/references/postmortem.md +16 -0
  109. package/kit/skills/iterate/SKILL.md +40 -0
  110. package/kit/skills/mcp-builder/SKILL.md +54 -0
  111. package/kit/skills/mcp-builder/references/building.md +71 -0
  112. package/kit/skills/observability/SKILL.md +50 -0
  113. package/kit/skills/performance/SKILL.md +38 -0
  114. package/kit/skills/performance/references/bottlenecks.md +10 -0
  115. package/kit/skills/privacy-compliance/SKILL.md +75 -0
  116. package/kit/skills/red-team/SKILL.md +48 -0
  117. package/kit/skills/reflect/SKILL.md +57 -0
  118. package/kit/skills/release/SKILL.md +44 -0
  119. package/kit/skills/security-scan/SKILL.md +99 -0
  120. package/kit/skills/security-scan/references/ai-agents.md +107 -0
  121. package/kit/skills/security-scan/references/fronts.md +99 -0
  122. package/kit/skills/security-scan/references/prompting.md +23 -0
  123. package/kit/skills/security-scan/references/reporting.md +75 -0
  124. package/kit/skills/security-scan/references/verify.md +131 -0
  125. package/kit/skills/sonarqube-check/SKILL.md +108 -0
  126. package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
  127. package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
  128. package/kit/skills/spec-planning/SKILL.md +92 -0
  129. package/kit/skills/systematic-debugging/SKILL.md +57 -0
  130. package/kit/skills/systematic-debugging/references/techniques.md +44 -0
  131. package/kit/skills/teamboard/SKILL.md +88 -0
  132. package/kit/skills/teamboard/references/setup.md +37 -0
  133. package/kit/skills/testing/SKILL.md +49 -0
  134. package/kit/skills/testing/references/flaky-triage.md +51 -0
  135. package/kit/skills/threat-model/SKILL.md +57 -0
  136. package/kit/skills/threat-model/references/interview.md +51 -0
  137. package/kit/skills/threat-model/references/schema.md +42 -0
  138. package/kit/skills/token-budget/SKILL.md +51 -0
  139. package/kit/skills/trace-scan/SKILL.md +43 -0
  140. package/kit/skills/worktree/SKILL.md +40 -0
  141. package/kit/studio/README.md +266 -0
  142. package/kit/studio/ensure-node.sh +471 -0
  143. package/kit/studio/package.json +14 -0
  144. package/kit/studio/server/hooks/studio-gate.sh +169 -0
  145. package/kit/studio/server/index.js +902 -0
  146. package/kit/studio/server/lib/crew-env.js +11 -0
  147. package/kit/studio/server/lib/fleet.js +146 -0
  148. package/kit/studio/server/lib/graph.js +533 -0
  149. package/kit/studio/server/lib/instance.js +135 -0
  150. package/kit/studio/server/lib/kit-telemetry.js +182 -0
  151. package/kit/studio/server/lib/kit.js +158 -0
  152. package/kit/studio/server/lib/palette.js +118 -0
  153. package/kit/studio/server/lib/peers.js +72 -0
  154. package/kit/studio/server/lib/permissions.js +150 -0
  155. package/kit/studio/server/lib/projects.js +294 -0
  156. package/kit/studio/server/lib/roster.js +191 -0
  157. package/kit/studio/server/lib/session.js +309 -0
  158. package/kit/studio/server/lib/terminal.js +91 -0
  159. package/kit/studio/server/lib/transcript.js +100 -0
  160. package/kit/studio/web/app.js +1098 -0
  161. package/kit/studio/web/canvas.js +1179 -0
  162. package/kit/studio/web/chat.js +543 -0
  163. package/kit/studio/web/index.html +86 -0
  164. package/kit/studio/web/md.js +110 -0
  165. package/kit/studio/web/storage-migrate.js +23 -0
  166. package/kit/studio/web/style.css +1453 -0
  167. package/package.json +46 -8
  168. package/start.sh +764 -0
  169. package/bin/crewforth.js +0 -10
@@ -0,0 +1,639 @@
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
+ # Every reporter takes a FORMAT and its arguments, and the format is the key of the message table below: a value
22
+ # (a path, a count, a setting's name) goes in as an argument, so the sentence around it can be translated whole.
23
+ ok(){ _mt "$@"; echo " ✅ $_M"; }
24
+ # bad MESSAGE FIX [arguments for MESSAGE]. The fix is a key of its own and takes no arguments; a fix that ends in
25
+ # something untranslatable (a command, a list of names) puts it in BAD_TAIL, which is printed after it once.
26
+ bad(){ local _m="$1" _x="$2"; shift 2; _mt "$_m" "$@"; echo " ❌ $_M"; _mt "fix: "; local _f="$_M"; _mt "$_x"
27
+ echo " ↳ $_f$_M${BAD_TAIL:-}"; BAD_TAIL=""; FAIL=$((FAIL+1)); }
28
+ warn(){ _mt "$@"; echo " ⚠️ $_M"; }
29
+ # `skip` lives up here with the other reporters, not down in the readiness block where it used to be
30
+ # defined: the health checks call it too, and a helper defined after its first caller is a silent
31
+ # no-op — the line never prints and the run shows `skip: command not found` on stderr, where nobody
32
+ # looks. Found by RUNNING doctor against a fixture install; grepping for the call sites said wired.
33
+ skip(){ _mt "$@"; echo " · $_M"; }
34
+
35
+ # ---- CREW-I18N ------------------------------------------------------------------------------------------
36
+ # The doctor speaks the language the project was installed in: CREW_LANG when it is set, otherwise the `lang=` that
37
+ # start.sh / adopt.sh recorded in .claude/kit.conf, otherwise the locale. Same contract as the installers: the
38
+ # English string is the key, a missing translation prints English, and names are never translated — commands,
39
+ # paths, file names, settings keys and hook events are identifiers, only the prose around them is.
40
+ case "${CREW_LANG:-}" in tr|en) ;; *)
41
+ CREW_LANG=""
42
+ if [ -f .claude/kit.conf ]; then
43
+ while IFS= read -r _l || [ -n "$_l" ]; do case "$_l" in lang=*) CREW_LANG="${_l#lang=}"; CREW_LANG="${CREW_LANG%$'\r'}" ;; esac; done < .claude/kit.conf
44
+ fi
45
+ case "$CREW_LANG" in tr|en) ;; *)
46
+ _loc="${LC_ALL:-}"; [ -n "$_loc" ] || _loc="${LC_MESSAGES:-}"; [ -n "$_loc" ] || _loc="${LANG:-}"
47
+ case "$_loc" in tr*|TR*) CREW_LANG=tr ;; *) CREW_LANG=en ;; esac ;;
48
+ esac ;;
49
+ esac
50
+ export CREW_LANG # preflight.sh, which the doctor runs below, reads it and speaks the same language
51
+ _mt() {
52
+ # An empty key must still ASSIGN: bash 3.2's `printf -v _M ""` leaves _M holding the previous translation.
53
+ [ -n "${1:-}" ] || { _M=""; return 0; }
54
+ local s="$1"; shift
55
+ if [ "$CREW_LANG" = tr ]; then
56
+ case "$s" in
57
+ '== Crewforth — install doctor ==') s='== Crewforth — kurulum denetimi ==' ;;
58
+ 'fix: ') s='çözüm: ' ;;
59
+ "no .claude/ in '%s' — is Crewforth installed here?") s="'%s' içinde .claude/ yok — Crewforth burada kurulu mu?" ;;
60
+ 'VERSION present (%s)') s='VERSION var (%s)' ;;
61
+ 'VERSION missing') s='VERSION yok' ;;
62
+ 'reinstall or update Crewforth (npx crewforth update)') s="Crewforth'u yeniden kurun ya da güncelleyin (npx crewforth update)" ;;
63
+ 'Crewforth v%s installed, v%s published — update with /crew-update') s='Crewforth v%s kurulu, v%s yayında — /crew-update ile güncelleyin' ;;
64
+ 'required git hooks present (pre-commit, commit-msg)') s="gerekli git hook'ları yerinde (pre-commit, commit-msg)" ;;
65
+ 'MISSING git hook(s):%s — the commit trace/secret scan is absent') s="EKSİK git hook'u:%s — commit iz ve gizli bilgi taraması yok" ;;
66
+ 'reinstall or update Crewforth') s="Crewforth'u yeniden kurun ya da güncelleyin" ;;
67
+ 'all hooks are executable') s="tüm hook'lar çalıştırılabilir" ;;
68
+ 'not executable:%s') s='çalıştırılabilir değil:%s' ;;
69
+ 'guard-bash.sh did NOT block a force-push — the §4.5 gate is neutered/disarmed') s="guard-bash.sh force-push'u ENGELLEMEDİ — §4.5 kapısı etkisiz" ;;
70
+ 'restore guard-bash.sh from Crewforth (npx crewforth update)') s="guard-bash.sh'yi Crewforth'tan geri yükleyin (npx crewforth update)" ;;
71
+ 'guard-bash.sh blocks a force-push (gate live, not neutered)') s="guard-bash.sh force-push'u engelliyor (kapı canlı, etkisiz değil)" ;;
72
+ 'guard-bash.sh enforces the §4.6 review gate (gate live, not neutered)') s='guard-bash.sh §4.6 review kapısını uyguluyor (kapı canlı, etkisiz değil)' ;;
73
+ 'guard-bash.sh did NOT enforce §4.6 — a commit can land with no review of its diff') s="guard-bash.sh §4.6'yı UYGULAMADI — diff'i review edilmemiş bir commit geçebilir" ;;
74
+ 'restore guard-bash.sh from Crewforth (and check crew-review-agent still writes .claude/review-pass.json)') s="guard-bash.sh'yi Crewforth'tan geri yükleyin (ve crew-review-agent'ın .claude/review-pass.json'u hâlâ yazdığını kontrol edin)" ;;
75
+ 'core.hooksPath -> %s (commit-time gates active)') s='core.hooksPath -> %s (commit anındaki kapılar devrede)' ;;
76
+ 'core.hooksPath is unset — commit trace/secret/bloat gates are INACTIVE') s='core.hooksPath ayarlı değil — commit iz, gizli bilgi ve şişkinlik kapıları DEVRE DIŞI' ;;
77
+ "core.hooksPath -> %s (not Crewforth's hooks)") s="core.hooksPath -> %s (Crewforth'un hook'ları değil)" ;;
78
+ 'not a git repo — commit gates need: git init && git config core.hooksPath .claude/hooks') s='git deposu değil — commit kapıları için: git init && git config core.hooksPath .claude/hooks' ;;
79
+ "Crewforth's JSON reader is missing (%s) — settings.json cannot be checked") s="Crewforth'un JSON okuyucusu yok (%s) — settings.json denetlenemiyor" ;;
80
+ 'update Crewforth') s="Crewforth'u güncelleyin" ;;
81
+ 'settings.json is valid JSON') s='settings.json geçerli JSON' ;;
82
+ 'settings.json wires PreToolUse / UserPromptSubmit / Stop (non-empty)') s='settings.json PreToolUse / UserPromptSubmit / Stop olaylarını bağlıyor (boş değil)' ;;
83
+ "settings.json hook events empty or missing:%s — those gates won't fire") s="settings.json'da boş ya da eksik hook olayları:%s — bu kapılar çalışmaz" ;;
84
+ 'restore settings.json from Crewforth (npx crewforth update)') s="settings.json'u Crewforth'tan geri yükleyin (npx crewforth update)" ;;
85
+ 'SessionStart not wired — session rehydration after /compact or /clear is inactive (update Crewforth)') s="SessionStart bağlı değil — /compact ya da /clear sonrasında oturum toparlanmıyor (Crewforth'u güncelleyin)" ;;
86
+ 'SessionStart wired (session rehydration active)') s='SessionStart bağlı (oturum toparlama devrede)' ;;
87
+ 'settings.json is invalid JSON') s='settings.json geçersiz JSON' ;;
88
+ "fix the syntax by hand — restoring Crewforth's file would drop any hooks you added") s="sözdizimini elle düzeltin — Crewforth'un dosyasını geri yüklemek eklediğiniz hook'ları siler" ;;
89
+ 'settings.json wires hooks through the %s placeholder — on Windows its separators are stripped before bash runs, so NO hook launches and every gate is silently absent') s="settings.json hook'ları %s yer tutucusuyla bağlıyor — Windows'ta ayırıcılar bash çalışmadan silinir, bu yüzden HİÇBİR hook başlamaz ve bütün kapılar sessizce yok olur" ;;
90
+ 'update Crewforth (npx crewforth adopt) — hook commands become:') s="Crewforth'u güncelleyin (npx crewforth adopt) — hook komutları şu olur:" ;;
91
+ 'hook wiring carries no path placeholder (nothing for Windows to mangle)') s="hook bağlantısında yol yer tutucusu yok (Windows'un bozacağı bir şey yok)" ;;
92
+ 'settings.json missing — the tool-level gates (commit approval, guards, context) are INACTIVE') s='settings.json yok — araç düzeyindeki kapılar (commit onayı, korumalar, bağlam) DEVRE DIŞI' ;;
93
+ 'reinstall Crewforth') s="Crewforth'u yeniden kurun" ;;
94
+ "Claude Code's default") s="Claude Code'un varsayılanı" ;;
95
+ ' (estimated: only 1M and 200k windows were measured)') s=' (tahmini: yalnız 1M ve 200k pencereler ölçüldü)' ;;
96
+ 'skill listing: %s skill(s) use when_to_use or a folded description — a shape the count was not measured on') s='skill listesi: %s skill when_to_use ya da katlanmış açıklama kullanıyor — sayımın ölçülmediği bir biçim' ;;
97
+ 'skill listing %s chars for %s skills fits the %s-char budget (fraction %s from %s, %s-token window%s)') s="skill listesi %s karakter, %s skill; %s karakterlik bütçeye sığıyor (kesir %s, kaynak %s; %s token'lık pencere%s)" ;;
98
+ 'skill listing %s chars for %s skills EXCEEDS the %s-char budget (fraction %s from %s, %s-token window%s)') s="skill listesi %s karakter, %s skill; %s karakterlik bütçeyi AŞIYOR (kesir %s, kaynak %s; %s token'lık pencere%s)" ;;
99
+ ' — Claude Code drops the descriptions of the least-used skills, and those stop matching requests.') s=" — Claude Code en az kullanılan skill'lerin açıklamalarını düşürür; o skill'ler isteklerle eşleşmez olur." ;;
100
+ ' Fixes: raise %s in settings, or set rarely-used skills to %s in %s.') s=" Çözüm: ayarlarda %s değerini yükseltin ya da nadir kullanılan skill'leri %s olarak %s içine yazın." ;;
101
+ ' Which skills? bash .claude/eval/utilization.sh — it reports the ones nothing in this project reached.') s=" Hangileri? bash .claude/eval/utilization.sh — bu projede hiçbir şeyin ulaşmadığı skill'leri listeler." ;;
102
+ "on a 200,000-token model the whole listing would be ~%s chars (Crewforth %s + your personal skills %s + Claude Code's own ~%s, measured) against %s at fraction %s — the least-used skills there lose their descriptions.") s="200 000 token'lık bir modelde listenin tamamı ~%s karakter olur (Crewforth %s + kişisel skill'leriniz %s + Claude Code'un kendi ~%s, ölçüldü); bütçe ise %s karakter (kesir %s). Orada en az kullanılan skill'ler açıklamalarını kaybeder." ;;
103
+ ' If you use such a model, one line in %s/settings.json fixes it: %s') s=' Böyle bir model kullanıyorsanız %s/settings.json dosyasına tek satır yeter: %s' ;;
104
+ ' What it costs: the listing is sent every turn. At 0.04 it stays whole — ~%s chars, about %s tokens,') s=" Bedeli: liste her turda gönderilir. 0.04'te bütün kalır — ~%s karakter, yaklaşık %s token," ;;
105
+ ' %s%% of a 200k window — instead of at most %s chars. (4 characters per token is how Claude Code sizes') s=' 200k pencerede %%%s yer tutar — en fazla %s karakter yerine. (Claude Code bu bütçeyi 200k pencerede token başına' ;;
106
+ ' this budget on a 200k window: 0.01 of 200,000 tokens is 8,000 characters.)') s=" 4 karakter sayarak hesaplar: 200 000 token'ın 0.01'i 8 000 karakterdir.)" ;;
107
+ "delegation check could not read:%s — not valid JSON (or Crewforth's reader is missing).") s="delegasyon denetimi okuyamadı:%s — geçerli JSON değil (ya da Crewforth'un okuyucusu yok)." ;;
108
+ ' open it and check that Agent/Task is not under permissions.deny. If it is, no subagent can ever run.') s=" dosyayı açıp Agent/Task'ın permissions.deny altında olmadığını kontrol edin. Oradaysa hiçbir alt ajan çalışamaz." ;;
109
+ 'the Agent tool is DENIED in:%s — no subagent can ever run, so every agent on disk is dead weight') s='Agent aracı şurada REDDEDİLMİŞ:%s — hiçbir alt ajan çalışamaz, diskteki her ajan ölü yük' ;;
110
+ 'remove the Agent/Task entry from permissions.deny, or accept that this project runs main-thread-only') s='Agent/Task girdisini permissions.deny içinden çıkarın ya da bu projenin yalnız ana oturumda çalışacağını kabul edin' ;;
111
+ 'delegation is enabled (the Agent tool is not denied)') s='delegasyon açık (Agent aracı reddedilmemiş)' ;;
112
+ 'CLAUDE.md (or a doc it references) names auto-delegated agent(s) that no installed agent matches — delegation to them silently fails') s='CLAUDE.md (ya da başvurduğu bir belge) kurulu hiçbir ajanla eşleşmeyen, otomatik devredilen ajan(lar) anıyor — onlara devretmek sessizce başarısız olur' ;;
113
+ 'rename each bare reference to its `crew-` id:') s='her çıplak anmayı `crew-` kimliğiyle değiştirin:' ;;
114
+ '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:%s') s='CLAUDE.md (ya da başvurduğu bir belge) yalnız açıkça çağrılan ajan(lar)ı eski çıplak kimliğiyle anıyor — açıkça çağrıldıkları için devretme yine çalışır; tutarlılık için yeniden adlandırın:%s' ;;
115
+ 'agent references resolve to installed agents (CLAUDE.md + referenced docs)') s='ajan anmaları kurulu ajanlara çözülüyor (CLAUDE.md ve başvurduğu belgeler)' ;;
116
+ 'CLAUDE.md missing — .claude/DISCIPLINE.md is never loaded (routing / DoD / session rules absent)') s='CLAUDE.md yok — .claude/DISCIPLINE.md hiç yüklenmiyor (yönlendirme / DoD / oturum kuralları yok)' ;;
117
+ 'create CLAUDE.md with this as its own line: @.claude/DISCIPLINE.md') s="CLAUDE.md'yi oluşturun ve şunu ayrı bir satır olarak yazın: @.claude/DISCIPLINE.md" ;;
118
+ 'CLAUDE.md imports .claude/DISCIPLINE.md (the discipline reaches the model)') s="CLAUDE.md .claude/DISCIPLINE.md'yi içe aktarıyor (disiplin modele ulaşıyor)" ;;
119
+ "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") s="CLAUDE.md disiplini SATIR İÇİNDE taşıyor (1.1 öncesi düzen) — yükleniyor ama Crewforth güncellemeleri ona ulaşmıyor; '@.claude/DISCIPLINE.md' içe aktarma satırına geçin" ;;
120
+ 'CLAUDE.md does not import .claude/DISCIPLINE.md — the discipline is on disk but never loaded') s="CLAUDE.md .claude/DISCIPLINE.md'yi içe aktarmıyor — disiplin diskte duruyor ama hiç yüklenmiyor" ;;
121
+ 'add this as its own line at the top of CLAUDE.md: @.claude/DISCIPLINE.md') s="CLAUDE.md'nin en üstüne şunu ayrı bir satır olarak ekleyin: @.claude/DISCIPLINE.md" ;;
122
+ ' · panel needs Node 18+ — .claude/studio/ensure-node.sh --plan fetches one') s=' · panel Node 18+ ister — .claude/studio/ensure-node.sh --plan bir tane indirir' ;;
123
+ '%s is set — its 3.0 name is CREW_%s (the old name works until 4.0)') s="%s ayarlı — 3.0'daki adı CREW_%s (eski ad 4.0'a kadar çalışır)" ;;
124
+ '%s is set but no longer read — set CREW_%s instead') s='%s ayarlı ama artık okunmuyor — yerine CREW_%s ayarlayın' ;;
125
+ 'DOCTOR: healthy ✅%s') s='DOCTOR: sağlıklı ✅%s' ;;
126
+ 'DOCTOR: %s issue(s) ❌ — apply the fixes above%s') s='DOCTOR: %s sorun ❌ — yukarıdaki çözümleri uygulayın%s' ;;
127
+ 'shell gates watch both Bash and PowerShell') s="kabuk kapıları hem Bash'i hem PowerShell'i izliyor" ;;
128
+ 'shell gates watch only Bash — PowerShell commands bypass every §4.5 rule') s="kabuk kapıları yalnız Bash'i izliyor — PowerShell komutları her §4.5 kuralını atlıyor" ;;
129
+ 'update Crewforth (npx crewforth update), or set the PreToolUse matcher to') s="Crewforth'u güncelleyin (npx crewforth update) ya da PreToolUse matcher'ını şuna ayarlayın:" ;;
130
+ 'auto-mode classifier config: built-ins intact, Crewforth rules present (config, not a gate)') s='auto-mode sınıflandırıcı ayarı: yerleşik kurallar sağlam, Crewforth kuralları var (ayar, kapı değil)' ;;
131
+ 'auto-mode classifier BUILT-INS DROPPED — an autoMode array lacks %s') s='auto-mode sınıflandırıcının YERLEŞİK KURALLARI DÜŞMÜŞ — bir autoMode dizisinde %s yok' ;;
132
+ 'restore it in ~/.claude/settings.json; see .claude/skills/automode-policy/SKILL.md') s='~/.claude/settings.json içinde geri koyun; bkz. .claude/skills/automode-policy/SKILL.md' ;;
133
+ 'auto-mode classifier config: Crewforth rules absent (measured not to enforce — see the skill)') s="auto-mode sınıflandırıcı ayarı: Crewforth kuralları yok (uygulanmadıkları ölçüldü — skill'e bakın)" ;;
134
+ 'auto-mode classifier config: not checked (no claude CLI, or auto mode unavailable here)') s='auto-mode sınıflandırıcı ayarı: denetlenmedi (claude CLI yok ya da burada auto mode kullanılamıyor)' ;;
135
+ 'auto-mode policy check skipped (install predates the automode-policy skill; run the updater)') s="auto-mode politika denetimi atlandı (kurulum automode-policy skill'inden eski; güncelleyiciyi çalıştırın)" ;;
136
+ 'gate activity: no gate has fired in this project yet — %s rules wired, recording on') s='kapı etkinliği: bu projede henüz hiçbir kapı tetiklenmedi — %s kural bağlı, kayıt açık' ;;
137
+ 'gate activity: %s decision(s) recorded (see /crew-gates)') s='kapı etkinliği: %s karar kaydedildi (bkz. /crew-gates)' ;;
138
+ 'gate activity recorded (see /crew-gates)') s='kapı etkinliği kaydediliyor (bkz. /crew-gates)' ;;
139
+ 'gate activity NOT MEASURED — nowhere to record (see /crew-gates)') s='kapı etkinliği ÖLÇÜLMEDİ — kaydedilecek yer yok (bkz. /crew-gates)' ;;
140
+ 'gate activity unreadable (see /crew-gates)') s='kapı etkinliği okunamadı (bkz. /crew-gates)' ;;
141
+ 'Readiness (advisory — does not affect the verdict above):') s='Hazırlık (tavsiye niteliğinde — yukarıdaki kararı etkilemez):' ;;
142
+ 'CLAUDE.md project section is still the template (placeholders left in)') s='CLAUDE.md proje bölümü hâlâ şablon (yer tutucular duruyor)' ;;
143
+ 'fill in Project / Stack / Project skills — agents read the stack from there') s='Project / Stack / Project skills bölümlerini doldurun — ajanlar yığını oradan okur' ;;
144
+ 'CLAUDE.md project section is filled in') s='CLAUDE.md proje bölümü doldurulmuş' ;;
145
+ "%s project-specific skill(s) alongside Crewforth's") s="Crewforth'unkilerin yanında %s projeye özel skill" ;;
146
+ "no project-specific skill — only Crewforth's generic ones are installed") s="projeye özel skill yok — yalnız Crewforth'un genel skill'leri kurulu" ;;
147
+ "put the domain 'how's in .claude/skills/ (format: .claude/AGENT_TEMPLATE.md)") s="alanınıza özgü 'nasıl'ları .claude/skills/ altına koyun (biçim: .claude/AGENT_TEMPLATE.md)" ;;
148
+ 'project-skill signal skipped (no .claude/kit-manifest.txt — install predates it; run the updater)') s="proje skill'i sinyali atlandı (.claude/kit-manifest.txt yok — kurulum ondan eski; güncelleyiciyi çalıştırın)" ;;
149
+ 'devcontainer present (agentic execution is sandboxed)') s='devcontainer var (ajan komutları yalıtılmış ortamda çalışır)' ;;
150
+ 'no .devcontainer/devcontainer.json — agent commands run directly against your machine') s='.devcontainer/devcontainer.json yok — ajan komutları doğrudan makinenizde çalışır' ;;
151
+ 'add a devcontainer, or keep approval-mode gates on for anything destructive (§4.5)') s='bir devcontainer ekleyin ya da yıkıcı her şey için onay kapılarını açık tutun (§4.5)' ;;
152
+ 'MCP servers configured (project tools/data reach the model)') s='MCP sunucuları ayarlı (projenin araçları ve verisi modele ulaşır)' ;;
153
+ 'no MCP server configured — the model has no project-specific tool access') s='MCP sunucusu ayarlı değil — modelin projeye özel araç erişimi yok' ;;
154
+ 'add .mcp.json when a tool/data source would help (the mcp-builder skill covers writing one)') s="bir araç ya da veri kaynağı işe yarayacaksa .mcp.json ekleyin (nasıl yazılacağını mcp-builder skill'i anlatır)" ;;
155
+ 'CLAUDE.md is current (%s commit(s) of drift since it was last touched)') s="CLAUDE.md güncel (son dokunulduğundan beri %s commit'lik kayma)" ;;
156
+ 'CLAUDE.md is stale — %s commits changed the project since it was last touched (limit %s)') s='CLAUDE.md bayat — son dokunulduğundan beri %s commit projeyi değiştirdi (sınır %s)' ;;
157
+ 're-read it against the code and update Stack / Project skills (the claude-md-improver flow)') s='kodla karşılaştırarak yeniden okuyun ve Stack / Project skills bölümlerini güncelleyin (claude-md-improver akışı)' ;;
158
+ 'freshness signal skipped (cannot read CLAUDE.md mtime on this platform)') s="güncellik sinyali atlandı (bu platformda CLAUDE.md'nin değişme zamanı okunamıyor)" ;;
159
+ ' → readiness %s/%s') s=' → hazırlık %s/%s' ;;
160
+ 'npx crewforth adopt') ;; # identifier, printed as is
161
+ 'chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg') ;; # identifier, printed as is
162
+ 'git config core.hooksPath .claude/hooks') ;; # identifier, printed as is
163
+ # No row: the line prints in English. CREW_I18N_MISS collects every such key, so smoke catches a missing
164
+ # translation by NAME.
165
+ *) [ -n "${CREW_I18N_MISS:-}" ] && printf '%s\n' "$s" >> "$CREW_I18N_MISS" ;;
166
+ esac
167
+ fi
168
+ # shellcheck disable=SC2059
169
+ printf -v _M "$s" "$@"
170
+ }
171
+ # ---- /CREW-I18N -----------------------------------------------------------------------------------------
172
+ _mt "== Crewforth — install doctor =="; echo "$_M"
173
+
174
+ # 0) Is Crewforth even here?
175
+ [ -d .claude ] || { bad "no .claude/ in '%s' — is Crewforth installed here?" "npx crewforth adopt" "$PWD"; exit 1; }
176
+
177
+ # 1) VERSION (marks a full install; also what /crew-update compares)
178
+ if [ -f .claude/VERSION ]; then ok "VERSION present (%s)" "$(head -1 .claude/VERSION | tr -cd '0-9A-Za-z.-')"
179
+ else bad "VERSION missing" "reinstall or update Crewforth (npx crewforth update)"; fi
180
+
181
+ # 1b) Is that version the current one? Read-only, from the cache session-update-check.sh maintains — doctor makes
182
+ # no network call of its own, so this stays honest offline (no cache -> nothing said) and instant everywhere.
183
+ # The session notice fires once per release; this is the surface that still answers when it was missed.
184
+ if [ -f .claude/VERSION ] && [ -f .claude/.state/update-check ]; then
185
+ read -r DLATEST _ < .claude/.state/update-check 2>/dev/null || DLATEST=""
186
+ DLATEST="$(printf '%s' "${DLATEST:-}" | tr -cd '0-9A-Za-z.-')"
187
+ DCUR="$(head -1 .claude/VERSION 2>/dev/null | tr -cd '0-9A-Za-z.-')"
188
+ if [ -n "$DLATEST" ] && awk -v a="$DLATEST" -v b="$DCUR" 'BEGIN{split(a,x,".");split(b,y,".");
189
+ 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
190
+ warn "Crewforth v%s installed, v%s published — update with /crew-update" "$DCUR" "$DLATEST"
191
+ fi
192
+ fi
193
+
194
+ # 2) Hooks present + executable. The .sh set is a glob (extras ok); pre-commit + commit-msg are REQUIRED — they
195
+ # ARE the §4.1/§4.2 trace/secret gate, so a MISSING one is a failure, not a silent skip.
196
+ NX=""; GONE=""
197
+ for h in .claude/hooks/*.sh; do [ -e "$h" ] || continue; [ -x "$h" ] || NX="$NX $(basename "$h")"; done
198
+ for h in .claude/hooks/pre-commit .claude/hooks/commit-msg; do
199
+ if [ ! -e "$h" ]; then GONE="$GONE $(basename "$h")"; elif [ ! -x "$h" ]; then NX="$NX $(basename "$h")"; fi
200
+ done
201
+ [ -z "$GONE" ] && ok "required git hooks present (pre-commit, commit-msg)" \
202
+ || bad "MISSING git hook(s):%s — the commit trace/secret scan is absent" "reinstall or update Crewforth" "$GONE"
203
+ [ -z "$NX" ] && ok "all hooks are executable" \
204
+ || bad "not executable:%s" "chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg" "$NX"
205
+
206
+ # 2b) Behaviour probe — a hook that is present + executable can still be NEUTERED (its body replaced with `exit 0`).
207
+ # Drive guard-bash with a command it MUST block; if it does not exit 2, the §4.5 gate is disarmed.
208
+ if [ -x .claude/hooks/guard-bash.sh ]; then
209
+ # CREW_GATE_LOG=/dev/null: this probe drives the real gate, so without it every `/crew-doctor` writes a
210
+ # synthetic "git push --force blocked" line into the evidence log — and the gate report would then be
211
+ # counting the diagnostics instead of what the model reached for. A measurement tool that contaminates the
212
+ # thing it measures is worse than none.
213
+ 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
214
+ 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)"
215
+ else ok "guard-bash.sh blocks a force-push (gate live, not neutered)"; fi
216
+
217
+ # 2c) The §4.6 review gate, probed the same way — and probed on the ONE case whose verdict cannot depend on
218
+ # this project's state. "git commit" alone is no probe: with a matching review-pass.json it legitimately
219
+ # passes §4.6, and under a mode that cannot prompt §4.4 blocks it anyway, so exit 2 would prove nothing
220
+ # about §4.6. A commit pointed at another worktree is refused by §4.6 whatever else is true here, and the
221
+ # verdict is read from the MESSAGE rather than the exit code for the same reason. Mode `default` so that a
222
+ # disarmed gate answers "ask" and exits 0 instead of colliding with §4.4's fail-closed branch. Nothing
223
+ # runs: this is a PreToolUse payload, not a command.
224
+ PROBE46="$(printf '%s' '{"tool_name":"Bash","permission_mode":"default","tool_input":{"command":"git -C /nonexistent-crew-probe commit -m probe"}}' \
225
+ | CREW_GATE_LOG=/dev/null bash .claude/hooks/guard-bash.sh 2>&1 >/dev/null)"
226
+ case "$PROBE46" in
227
+ *"4.6"*) ok "guard-bash.sh enforces the §4.6 review gate (gate live, not neutered)" ;;
228
+ *) bad "guard-bash.sh did NOT enforce §4.6 — a commit can land with no review of its diff" \
229
+ "restore guard-bash.sh from Crewforth (and check crew-review-agent still writes .claude/review-pass.json)" ;;
230
+ esac
231
+ fi
232
+
233
+ # 3) core.hooksPath — without it the §4.1/§4.2 commit trace + secret/bloat scan never runs
234
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
235
+ HP="$(git config --get core.hooksPath 2>/dev/null || true)"
236
+ case "$HP" in
237
+ */.claude/hooks|.claude/hooks) ok "core.hooksPath -> %s (commit-time gates active)" "$HP" ;;
238
+ "") bad "core.hooksPath is unset — commit trace/secret/bloat gates are INACTIVE" "git config core.hooksPath .claude/hooks" ;;
239
+ *) bad "core.hooksPath -> %s (not Crewforth's hooks)" "git config core.hooksPath .claude/hooks" "$HP" ;;
240
+ esac
241
+ else
242
+ warn "not a git repo — commit gates need: git init && git config core.hooksPath .claude/hooks"
243
+ fi
244
+
245
+ # 4) settings.json wires the tool-level gates, and each event maps to a NON-EMPTY hook array (an empty [] wires
246
+ # nothing). PreToolUse/UserPromptSubmit/Stop are required; SessionStart (rehydration) is a warn if absent.
247
+ S=.claude/settings.json
248
+ if [ -f "$S" ]; then
249
+ # ONE READER on every machine: Crewforth's own awk JSON reader, the same file adopt.sh merges with. There were two
250
+ # branches here — jq where jq ran, a name-and-bracket shape check where it did not — and the second could not
251
+ # tell valid JSON from invalid at all, so a stock Windows box got a weaker doctor than a Mac. Now both get the
252
+ # parse: validity, then each event's array length.
253
+ SJ=.claude/eval/lib/settings-json.awk
254
+ if [ ! -f "$SJ" ]; then
255
+ bad "Crewforth's JSON reader is missing (%s) — settings.json cannot be checked" "update Crewforth" "$SJ"
256
+ elif awk -v op=validate -f "$SJ" "$S" 2>/dev/null; then
257
+ ok "settings.json is valid JSON"
258
+ EMPTY=""
259
+ for ev in PreToolUse UserPromptSubmit Stop; do
260
+ n="$(awk -v op=len -v path="hooks.$ev" -f "$SJ" "$S" 2>/dev/null)"
261
+ case "$n" in ''|0) EMPTY="$EMPTY $ev" ;; esac
262
+ done
263
+ [ -z "$EMPTY" ] && ok "settings.json wires PreToolUse / UserPromptSubmit / Stop (non-empty)" \
264
+ || bad "settings.json hook events empty or missing:%s — those gates won't fire" "restore settings.json from Crewforth (npx crewforth update)" "$EMPTY"
265
+ sn="$(awk -v op=len -v path=hooks.SessionStart -f "$SJ" "$S" 2>/dev/null)"
266
+ 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
267
+ else bad "settings.json is invalid JSON" "fix the syntax by hand — restoring Crewforth's file would drop any hooks you added"; fi
268
+ # `${CLAUDE_PROJECT_DIR}` inside a hook command is the shape that breaks on Windows, and it breaks invisibly.
269
+ # Claude Code substitutes that placeholder into the command STRING before any shell sees it; on Windows the
270
+ # value is `C:\Repos\app` and the separators are gone by the time bash reads it. The reported path was
271
+ # `C:ReposApp/.claude/hooks/...` — every hook failed to launch and every gate was absent, while settings.json
272
+ # looked perfectly correct on inspection. Crewforth now uses a RELATIVE path (hooks run in the project
273
+ # directory), with a `cd` off the bare `$CLAUDE_PROJECT_DIR` as a belt for a session started in a subdirectory.
274
+ # Bare `$VAR` is not the placeholder syntax, so Claude Code leaves it for the shell to expand.
275
+ #
276
+ # Reported on every platform, not only Windows: a repo is shared across machines, and the wiring is wrong on
277
+ # all of them the moment one teammate is on Windows.
278
+ if grep -q '\${CLAUDE_PROJECT_DIR' "$S" 2>/dev/null; then
279
+ BAD_TAIL=' cd "$CLAUDE_PROJECT_DIR" 2>/dev/null; bash .claude/hooks/<name>.sh'
280
+ bad "settings.json wires hooks through the %s placeholder — on Windows its separators are stripped before bash runs, so NO hook launches and every gate is silently absent" \
281
+ "update Crewforth (npx crewforth adopt) — hook commands become:" '${CLAUDE_PROJECT_DIR}'
282
+ else
283
+ ok "hook wiring carries no path placeholder (nothing for Windows to mangle)"
284
+ fi
285
+ else
286
+ bad "settings.json missing — the tool-level gates (commit approval, guards, context) are INACTIVE" "reinstall Crewforth"
287
+ fi
288
+
289
+ # 4a) Skill listing budget. Claude Code puts a listing of every model-invocable skill's name and description into
290
+ # context each turn and caps it at skillListingBudgetFraction of the context window (default 0.01); over the
291
+ # cap it drops the descriptions of the least-used skills, which stops them matching requests. The count is
292
+ # eval/lib/skill-listing.awk — the method smoke-test.sh gates with, measured against Claude Code's own counter
293
+ # (see that file). The budget is the fraction as Claude Code resolves it (settings.local.json, then
294
+ # settings.json, then the user's settings, then 0.01) times the window's characters, and those were MEASURED
295
+ # from the "Skill listing over budget: … > B budget" debug warning: a 1,000,000-token window gives 3 characters
296
+ # per token of budget (0.001 -> 3000, 0.005 -> 15000), a 200,000-token window 4 (0.001 -> 800, 0.01 -> 8000).
297
+ # Another window is not measured; it is estimated at 3 and said to be an estimate. Reported, never enforced.
298
+ if [ -d .claude/skills ]; then
299
+ SLA=.claude/eval/lib/skill-listing.awk
300
+ SKF=""; for f in .claude/skills/*/SKILL.md; do [ -e "$f" ] && SKF=1 && break; done
301
+ if [ -n "$SKF" ] && [ -f "$SLA" ]; then
302
+ read -r LISTING NLISTED UNMEAS <<EOF_SLA
303
+ $(LC_ALL=C awk -f "$SLA" .claude/skills/*/SKILL.md)
304
+ EOF_SLA
305
+ FRAC=""; _mt "Claude Code's default"; FROM="$_M"
306
+ for sf in .claude/settings.local.json .claude/settings.json "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"; do
307
+ [ -f "$sf" ] || continue
308
+ v="$(awk -v op=get -v path=skillListingBudgetFraction -f "${SJ:-.claude/eval/lib/settings-json.awk}" "$sf" 2>/dev/null)" || v=""
309
+ case "$v" in ''|*[!0-9.]*) ;; *) FRAC="$v"; FROM="$sf"; break ;; esac
310
+ done
311
+ [ -n "$FRAC" ] || FRAC=0.01
312
+ CW="${CONTEXT_WINDOW:-1000000}"
313
+ case "$CW" in 1000000) CPT=3; EST="" ;; 200000) CPT=4; EST="" ;; *) CPT=3; _mt " (estimated: only 1M and 200k windows were measured)"; EST="$_M" ;; esac
314
+ BUDGET=$(awk -v f="$FRAC" -v w="$CW" -v c="$CPT" 'BEGIN { printf "%d", f * w * c }')
315
+ B200=$(awk -v f="$FRAC" 'BEGIN { printf "%d", f * 200000 * 4 }')
316
+ [ "${UNMEAS:-0}" = 0 ] || warn "skill listing: %s skill(s) use when_to_use or a folded description — a shape the count was not measured on" "$UNMEAS"
317
+ if [ "$LISTING" -le "$BUDGET" ]; then
318
+ ok "skill listing %s chars for %s skills fits the %s-char budget (fraction %s from %s, %s-token window%s)" "$LISTING" "$NLISTED" "$BUDGET" "$FRAC" "$FROM" "$CW" "$EST"
319
+ else
320
+ warn "skill listing %s chars for %s skills EXCEEDS the %s-char budget (fraction %s from %s, %s-token window%s)" "$LISTING" "$NLISTED" "$BUDGET" "$FRAC" "$FROM" "$CW" "$EST"
321
+ warn " — Claude Code drops the descriptions of the least-used skills, and those stop matching requests."
322
+ warn " Fixes: raise %s in settings, or set rarely-used skills to %s in %s." '"skillListingBudgetFraction"' '"name-only"' '"skillOverrides"'
323
+ warn " Which skills? bash .claude/eval/utilization.sh — it reports the ones nothing in this project reached."
324
+ fi
325
+ # The whole listing, not only Crewforth's share: the user's personal skills and Claude Code's own bundled skills
326
+ # sit in the same budget. The bundled ones were measured once (14 skills, 5898 chars, Claude Code v2.1.282, an
327
+ # isolated config) — another version may differ, so the figure is labelled as measured, not read. Skills from
328
+ # other plugins are not counted: the doctor cannot see them.
329
+ PERS=0; PD="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills"
330
+ for f in "$PD"/*/SKILL.md; do [ -e "$f" ] && { PERS=$(LC_ALL=C awk -f "$SLA" "$PD"/*/SKILL.md | cut -d' ' -f1); break; }; done
331
+ BUILTIN=5898; TOTAL=$((LISTING + BUILTIN + 1)); [ "$PERS" -gt 0 ] && TOTAL=$((TOTAL + PERS + 1)) # one joining character per group
332
+ if [ "$TOTAL" -gt "$B200" ]; then
333
+ warn "on a 200,000-token model the whole listing would be ~%s chars (Crewforth %s + your personal skills %s + Claude Code's own ~%s, measured) against %s at fraction %s — the least-used skills there lose their descriptions." "$TOTAL" "$LISTING" "$PERS" "$BUILTIN" "$B200" "$FRAC"
334
+ warn " If you use such a model, one line in %s/settings.json fixes it: %s" "${CLAUDE_CONFIG_DIR:-~/.claude}" '"skillListingBudgetFraction": 0.04'
335
+ TK=$((TOTAL / 4)); PCT=$(awk -v t="$TK" 'BEGIN { printf "%.1f", t / 2000 }'); [ "$CREW_LANG" = tr ] && PCT="${PCT/./,}"
336
+ warn " What it costs: the listing is sent every turn. At 0.04 it stays whole — ~%s chars, about %s tokens," "$TOTAL" "$TK"
337
+ warn " %s%% of a 200k window — instead of at most %s chars. (4 characters per token is how Claude Code sizes" "$PCT" "$B200"
338
+ warn " this budget on a 200k window: 0.01 of 200,000 tokens is 8,000 characters.)"
339
+ fi
340
+ fi
341
+ fi
342
+
343
+ # 4b) Is delegation itself switched off? The documented way to stop Claude using ANY subagent is to deny the `Agent`
344
+ # tool in permissions.deny. A project that does that keeps twelve agents on disk that can never run, and the only
345
+ # symptom is that every task quietly happens on the main thread — which reads as "Crewforth does nothing" rather
346
+ # than as a setting. Checked at every scope the CLI merges, because one line in ~/.claude/settings.json disables
347
+ # delegation for every project on the machine. Denying it may be deliberate; this names it, it does not judge.
348
+ DENYSRC=""
349
+ for f in .claude/settings.json .claude/settings.local.json "$HOME/.claude/settings.json"; do
350
+ [ -f "$f" ] || continue
351
+ # A deny entry for the delegation tool, in any of its spellings, with or without an argument pattern. Read
352
+ # with Crewforth's JSON reader, so "is Agent/Task in permissions.deny" is answered from the parse on every OS.
353
+ # It used to need python3: on Windows — a Store stub, or nothing — the check degraded to "both words appear in
354
+ # the file", a prompt instead of a verdict. An unreadable file is still NOT a silent pass (NOPY below).
355
+ 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
356
+ NOPY=1; MAYBEDENY="${MAYBEDENY:-} $f"; continue
357
+ fi
358
+ awk -v op=strings -v path=permissions.deny -f "${SJ:-.claude/eval/lib/settings-json.awk}" "$f" 2>/dev/null \
359
+ | grep -qE '^(Agent|Task)([^A-Za-z0-9_]|$)' && DENYSRC="$DENYSRC $f"
360
+ done
361
+ if [ "${NOPY:-0}" = 1 ]; then
362
+ warn "delegation check could not read:%s — not valid JSON (or Crewforth's reader is missing)." "$MAYBEDENY"
363
+ warn " open it and check that Agent/Task is not under permissions.deny. If it is, no subagent can ever run."
364
+ fi
365
+ # `A && B && ok … || bad …` cannot express three outcomes. With no usable python3 the && chain is false, so the
366
+ # || arm fired and reported `the Agent tool is DENIED in:` — an empty list, a failure that had not been found,
367
+ # and a verdict of ❌ on a healthy install. It stayed invisible while `command -v python3` was the test, because
368
+ # on Windows the Store stub answered yes and NOPY was never set. Three states, three branches.
369
+ if [ -n "$DENYSRC" ]; then
370
+ bad "the Agent tool is DENIED in:%s — no subagent can ever run, so every agent on disk is dead weight" \
371
+ "remove the Agent/Task entry from permissions.deny, or accept that this project runs main-thread-only" "$DENYSRC"
372
+ elif [ "${NOPY:-0}" != 1 ]; then
373
+ ok "delegation is enabled (the Agent tool is not denied)"
374
+ fi
375
+
376
+ # 5) Agent-name references resolve to an installed agent — checked across CLAUDE.md AND every local doc it points to
377
+ # (its @imports and docs/*.md references). A brownfield takeover renames the project's agents to `crew-` ids, but
378
+ # CLAUDE.md — or an orchestration doc it delegates to, e.g. "detail: docs/AGENTS.md" — may still name the OLD bare
379
+ # agent. That name matches no installed agent, so delegation to it silently fails. Following CLAUDE.md's reference
380
+ # chain catches the pointed-to docs too, while unreferenced prose (design/audit docs, code comments) is ignored —
381
+ # so it stays complete without the false positives a blanket repo scan would raise.
382
+ if [ -f CLAUDE.md ] && ls .claude/agents/*.md >/dev/null 2>&1; then
383
+ # Two pull-only agents are invoked explicitly (a commit needs approval; session health is emitted by a hook),
384
+ # NOT auto-delegated — so a bare reference to them does not break delegation; it is only a naming inconsistency.
385
+ PULL_AGENTS=" crew-commit-agent crew-session-manager "
386
+ # Scan set = CLAUDE.md + the local .md files it references (one level: its @imports and any `docs/…md` path).
387
+ SCAN="CLAUDE.md"
388
+ for r in $(grep -oE '@?[A-Za-z0-9_./-]+\.md' CLAUDE.md 2>/dev/null | sed 's/^@//' | sort -u); do
389
+ [ -f "$r" ] && [ "$r" != "CLAUDE.md" ] && SCAN="$SCAN $r"
390
+ done
391
+ # TWO awk passes, not a nested shell loop. This check used to run `sed|head|tr` per agent and then a
392
+ # `grep|cut|tr|sed` for every (agent × scanned file) pair — 12 agents against a handful of docs is already
393
+ # ~250 process spawns. On Linux/macOS that is invisible; on Windows, where Git Bash pays 62-135 ms per spawn
394
+ # instead of ~1.7ms, doctor stopped dead right here and looked hung to the user who ran it. Same disease the
395
+ # route-hint hook had, same cure: let awk do the looping. Two spawns, whatever the component count.
396
+ CREW_AGENT_BASES="$(awk '
397
+ FNR==1 { files[++nf]=FILENAME }
398
+ !got[FILENAME] && /^name:[[:space:]]*/ {
399
+ n=$0; sub(/^name:[[:space:]]*/,"",n); gsub(/[^a-zA-Z0-9-]/,"",n) # same charset tr -cd kept
400
+ if (n != "") { nm[FILENAME]=n; got[FILENAME]=1 }
401
+ }
402
+ END {
403
+ for (i=1;i<=nf;i++) {
404
+ f=files[i]; n=(f in nm) ? nm[f] : ""
405
+ if (n=="") { n=f; sub(/\.md$/,"",n); sub(/.*\//,"",n) } # fallback: the file name
406
+ if (n ~ /^crew-/) { b=n; sub(/^crew-/,"",b); print b "\t" n; print b "-csk\t" n } # the 2.x name is stale too
407
+ }
408
+ }' .claude/agents/*.md)"
409
+ export CREW_AGENT_BASES
410
+ STALE=""; STALE_PULL=""
411
+ while IFS="$(printf '\t')" read -r base name f lines; do
412
+ [ -n "$base" ] || continue
413
+ entry="
414
+ ↳ \"$base\" → \"$name\" ($f line(s): $lines)"
415
+ case "$PULL_AGENTS" in *" $name "*) STALE_PULL="$STALE_PULL$entry" ;; *) STALE="$STALE$entry" ;; esac
416
+ done <<EOF
417
+ $(awk '
418
+ BEGIN {
419
+ n = split(ENVIRON["CREW_AGENT_BASES"], rows, "\n"); k=0
420
+ for (i=1;i<=n;i++) { if (rows[i]=="") continue; split(rows[i], a, "\t"); k++; base[k]=a[1]; full[k]=a[2] }
421
+ nb=k
422
+ }
423
+ FNR==1 { order[++nf]=FILENAME }
424
+ {
425
+ # bare `base` NOT touching a `-` on either side (so not crew-base, nor base-local) and not glued into a longer word — the identical
426
+ # boundary the grep used. Agent ids are [a-z-] only, so nothing here needs regex escaping.
427
+ for (i=1;i<=nb;i++)
428
+ if ($0 ~ ("(^|[^a-zA-Z0-9_-]|@agent-)" base[i] "([^a-zA-Z0-9_-]|$)"))
429
+ hit[i, FILENAME] = (hit[i, FILENAME]=="" ? FNR : hit[i, FILENAME] "," FNR)
430
+ }
431
+ # Emitted in agent order, then scanned-file order, so the report reads the same as it always did rather
432
+ # than in awk hash order.
433
+ END {
434
+ for (i=1;i<=nb;i++)
435
+ for (j=1;j<=nf;j++)
436
+ if ((i, order[j]) in hit) print base[i] "\t" full[i] "\t" order[j] "\t" hit[i, order[j]]
437
+ }' $SCAN)
438
+ EOF
439
+ if [ -n "$STALE" ]; then
440
+ BAD_TAIL="$STALE"
441
+ bad "CLAUDE.md (or a doc it references) names auto-delegated agent(s) that no installed agent matches — delegation to them silently fails" \
442
+ 'rename each bare reference to its `crew-` id:'
443
+ fi
444
+ [ -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:%s" "$STALE_PULL"
445
+ [ -z "$STALE$STALE_PULL" ] && ok "agent references resolve to installed agents (CLAUDE.md + referenced docs)"
446
+ fi
447
+
448
+ # 6) Does the discipline actually REACH the model? `.claude/DISCIPLINE.md` sitting on disk is inert unless
449
+ # `./CLAUDE.md` pulls it in — Claude Code reads CLAUDE.md, not Crewforth's own files. This is the one failure
450
+ # every check above is blind to: the hooks fire, the gates are live, and yet §1–§3 (routing, DoD, session
451
+ # management) never enter the context, so the model works without any of the discipline it is measured on.
452
+ # Two shapes load it: the `@import` line, or the pre-1.1 layout that pasted the discipline inline (stale,
453
+ # but loaded). Neither present -> absent, and that is a failure, not a style note.
454
+ if [ -f .claude/DISCIPLINE.md ]; then
455
+ if [ ! -f CLAUDE.md ]; then
456
+ bad "CLAUDE.md missing — .claude/DISCIPLINE.md is never loaded (routing / DoD / session rules absent)" \
457
+ "create CLAUDE.md with this as its own line: @.claude/DISCIPLINE.md"
458
+ elif grep -qE '^[[:space:]]*@\.claude/DISCIPLINE\.md[[:space:]]*$' CLAUDE.md; then
459
+ ok "CLAUDE.md imports .claude/DISCIPLINE.md (the discipline reaches the model)"
460
+ elif grep -q '^## Four working principles' CLAUDE.md && grep -qE '^### 4\.[45] ' CLAUDE.md; then
461
+ 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"
462
+ else
463
+ bad "CLAUDE.md does not import .claude/DISCIPLINE.md — the discipline is on disk but never loaded" \
464
+ "add this as its own line at the top of CLAUDE.md: @.claude/DISCIPLINE.md"
465
+ fi
466
+ fi
467
+
468
+ echo "---"
469
+ # The verdict is the line people read, and some read only it. The preflight block
470
+ # below reports a missing node, but it prints AFTER this — so on a machine that
471
+ # cannot start the panel the last word a reader takes away was "healthy". The
472
+ # verdict is not wrong (every gate is wired and holds without node) so it keeps
473
+ # its tick, and carries what it costs beside it.
474
+ # Resolved here rather than beside the preflight report below, because the verdict
475
+ # needs to ask it a question and the verdict prints first.
476
+ PREFLIGHT=".claude/eval/preflight.sh"
477
+ [ -f "$PREFLIGHT" ] || PREFLIGHT="$(dirname "$0")/preflight.sh"
478
+ PANEL_NOTE=""
479
+ if [ -d .claude/studio ] && ! bash "$PREFLIGHT" --has node 2>/dev/null; then
480
+ _mt " · panel needs Node 18+ — .claude/studio/ensure-node.sh --plan fetches one"; PANEL_NOTE="$_M"
481
+ fi
482
+ # 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.
483
+ for _v in $(compgen -e); do
484
+ case "$_v" in CSK_CORRECT_STACK) ;; CSK_*)
485
+ case "${_crew_legacy:-}" in
486
+ *" ${_v#CSK_} "*) warn "%s is set — its 3.0 name is CREW_%s (the old name works until 4.0)" "$_v" "${_v#CSK_}" ;;
487
+ *) warn "%s is set but no longer read — set CREW_%s instead" "$_v" "${_v#CSK_}" ;;
488
+ esac ;;
489
+ esac
490
+ done
491
+ if [ "$FAIL" -eq 0 ]; then _mt "DOCTOR: healthy ✅%s" "$PANEL_NOTE"; echo "$_M"
492
+ # Healthy verdict: the star line, once per kit version — the marker is shared with the installers, so the
493
+ # doctor run that /crew-update makes right after an update stays quiet. Text/URL/silence: lib/star.sh.
494
+ [ -f "$(dirname "$0")/lib/star.sh" ] && bash "$(dirname "$0")/lib/star.sh" --once .
495
+ else _mt "DOCTOR: %s issue(s) ❌ — apply the fixes above%s" "$FAIL" "$PANEL_NOTE"; echo "$_M"; fi
496
+
497
+ # 8b) The shell matcher. Claude Code's hooks reference is explicit: inspect shell commands with
498
+ # `Bash|PowerShell`, because wherever the PowerShell tool is enabled it IS the shell — and it is on by
499
+ # default for claude.ai and Console accounts on Windows. An install from before 2.5.0 watches only `Bash`,
500
+ # so every PowerShell command walks past the §4.5 rules with nothing firing. Nothing about the session
501
+ # looks wrong, which is why this is a check and not a release note.
502
+ if [ -f .claude/settings.json ]; then
503
+ if grep -q '"matcher"[[:space:]]*:[[:space:]]*"[^"]*PowerShell' .claude/settings.json; then
504
+ ok "shell gates watch both Bash and PowerShell"
505
+ elif grep -q '"matcher"[[:space:]]*:[[:space:]]*"Bash"' .claude/settings.json; then
506
+ BAD_TAIL=' "Bash|PowerShell"'
507
+ bad "shell gates watch only Bash — PowerShell commands bypass every §4.5 rule" \
508
+ "update Crewforth (npx crewforth update), or set the PreToolUse matcher to"
509
+ fi
510
+ fi
511
+ # 9) The auto-mode classifier. Since 2026-08-14 auto mode is the default permission mode on Pro/Max/Team, so a
512
+ # classifier answers permission prompts the user used to answer. Two things can be wrong and neither shows
513
+ # up in a session. Only ONE of them is a real gate finding: a custom autoMode block that dropped the
514
+ # built-ins by omitting "$defaults" — 66 soft blocks gone, silently. Whether Crewforth's own rules are present
515
+ # is reported but NOT treated as a failure: they were measured on 2026-08-24 not to enforce (see the skill).
516
+ if [ -x .claude/skills/automode-policy/scripts/check.sh ] || [ -f .claude/skills/automode-policy/scripts/check.sh ]; then
517
+ AMOUT="$(bash .claude/skills/automode-policy/scripts/check.sh 2>&1)"; AMRC=$?
518
+ case "$AMRC" in
519
+ 0) ok "auto-mode classifier config: built-ins intact, Crewforth rules present (config, not a gate)" ;;
520
+ 2) bad "auto-mode classifier BUILT-INS DROPPED — an autoMode array lacks %s" \
521
+ "restore it in ~/.claude/settings.json; see .claude/skills/automode-policy/SKILL.md" '"$defaults"' ;;
522
+ 3) skip "auto-mode classifier config: Crewforth rules absent (measured not to enforce — see the skill)" ;;
523
+ # Same vocabulary as the other three branches on purpose. It used to read "auto-mode policy check
524
+ # skipped", and the suite's assertion — written on a machine that HAS the claude CLI — never saw this
525
+ # branch. CI has no CLI, so every run took it and the case failed on a wording difference, not a defect.
526
+ *) skip "auto-mode classifier config: not checked (no claude CLI, or auto mode unavailable here)" ;;
527
+ esac
528
+ [ "$AMRC" = 2 ] && printf '%s\n' "$AMOUT" | grep 'auto-mode config'
529
+ else
530
+ warn "auto-mode policy check skipped (install predates the automode-policy skill; run the updater)"
531
+ fi
532
+ # 10) Gate activity. The suite proves the gates CAN fire; this reports whether anything actually tripped them.
533
+ # Recording is on by default (rule names only, never the command), so "no log" here means no gate has
534
+ # fired yet — a measured zero, not a gap. Never a failure either way.
535
+ # Read through --json and worded here, so the line speaks the doctor's language (gate-report itself is English
536
+ # and prints its own sentence; quoting it here used to leave half of that sentence dangling on this line).
537
+ if [ -f .claude/eval/gate-report.sh ]; then
538
+ GOUT="$(bash .claude/eval/gate-report.sh --json 2>/dev/null)"; GRC=$?
539
+ case "$GRC" in
540
+ 0) GRU="${GOUT#*\"rules\":}"; GRU="${GRU%%[,\}]*}"; GDE="${GOUT#*\"decisions\":}"; GDE="${GDE%%[,\}]*}"
541
+ case "$GRU$GDE" in
542
+ *[!0-9]*|'') ok "gate activity recorded (see /crew-gates)" ;;
543
+ *) if [ "$GDE" = 0 ]; then ok "gate activity: no gate has fired in this project yet — %s rules wired, recording on" "$GRU"
544
+ else ok "gate activity: %s decision(s) recorded (see /crew-gates)" "$GDE"; fi ;;
545
+ esac ;;
546
+ 3) skip "gate activity NOT MEASURED — nowhere to record (see /crew-gates)" ;;
547
+ *) skip "gate activity unreadable (see /crew-gates)" ;;
548
+ esac
549
+ fi
550
+ # --- Agentic readiness (ADVISORY) -------------------------------------------------------------------------
551
+ # Everything above answers "are Crewforth's gates live?". This answers a different question the gates cannot see:
552
+ # "is this PROJECT set up so an agent can actually work well in it?" A flawless install still starves its
553
+ # agents when the CLAUDE.md project section is left as the template, there is no sandbox to run in, and no
554
+ # project-specific skill carries the domain. These are project maturity, not install health, so they NEVER
555
+ # change the exit code — doctor's verdict stays a statement about the install.
556
+ echo
557
+ # The toolchain the install actually landed on. It runs here as well as in the installers because the machine
558
+ # changes after install day — a wiped PATH, a new laptop, a corporate image that removed jq — and Crewforth's
559
+ # fallbacks mean none of that announces itself. Advisory: it never changes the verdict above.
560
+ # doctor has already cd'd into the project, so the installed copy is the one to run; fall back to the copy
561
+ # sitting beside this script for the case where doctor is run straight out of the Crewforth source.
562
+ [ -f "$PREFLIGHT" ] && bash "$PREFLIGHT"
563
+
564
+ _mt "Readiness (advisory — does not affect the verdict above):"; echo "$_M"
565
+ RDY=0; RTOT=0
566
+ rdy(){ RTOT=$((RTOT+1)); RDY=$((RDY+1)); _mt "$@"; echo " ✅ $_M"; }
567
+ gap(){ local _m="$1" _x="$2"; shift 2; RTOT=$((RTOT+1)); _mt "$_m" "$@"; echo " ➖ $_M"; _mt "$_x"; echo " ↳ $_M"; }
568
+
569
+ # R1) Is the CLAUDE.md project section filled in, or still the shipped template? An unfilled section means every
570
+ # agent works stack-blind — it is the single most common way a correct install still underperforms.
571
+ if [ -f CLAUDE.md ]; then
572
+ if grep -qE '<PROJECT NAME>|<One sentence:|<Fill in per the project' CLAUDE.md; then
573
+ gap "CLAUDE.md project section is still the template (placeholders left in)" \
574
+ "fill in Project / Stack / Project skills — agents read the stack from there"
575
+ else rdy "CLAUDE.md project section is filled in"; fi
576
+ fi
577
+
578
+ # R2) A project-specific skill — Crewforth ships the generic 'how's; the domain ones (payment-contract,
579
+ # notification-rules, a backend-pattern skill) are the project's to add. Needs the install manifest to tell
580
+ # kit-shipped from project-owned; without it (pre-1.8 install) the signal is unknowable, so it is skipped
581
+ # rather than guessed — a wrong "you have no project skills" is worse than no line at all.
582
+ MAN=.claude/kit-manifest.txt
583
+ if [ -f "$MAN" ]; then
584
+ OWN=0
585
+ # The CR strip is the whole reason this reads through `tr` rather than grepping the file directly, and it is
586
+ # not defensive: MEASURED on a CRLF manifest with one project skill installed, this counted TWO. `grep -qxF`
587
+ # wants a whole-line match, `skills/handoff\r` is not `skills/handoff`, so every KIT skill read as
588
+ # project-owned. A Windows checkout with core.autocrlf produces exactly that manifest. `skill-trust.sh`
589
+ # already strips it for the same reason and the same file — this was the copy that did not, which is why the
590
+ # two answers disagreed on the same install.
591
+ MANTXT="$(tr -d '\r' < "$MAN")"
592
+ # A whole-line match by shell pattern, the way skill-trust.sh reads the same file: a `basename` and a `grep`
593
+ # per skill was 82 spawns for the shipped set — the bulk of doctor's cost on Git Bash.
594
+ NL='
595
+ '
596
+ for d in .claude/skills/*/; do
597
+ [ -d "$d" ] || continue
598
+ s="${d%/}"; s="${s##*/}"
599
+ case "$NL$MANTXT$NL" in *"${NL}skills/$s$NL"*) ;; *) OWN=$((OWN+1)) ;; esac
600
+ done
601
+ [ "$OWN" -gt 0 ] && rdy "%s project-specific skill(s) alongside Crewforth's" "$OWN" \
602
+ || gap "no project-specific skill — only Crewforth's generic ones are installed" \
603
+ "put the domain 'how's in .claude/skills/ (format: .claude/AGENT_TEMPLATE.md)"
604
+ else
605
+ skip "project-skill signal skipped (no .claude/kit-manifest.txt — install predates it; run the updater)"
606
+ fi
607
+
608
+ # R3) A sandbox to run in. Agentic work executes commands; a devcontainer is what makes that bounded rather
609
+ # than trusting every command against the host.
610
+ if [ -f .devcontainer/devcontainer.json ]; then rdy "devcontainer present (agentic execution is sandboxed)"
611
+ else gap "no .devcontainer/devcontainer.json — agent commands run directly against your machine" \
612
+ "add a devcontainer, or keep approval-mode gates on for anything destructive (§4.5)"; fi
613
+
614
+ # R4) MCP servers — the project's own tools/data reaching the model. Either the project-level .mcp.json or an
615
+ # mcpServers block in Crewforth's settings counts.
616
+ if [ -f .mcp.json ] || grep -q '"mcpServers"' .claude/settings.json 2>/dev/null; then
617
+ rdy "MCP servers configured (project tools/data reach the model)"
618
+ else gap "no MCP server configured — the model has no project-specific tool access" \
619
+ "add .mcp.json when a tool/data source would help (the mcp-builder skill covers writing one)"; fi
620
+
621
+ # R5) Context freshness. CLAUDE.md is read once per session and is the only project-wide instruction the model
622
+ # gets; if the code moved a long way since it was last touched, it is describing a project that no longer
623
+ # exists. Counted as commits (not days) that changed something OUTSIDE .claude since CLAUDE.md's mtime —
624
+ # mtime, not git history, because a default install gitignores CLAUDE.md and it has no history to read.
625
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -f CLAUDE.md ]; then
626
+ MT="$(stat -c %Y CLAUDE.md 2>/dev/null || stat -f %m CLAUDE.md 2>/dev/null)"
627
+ MT="$(printf '%s' "${MT:-}" | tr -cd '0-9')"
628
+ MAXC="${CREW_FRESHNESS_MAX:-40}"
629
+ if [ -n "$MT" ]; then
630
+ CH="$(git rev-list --count HEAD --since="@$MT" -- . ':(exclude).claude' 2>/dev/null | tr -cd '0-9')"
631
+ CH="${CH:-0}"
632
+ [ "$CH" -le "$MAXC" ] && rdy "CLAUDE.md is current (%s commit(s) of drift since it was last touched)" "$CH" \
633
+ || gap "CLAUDE.md is stale — %s commits changed the project since it was last touched (limit %s)" \
634
+ "re-read it against the code and update Stack / Project skills (the claude-md-improver flow)" "$CH" "$MAXC"
635
+ else skip "freshness signal skipped (cannot read CLAUDE.md mtime on this platform)"; fi
636
+ fi
637
+
638
+ [ "$RTOT" -gt 0 ] && { _mt " → readiness %s/%s" "$RDY" "$RTOT"; echo "$_M"; }
639
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1