crewforth 3.0.0-rc.1 → 3.0.0-rc.3
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 +22 -0
- package/adopt.sh +82 -13
- package/kit/CLAUDE.md +1 -1
- package/kit/eval/doctor.sh +240 -66
- package/kit/eval/lib/skill-listing.awk +43 -0
- package/kit/eval/smoke-test.sh +137 -70
- package/kit/hooks/guard-bash.sh +3 -1
- package/kit/hooks/session-rehydrate.sh +24 -5
- package/kit/hooks/skill-trust.sh +16 -3
- package/kit/settings.json +1 -1
- package/kit/skills/crew-doctor/SKILL.md +12 -2
- package/kit/skills/crew-review/SKILL.md +7 -4
- package/kit/skills/crew-skill/SKILL.md +2 -1
- package/package.json +1 -1
- package/start.sh +23 -7
package/kit/eval/doctor.sh
CHANGED
|
@@ -18,22 +18,164 @@ cd "$ROOT" 2>/dev/null || { echo "doctor: cannot enter '$ROOT'"; exit 2; }
|
|
|
18
18
|
exec </dev/null
|
|
19
19
|
|
|
20
20
|
FAIL=0
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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"; }
|
|
24
29
|
# `skip` lives up here with the other reporters, not down in the readiness block where it used to be
|
|
25
30
|
# defined: the health checks call it too, and a helper defined after its first caller is a silent
|
|
26
31
|
# no-op — the line never prints and the run shows `skip: command not found` on stderr, where nobody
|
|
27
32
|
# looks. Found by RUNNING doctor against a fixture install; grepping for the call sites said wired.
|
|
28
|
-
skip(){ echo " · $
|
|
33
|
+
skip(){ _mt "$@"; echo " · $_M"; }
|
|
29
34
|
|
|
30
|
-
|
|
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"
|
|
31
173
|
|
|
32
174
|
# 0) Is Crewforth even here?
|
|
33
|
-
[ -d .claude ] || {
|
|
175
|
+
[ -d .claude ] || { bad "no .claude/ in '%s' — is Crewforth installed here?" "npx crewforth adopt" "$PWD"; exit 1; }
|
|
34
176
|
|
|
35
177
|
# 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.-')
|
|
178
|
+
if [ -f .claude/VERSION ]; then ok "VERSION present (%s)" "$(head -1 .claude/VERSION | tr -cd '0-9A-Za-z.-')"
|
|
37
179
|
else bad "VERSION missing" "reinstall or update Crewforth (npx crewforth update)"; fi
|
|
38
180
|
|
|
39
181
|
# 1b) Is that version the current one? Read-only, from the cache session-update-check.sh maintains — doctor makes
|
|
@@ -45,7 +187,7 @@ if [ -f .claude/VERSION ] && [ -f .claude/.state/update-check ]; then
|
|
|
45
187
|
DCUR="$(head -1 .claude/VERSION 2>/dev/null | tr -cd '0-9A-Za-z.-')"
|
|
46
188
|
if [ -n "$DLATEST" ] && awk -v a="$DLATEST" -v b="$DCUR" 'BEGIN{split(a,x,".");split(b,y,".");
|
|
47
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
|
|
48
|
-
warn "Crewforth v
|
|
190
|
+
warn "Crewforth v%s installed, v%s published — update with /crew-update" "$DCUR" "$DLATEST"
|
|
49
191
|
fi
|
|
50
192
|
fi
|
|
51
193
|
|
|
@@ -57,9 +199,9 @@ for h in .claude/hooks/pre-commit .claude/hooks/commit-msg; do
|
|
|
57
199
|
if [ ! -e "$h" ]; then GONE="$GONE $(basename "$h")"; elif [ ! -x "$h" ]; then NX="$NX $(basename "$h")"; fi
|
|
58
200
|
done
|
|
59
201
|
[ -z "$GONE" ] && ok "required git hooks present (pre-commit, commit-msg)" \
|
|
60
|
-
|| bad "MISSING git hook(s)
|
|
202
|
+
|| bad "MISSING git hook(s):%s — the commit trace/secret scan is absent" "reinstall or update Crewforth" "$GONE"
|
|
61
203
|
[ -z "$NX" ] && ok "all hooks are executable" \
|
|
62
|
-
|| bad "not executable
|
|
204
|
+
|| bad "not executable:%s" "chmod +x .claude/hooks/*.sh .claude/hooks/pre-commit .claude/hooks/commit-msg" "$NX"
|
|
63
205
|
|
|
64
206
|
# 2b) Behaviour probe — a hook that is present + executable can still be NEUTERED (its body replaced with `exit 0`).
|
|
65
207
|
# Drive guard-bash with a command it MUST block; if it does not exit 2, the §4.5 gate is disarmed.
|
|
@@ -92,9 +234,9 @@ fi
|
|
|
92
234
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
93
235
|
HP="$(git config --get core.hooksPath 2>/dev/null || true)"
|
|
94
236
|
case "$HP" in
|
|
95
|
-
*/.claude/hooks|.claude/hooks) ok "core.hooksPath ->
|
|
237
|
+
*/.claude/hooks|.claude/hooks) ok "core.hooksPath -> %s (commit-time gates active)" "$HP" ;;
|
|
96
238
|
"") bad "core.hooksPath is unset — commit trace/secret/bloat gates are INACTIVE" "git config core.hooksPath .claude/hooks" ;;
|
|
97
|
-
*) bad "core.hooksPath ->
|
|
239
|
+
*) bad "core.hooksPath -> %s (not Crewforth's hooks)" "git config core.hooksPath .claude/hooks" "$HP" ;;
|
|
98
240
|
esac
|
|
99
241
|
else
|
|
100
242
|
warn "not a git repo — commit gates need: git init && git config core.hooksPath .claude/hooks"
|
|
@@ -110,7 +252,7 @@ if [ -f "$S" ]; then
|
|
|
110
252
|
# parse: validity, then each event's array length.
|
|
111
253
|
SJ=.claude/eval/lib/settings-json.awk
|
|
112
254
|
if [ ! -f "$SJ" ]; then
|
|
113
|
-
bad "Crewforth's JSON reader is missing (
|
|
255
|
+
bad "Crewforth's JSON reader is missing (%s) — settings.json cannot be checked" "update Crewforth" "$SJ"
|
|
114
256
|
elif awk -v op=validate -f "$SJ" "$S" 2>/dev/null; then
|
|
115
257
|
ok "settings.json is valid JSON"
|
|
116
258
|
EMPTY=""
|
|
@@ -119,7 +261,7 @@ if [ -f "$S" ]; then
|
|
|
119
261
|
case "$n" in ''|0) EMPTY="$EMPTY $ev" ;; esac
|
|
120
262
|
done
|
|
121
263
|
[ -z "$EMPTY" ] && ok "settings.json wires PreToolUse / UserPromptSubmit / Stop (non-empty)" \
|
|
122
|
-
|| bad "settings.json hook events empty or missing
|
|
264
|
+
|| bad "settings.json hook events empty or missing:%s — those gates won't fire" "restore settings.json from Crewforth (npx crewforth update)" "$EMPTY"
|
|
123
265
|
sn="$(awk -v op=len -v path=hooks.SessionStart -f "$SJ" "$S" 2>/dev/null)"
|
|
124
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
|
|
125
267
|
else bad "settings.json is invalid JSON" "fix the syntax by hand — restoring Crewforth's file would drop any hooks you added"; fi
|
|
@@ -134,8 +276,9 @@ if [ -f "$S" ]; then
|
|
|
134
276
|
# Reported on every platform, not only Windows: a repo is shared across machines, and the wiring is wrong on
|
|
135
277
|
# all of them the moment one teammate is on Windows.
|
|
136
278
|
if grep -q '\${CLAUDE_PROJECT_DIR' "$S" 2>/dev/null; then
|
|
137
|
-
|
|
138
|
-
|
|
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}'
|
|
139
282
|
else
|
|
140
283
|
ok "hook wiring carries no path placeholder (nothing for Windows to mangle)"
|
|
141
284
|
fi
|
|
@@ -143,34 +286,57 @@ else
|
|
|
143
286
|
bad "settings.json missing — the tool-level gates (commit approval, guards, context) are INACTIVE" "reinstall Crewforth"
|
|
144
287
|
fi
|
|
145
288
|
|
|
146
|
-
# 4a) Skill listing budget. Claude Code
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
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.
|
|
152
298
|
if [ -d .claude/skills ]; then
|
|
153
|
-
|
|
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.
|
|
299
|
+
SLA=.claude/eval/lib/skill-listing.awk
|
|
158
300
|
SKF=""; for f in .claude/skills/*/SKILL.md; do [ -e "$f" ] && SKF=1 && break; done
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
174
340
|
fi
|
|
175
341
|
fi
|
|
176
342
|
|
|
@@ -193,7 +359,7 @@ for f in .claude/settings.json .claude/settings.local.json "$HOME/.claude/settin
|
|
|
193
359
|
| grep -qE '^(Agent|Task)([^A-Za-z0-9_]|$)' && DENYSRC="$DENYSRC $f"
|
|
194
360
|
done
|
|
195
361
|
if [ "${NOPY:-0}" = 1 ]; then
|
|
196
|
-
warn "delegation check could not read
|
|
362
|
+
warn "delegation check could not read:%s — not valid JSON (or Crewforth's reader is missing)." "$MAYBEDENY"
|
|
197
363
|
warn " open it and check that Agent/Task is not under permissions.deny. If it is, no subagent can ever run."
|
|
198
364
|
fi
|
|
199
365
|
# `A && B && ok … || bad …` cannot express three outcomes. With no usable python3 the && chain is false, so the
|
|
@@ -201,8 +367,8 @@ fi
|
|
|
201
367
|
# and a verdict of ❌ on a healthy install. It stayed invisible while `command -v python3` was the test, because
|
|
202
368
|
# on Windows the Store stub answered yes and NOPY was never set. Three states, three branches.
|
|
203
369
|
if [ -n "$DENYSRC" ]; then
|
|
204
|
-
bad "the Agent tool is DENIED in
|
|
205
|
-
"remove the Agent/Task entry from permissions.deny, or accept that this project runs main-thread-only"
|
|
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"
|
|
206
372
|
elif [ "${NOPY:-0}" != 1 ]; then
|
|
207
373
|
ok "delegation is enabled (the Agent tool is not denied)"
|
|
208
374
|
fi
|
|
@@ -271,10 +437,11 @@ $(awk '
|
|
|
271
437
|
}' $SCAN)
|
|
272
438
|
EOF
|
|
273
439
|
if [ -n "$STALE" ]; then
|
|
440
|
+
BAD_TAIL="$STALE"
|
|
274
441
|
bad "CLAUDE.md (or a doc it references) names auto-delegated agent(s) that no installed agent matches — delegation to them silently fails" \
|
|
275
|
-
|
|
442
|
+
'rename each bare reference to its `crew-` id:'
|
|
276
443
|
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
|
|
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"
|
|
278
445
|
[ -z "$STALE$STALE_PULL" ] && ok "agent references resolve to installed agents (CLAUDE.md + referenced docs)"
|
|
279
446
|
fi
|
|
280
447
|
|
|
@@ -310,22 +477,22 @@ PREFLIGHT=".claude/eval/preflight.sh"
|
|
|
310
477
|
[ -f "$PREFLIGHT" ] || PREFLIGHT="$(dirname "$0")/preflight.sh"
|
|
311
478
|
PANEL_NOTE=""
|
|
312
479
|
if [ -d .claude/studio ] && ! bash "$PREFLIGHT" --has node 2>/dev/null; then
|
|
313
|
-
|
|
480
|
+
_mt " · panel needs Node 18+ — .claude/studio/ensure-node.sh --plan fetches one"; PANEL_NOTE="$_M"
|
|
314
481
|
fi
|
|
315
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.
|
|
316
483
|
for _v in $(compgen -e); do
|
|
317
484
|
case "$_v" in CSK_CORRECT_STACK) ;; CSK_*)
|
|
318
485
|
case "${_crew_legacy:-}" in
|
|
319
|
-
*" ${_v#CSK_} "*) warn "
|
|
320
|
-
*) warn "
|
|
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_}" ;;
|
|
321
488
|
esac ;;
|
|
322
489
|
esac
|
|
323
490
|
done
|
|
324
|
-
if [ "$FAIL" -eq 0 ]; then
|
|
491
|
+
if [ "$FAIL" -eq 0 ]; then _mt "DOCTOR: healthy ✅%s" "$PANEL_NOTE"; echo "$_M"
|
|
325
492
|
# Healthy verdict: the star line, once per kit version — the marker is shared with the installers, so the
|
|
326
493
|
# doctor run that /crew-update makes right after an update stays quiet. Text/URL/silence: lib/star.sh.
|
|
327
494
|
[ -f "$(dirname "$0")/lib/star.sh" ] && bash "$(dirname "$0")/lib/star.sh" --once .
|
|
328
|
-
else
|
|
495
|
+
else _mt "DOCTOR: %s issue(s) ❌ — apply the fixes above%s" "$FAIL" "$PANEL_NOTE"; echo "$_M"; fi
|
|
329
496
|
|
|
330
497
|
# 8b) The shell matcher. Claude Code's hooks reference is explicit: inspect shell commands with
|
|
331
498
|
# `Bash|PowerShell`, because wherever the PowerShell tool is enabled it IS the shell — and it is on by
|
|
@@ -336,8 +503,9 @@ if [ -f .claude/settings.json ]; then
|
|
|
336
503
|
if grep -q '"matcher"[[:space:]]*:[[:space:]]*"[^"]*PowerShell' .claude/settings.json; then
|
|
337
504
|
ok "shell gates watch both Bash and PowerShell"
|
|
338
505
|
elif grep -q '"matcher"[[:space:]]*:[[:space:]]*"Bash"' .claude/settings.json; then
|
|
506
|
+
BAD_TAIL=' "Bash|PowerShell"'
|
|
339
507
|
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
|
|
508
|
+
"update Crewforth (npx crewforth update), or set the PreToolUse matcher to"
|
|
341
509
|
fi
|
|
342
510
|
fi
|
|
343
511
|
# 9) The auto-mode classifier. Since 2026-08-14 auto mode is the default permission mode on Pro/Max/Team, so a
|
|
@@ -349,8 +517,8 @@ if [ -x .claude/skills/automode-policy/scripts/check.sh ] || [ -f .claude/skills
|
|
|
349
517
|
AMOUT="$(bash .claude/skills/automode-policy/scripts/check.sh 2>&1)"; AMRC=$?
|
|
350
518
|
case "$AMRC" in
|
|
351
519
|
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
|
|
353
|
-
"restore it in ~/.claude/settings.json; see .claude/skills/automode-policy/SKILL.md" ;;
|
|
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"' ;;
|
|
354
522
|
3) skip "auto-mode classifier config: Crewforth rules absent (measured not to enforce — see the skill)" ;;
|
|
355
523
|
# Same vocabulary as the other three branches on purpose. It used to read "auto-mode policy check
|
|
356
524
|
# skipped", and the suite's assertion — written on a machine that HAS the claude CLI — never saw this
|
|
@@ -364,11 +532,17 @@ fi
|
|
|
364
532
|
# 10) Gate activity. The suite proves the gates CAN fire; this reports whether anything actually tripped them.
|
|
365
533
|
# Recording is on by default (rule names only, never the command), so "no log" here means no gate has
|
|
366
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).
|
|
367
537
|
if [ -f .claude/eval/gate-report.sh ]; then
|
|
368
|
-
GOUT="$(bash .claude/eval/gate-report.sh 2>/dev/null)"; GRC=$?
|
|
538
|
+
GOUT="$(bash .claude/eval/gate-report.sh --json 2>/dev/null)"; GRC=$?
|
|
369
539
|
case "$GRC" in
|
|
370
|
-
0)
|
|
371
|
-
|
|
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 ;;
|
|
372
546
|
3) skip "gate activity NOT MEASURED — nowhere to record (see /crew-gates)" ;;
|
|
373
547
|
*) skip "gate activity unreadable (see /crew-gates)" ;;
|
|
374
548
|
esac
|
|
@@ -387,10 +561,10 @@ echo
|
|
|
387
561
|
# sitting beside this script for the case where doctor is run straight out of the Crewforth source.
|
|
388
562
|
[ -f "$PREFLIGHT" ] && bash "$PREFLIGHT"
|
|
389
563
|
|
|
390
|
-
|
|
564
|
+
_mt "Readiness (advisory — does not affect the verdict above):"; echo "$_M"
|
|
391
565
|
RDY=0; RTOT=0
|
|
392
|
-
rdy(){ RTOT=$((RTOT+1)); RDY=$((RDY+1)); echo " ✅ $
|
|
393
|
-
gap(){ RTOT=$((RTOT+1)); echo " ➖ $
|
|
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"; }
|
|
394
568
|
|
|
395
569
|
# R1) Is the CLAUDE.md project section filled in, or still the shipped template? An unfilled section means every
|
|
396
570
|
# agent works stack-blind — it is the single most common way a correct install still underperforms.
|
|
@@ -424,7 +598,7 @@ if [ -f "$MAN" ]; then
|
|
|
424
598
|
s="${d%/}"; s="${s##*/}"
|
|
425
599
|
case "$NL$MANTXT$NL" in *"${NL}skills/$s$NL"*) ;; *) OWN=$((OWN+1)) ;; esac
|
|
426
600
|
done
|
|
427
|
-
[ "$OWN" -gt 0 ] && rdy "
|
|
601
|
+
[ "$OWN" -gt 0 ] && rdy "%s project-specific skill(s) alongside Crewforth's" "$OWN" \
|
|
428
602
|
|| gap "no project-specific skill — only Crewforth's generic ones are installed" \
|
|
429
603
|
"put the domain 'how's in .claude/skills/ (format: .claude/AGENT_TEMPLATE.md)"
|
|
430
604
|
else
|
|
@@ -455,11 +629,11 @@ if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -f CLAUDE.md ]; then
|
|
|
455
629
|
if [ -n "$MT" ]; then
|
|
456
630
|
CH="$(git rev-list --count HEAD --since="@$MT" -- . ':(exclude).claude' 2>/dev/null | tr -cd '0-9')"
|
|
457
631
|
CH="${CH:-0}"
|
|
458
|
-
[ "$CH" -le "$MAXC" ] && rdy "CLAUDE.md is current (
|
|
459
|
-
|| gap "CLAUDE.md is stale —
|
|
460
|
-
"re-read it against the code and update Stack / Project skills (the claude-md-improver flow)"
|
|
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"
|
|
461
635
|
else skip "freshness signal skipped (cannot read CLAUDE.md mtime on this platform)"; fi
|
|
462
636
|
fi
|
|
463
637
|
|
|
464
|
-
[ "$RTOT" -gt 0 ] &&
|
|
638
|
+
[ "$RTOT" -gt 0 ] && { _mt " → readiness %s/%s" "$RDY" "$RTOT"; echo "$_M"; }
|
|
465
639
|
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# The size of the skill listing Claude Code puts into context, counted the way Claude Code counts it — the one
|
|
2
|
+
# method doctor.sh and smoke-test.sh share, so there is one number and not two.
|
|
3
|
+
#
|
|
4
|
+
# LC_ALL=C awk -f skill-listing.awk .claude/skills/*/SKILL.md -> "<chars> <skills> <unmeasured>"
|
|
5
|
+
# LC_ALL=C awk -v per=1 -f skill-listing.awk … -> one "<name>\t<chars>" row per skill, then the line above
|
|
6
|
+
#
|
|
7
|
+
# Measured against Claude Code's own counter (the "Skill listing over budget: N skills, C chars" debug warning, read
|
|
8
|
+
# with an isolated config and bundled skills off, so only these files are listed):
|
|
9
|
+
# - a skill costs len(name) + len(description) + 4; skills are joined by one more character each
|
|
10
|
+
# fixtures: name "aa" + "bbb" -> 9 · two skills (aa/bbb, cc/dddd) -> 20 · "aa" + 40 chars -> 46
|
|
11
|
+
# - a block description (`description: |`) counts its lines with nothing between them: the line breaks cost 0
|
|
12
|
+
# 46 shipped skills one by one: every multi-line description read (lines - 1) under "joined by a space"
|
|
13
|
+
# - a skill with `disable-model-invocation: true` is not listed at all
|
|
14
|
+
# - characters, not bytes: an em dash is one (UTF-8 continuation bytes are not counted)
|
|
15
|
+
# the 46 skills a fresh install lists: 9063 — the same figure Claude Code printed for them together.
|
|
16
|
+
# `when_to_use` and a folded (`>`) description were not measured; they are reported as "unmeasured" so a caller can
|
|
17
|
+
# refuse to trust the total rather than guess.
|
|
18
|
+
function chars(s, t) { t = s; gsub(/[\200-\277]/, "", t); return length(t) }
|
|
19
|
+
function flush() {
|
|
20
|
+
if (open && !dmi && name != "") {
|
|
21
|
+
n++; c = chars(name) + dlen + 4; total += c
|
|
22
|
+
if (per) printf "%s\t%d\n", name, c
|
|
23
|
+
}
|
|
24
|
+
open = 0
|
|
25
|
+
}
|
|
26
|
+
FNR == 1 { flush(); open = 1; fm = 0; name = ""; dlen = 0; blk = 0; dmi = 0 }
|
|
27
|
+
fm >= 2 { next }
|
|
28
|
+
/^---[[:space:]]*$/ { fm++; blk = 0; next }
|
|
29
|
+
fm != 1 { next }
|
|
30
|
+
blk && /^[[:space:]]+[^[:space:]]/ { l = $0; sub(/^[[:space:]]+/, "", l); sub(/[[:space:]]+$/, "", l); dlen += chars(l); next }
|
|
31
|
+
blk && /^[[:space:]]*$/ { next }
|
|
32
|
+
{ blk = 0 }
|
|
33
|
+
/^name:/ { v = $0; sub(/^name:[[:space:]]*/, "", v); sub(/[[:space:]]+$/, "", v); name = v; next }
|
|
34
|
+
/^description:[[:space:]]*\|[-+]?[[:space:]]*$/ { blk = 1; next }
|
|
35
|
+
/^description:[[:space:]]*>/ { blk = 1; unmeasured++; next }
|
|
36
|
+
/^description:/ {
|
|
37
|
+
v = $0; sub(/^description:[[:space:]]*/, "", v); sub(/[[:space:]]+$/, "", v)
|
|
38
|
+
if (v ~ /^".*"$/ || v ~ /^'.*'$/) v = substr(v, 2, length(v) - 2)
|
|
39
|
+
dlen = chars(v); next
|
|
40
|
+
}
|
|
41
|
+
/^disable-model-invocation:[[:space:]]*true[[:space:]]*$/ { dmi = 1; next }
|
|
42
|
+
/^when_to_use:/ { unmeasured++; next }
|
|
43
|
+
END { flush(); printf "%d %d %d\n", total + (n > 0 ? n - 1 : 0), n, unmeasured + 0 }
|