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/smoke-test.sh
CHANGED
|
@@ -1575,6 +1575,25 @@ if [ "$IS_KIT" = 1 ]; then
|
|
|
1575
1575
|
grep -q 'N_AG="$(count_installed' "$KR/start.sh" 2>/dev/null \
|
|
1576
1576
|
&& pass "start.sh derives its summary counts from the payload" \
|
|
1577
1577
|
|| fail "start.sh no longer derives its summary counts from the payload"
|
|
1578
|
+
# ...and the skills/commands it prints are the README's, not the directory count: the RC-1 rehearsal printed
|
|
1579
|
+
# "51 skills" beside a README that says 39 skills and 10 commands. count_showcase is lifted out of start.sh and
|
|
1580
|
+
# driven on a three-entry fixture (a skill, a command, an experimental one) and on the real payload, whose answer
|
|
1581
|
+
# must equal the counts the README gates above use.
|
|
1582
|
+
_csf="$(sed -n '/^count_showcase() {/,/^}/p' "$KR/start.sh")"
|
|
1583
|
+
if [ -z "$_csf" ]; then fail "start.sh has no count_showcase — the install prints a directory count again"
|
|
1584
|
+
else
|
|
1585
|
+
_csd="$(mktemp -d)"; mkdir -p "$_csd/a" "$_csd/b" "$_csd/c"
|
|
1586
|
+
printf -- '---\nname: a\ndescription: x\n---\n' > "$_csd/a/SKILL.md"
|
|
1587
|
+
printf -- '---\nname: b\ndescription: x\nmetadata:\n kind: command\n---\n' > "$_csd/b/SKILL.md"
|
|
1588
|
+
printf -- '---\nname: c\ndescription: x\nmetadata:\n kind: command\n experimental: true\n---\n' > "$_csd/c/SKILL.md"
|
|
1589
|
+
_csx="$(bash -c "$_csf"'; count_showcase "$1"; echo "$N_SKL $N_CMD"' _ "$_csd")"
|
|
1590
|
+
_cshown="$(bash -c "$_csf"'; count_showcase "$1"; echo "$N_SKL $N_CMD"' _ "$SKILLS")"
|
|
1591
|
+
rm -rf "$_csd"
|
|
1592
|
+
if [ "$_csx" != "1 1" ]; then fail "count_showcase miscounts its fixture: '$_csx', want '1 1' (the experimental entry counts as neither)"
|
|
1593
|
+
elif _nsk=0; for _sf in "$SKILLS"/*/SKILL.md; do _sn="${_sf%/SKILL.md}"; _sn="${_sn##*/}"; is_cmd "$_sn" || is_exp "$_sn" || _nsk=$((_nsk+1)); done
|
|
1594
|
+
[ "$_cshown" != "$_nsk $NCMD_SHOWN" ]; then fail "start.sh would print '$_cshown' skills/commands; the README gates count $_nsk and $NCMD_SHOWN"
|
|
1595
|
+
else pass "start.sh prints the README's counts ($_cshown skills/commands; fixture 1 1 with the experimental one left out)"; fi
|
|
1596
|
+
fi
|
|
1578
1597
|
# Crewforth installs through npx, the Claude Code plugin or the release archive; the Homebrew channel was
|
|
1579
1598
|
# removed. A line that tells a reader to `brew install` Crewforth, or names the old tap, points at a formula
|
|
1580
1599
|
# nobody maintains. `brew install node` and other tools stay: those install something else. The CHANGELOG is
|
|
@@ -2081,7 +2100,7 @@ sec "== 6f) always-on token budget =="
|
|
|
2081
2100
|
# for that cost, and a gate rather than a reminder — a verbose new description fails the suite instead of
|
|
2082
2101
|
# quietly taxing every future session. Budgets sit just above the current sizes: raising one is allowed, but
|
|
2083
2102
|
# only as a deliberate edit here.
|
|
2084
|
-
BUDGET_DISC=
|
|
2103
|
+
BUDGET_DISC=13741 # RC-1: 13711 → 13741 (+30): "Reply in the user's language even when a skill's text is English" — a Turkish session answered /crew-review in English, because every skill body it had just read was English; the rule is stated nowhere else. evals/cases/reply-language measures it. 5R.3: 13712 → 13711 (the old-name phrases rewritten, net −1). 5d.2 prompt audit: tightened to the measured size (13719 → 13712: format-to-content style line, one reload
|
|
2085
2104
|
# answer, the orphaned background-warning line removed). Before that: 3.0 rename (suffix → crew- prefix): +23 B (23 occurrences), not content — measured 13696 → 13719.
|
|
2086
2105
|
# DISCIPLINE.md (the discipline half of CLAUDE.md); before 3.0 the ceiling was 13700, currently 13601. (2026-09-18, a second
|
|
2087
2106
|
# +100 B on top of the raise below, and the whole of it went into ONE sentence of §4.6: a commit
|
|
@@ -2178,53 +2197,17 @@ BUDGET_AGENTS=5596 # 5d.2: tightened to the measured sum (was 5800 with 204 B
|
|
|
2178
2197
|
# +crew-performance-expert (~426B) — security, privacy and tests each had an independent
|
|
2179
2198
|
# reviewer and performance was the one quality axis where the author audited their own
|
|
2180
2199
|
# work. Bought at ~110 tokens per session; the alternative was leaving that gap open.)
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
# description: one added clause trips this gate, and that is the ratchet working, not a bug.
|
|
2193
|
-
# Raising the ceiling needs the same thing every bump here needed — a written reason for what
|
|
2194
|
-
# the bytes buy. (2.6.x: +843 B — ten descriptions gained a
|
|
2195
|
-
# "Use when …" sentence. The field's job is to say WHEN to reach for the skill; a description
|
|
2196
|
-
# that only says what its author knows is matched by nothing, and inside Crewforth that was
|
|
2197
|
-
# invisible because route-hint.sh and the trigger map do the routing. Outside the harness —
|
|
2198
|
-
# a skill copied into another project, another client, a bare session — the routing is gone
|
|
2199
|
-
# and the description is all there is. STATED HONESTLY: this bump does NOT fix the small-window
|
|
2200
|
-
# case. The listing budget is 1% of the context window, so a 200k model allows ~2,000 B and
|
|
2201
|
-
# Crewforth is far past that with or without these ten sentences. What changed is that the
|
|
2202
|
-
# remedy is now targetable: `eval/utilization.sh` reports which skills nothing in a project
|
|
2203
|
-
# actually reached, which is the list `skillOverrides: name-only` needs and never had. The
|
|
2204
|
-
# next skill that wants room takes it from a description, not from another bump.)
|
|
2205
|
-
# Was 8700 / 8678. (2.5.0: +307 B for `automode-policy` — about
|
|
2206
|
-
# half of it the allowed-tools grant that lets the skill run its own verifier without a
|
|
2207
|
-
# prompt, not prose. The listing is what every session pays for; the next skill that wants
|
|
2208
|
-
# room takes it from a description, not from another bump.) Was 8380 / 8371 — nine bytes
|
|
2209
|
-
# of headroom, so the ceiling was already the binding constraint, not this skill. (2.3.0: +187 B for `teamboard`. It is the only
|
|
2210
|
-
# skill whose absence from the LISTING is silently unsafe rather than merely unhelpful: a
|
|
2211
|
-
# skill that fails to match usually means the model does the work itself, but this one
|
|
2212
|
-
# failing to match means two people do the SAME work, on separate machines, discovering it
|
|
2213
|
-
# at merge time. Trimmed to one sentence and the words people actually type — claim, item,
|
|
2214
|
-
# team board.) RATCHETED DOWN in 1.11.0 from 12,350: the
|
|
2215
|
-
# `Trigger phrases:` lines moved out of every skill's `description` into the body. This is not
|
|
2216
|
-
# cosmetic. Claude Code loads a LISTING of skill names+descriptions every session and the
|
|
2217
|
-
# budget is 1% of the context window; over it, descriptions are truncated or dropped outright,
|
|
2218
|
-
# "which can strip the keywords Claude needs to match your request" (official skills docs). The
|
|
2219
|
-
# Crewforth's listing was 11,372 chars against a 10,000 budget on a 1M window — overflowing on every
|
|
2220
|
-
# model, and 5.7x over on a 200k one. Now 7,208. A kit whose own skills push its descriptions
|
|
2221
|
-
# out of the listing is a kit that stops matching, which is exactly the symptom users report.
|
|
2222
|
-
# (1.8.0: +confidence-check (~359B), Crewforth's
|
|
2223
|
-
# only gate that fires BEFORE implementation — every other one reviews code that already
|
|
2224
|
-
# exists, and none catch correct code that should never have been written; and
|
|
2225
|
-
# +dependency-upgrade (~444B), split from dependency-audit because one reports and the
|
|
2226
|
-
# other rewrites lockfiles: different risk, different DoD, and an audit you can run on any
|
|
2227
|
-
# branch stops being safe the moment it can also apply things)
|
|
2200
|
+
BUDGET_SKILL_LISTING=9063 # the skill listing as Claude Code counts it (eval/lib/skill-listing.awk — the count doctor.sh
|
|
2201
|
+
# reports too), in CHARACTERS: 46 model-invocable skills, 9063, the figure Claude Code's own
|
|
2202
|
+
# "Skill listing over budget" warning printed for a fresh install. It replaced a byte sum of
|
|
2203
|
+
# the frontmatter that no tool used (10249), so doctor and this gate now read one number.
|
|
2204
|
+
# The ceiling is "does not grow", not a target. 8,000 was weighed and rejected: that is the
|
|
2205
|
+
# whole listing's budget on a 200k model at the default fraction, and Claude Code's own
|
|
2206
|
+
# skills take ~5,900 of it (v2.1.282), so no Crewforth-only figure could make the whole list
|
|
2207
|
+
# fit. The full install raises the fraction to 0.04; the plugin cannot, so doctor and
|
|
2208
|
+
# /crew-doctor tell a plugin user on a 200k model the one line to add and what it costs.
|
|
2209
|
+
# Shorter descriptions wait for an eval that measures whether Claude still picks the skill
|
|
2210
|
+
# from them — trimming blind trades a number for matching nobody measured.
|
|
2228
2211
|
# A SKILL.md's `metadata:` block is Crewforth's own catalogue data (`kind: command`); Claude Code does not act on it
|
|
2229
2212
|
# and it never enters the listing, so it is not counted. Nothing else in a frontmatter is skipped.
|
|
2230
2213
|
fm_bytes(){ awk '/^---$/{c++; next} c==1 { if ($0 ~ /^metadata:/) { m=1; next } if (m && $0 ~ /^[ \t]/) next; m=0; print }' "$1" 2>/dev/null | wc -c | tr -d ' '; }
|
|
@@ -2242,15 +2225,33 @@ elif [ -f "$ROOT/DISCIPLINE.md" ]; then
|
|
|
2242
2225
|
DB="$(wc -c < "$ROOT/DISCIPLINE.md" | tr -d ' ')"; DBCR="$(tr -dc '\r' < "$ROOT/DISCIPLINE.md" | wc -c | tr -d ' ')"
|
|
2243
2226
|
else DB=0; DBCR=0; fi
|
|
2244
2227
|
AB=0; for f in "$AGENTS"/*.md; do [ -e "$f" ] && AB=$((AB + $(fm_bytes "$f"))); done
|
|
2245
|
-
#
|
|
2246
|
-
#
|
|
2247
|
-
#
|
|
2248
|
-
#
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2228
|
+
# The skill listing is counted by eval/lib/skill-listing.awk, the file doctor.sh uses: a skill with
|
|
2229
|
+
# `disable-model-invocation: true` is not listed, every other one costs its name and description as Claude Code counts
|
|
2230
|
+
# them. The counter is pinned first on the fixtures Claude Code itself was measured on, so a drift in the awk (or in an
|
|
2231
|
+
# awk: BSD, mawk and Git Bash gawk all run this) reads as a broken meter, not as a budget.
|
|
2232
|
+
SLA="$ROOT/eval/lib/skill-listing.awk"; [ -f "$SLA" ] || SLA="$SGR/kit/eval/lib/skill-listing.awk"
|
|
2233
|
+
if [ ! -f "$SLA" ]; then fail "eval/lib/skill-listing.awk is missing — the skill listing cannot be counted"; SL=0
|
|
2234
|
+
else
|
|
2235
|
+
_slf="$(mktemp -d)"; mkdir -p "$_slf/a" "$_slf/b" "$_slf/c" "$_slf/d"
|
|
2236
|
+
printf -- '---\nname: aa\ndescription: bbb\n---\n' > "$_slf/a/SKILL.md"
|
|
2237
|
+
printf -- '---\nname: cc\ndescription: dddd\n---\n' > "$_slf/b/SKILL.md"
|
|
2238
|
+
printf -- '---\nname: ee\ndescription: |\n one\n two\n---\n' > "$_slf/c/SKILL.md"
|
|
2239
|
+
printf -- '---\nname: ff\ndescription: gg\ndisable-model-invocation: true\n---\n' > "$_slf/d/SKILL.md"
|
|
2240
|
+
_c1="$(LC_ALL=C awk -f "$SLA" "$_slf/a/SKILL.md")"; _c2="$(LC_ALL=C awk -f "$SLA" "$_slf/a/SKILL.md" "$_slf/b/SKILL.md")"
|
|
2241
|
+
_c3="$(LC_ALL=C awk -f "$SLA" "$_slf/c/SKILL.md")"; _c4="$(LC_ALL=C awk -f "$SLA" "$_slf/a/SKILL.md" "$_slf/d/SKILL.md")"
|
|
2242
|
+
_c5="$(printf -- '---\nname: hh\ndescription: a\342\200\224b\n---\n' > "$_slf/e.md"; LC_ALL=C awk -f "$SLA" "$_slf/e.md")"
|
|
2243
|
+
rm -rf "$_slf"
|
|
2244
|
+
# 9 and 20 are Claude Code's own readings; ee = 2 + len("onetwo") + 4 (a line break costs nothing, as measured on
|
|
2245
|
+
# 46 shipped skills); ff is not listed; an em dash is one character (hh = 2 + 3 + 4).
|
|
2246
|
+
if [ "$_c1" = "9 1 0" ] && [ "$_c2" = "20 2 0" ] && [ "$_c3" = "12 1 0" ] && [ "$_c4" = "9 1 0" ] && [ "$_c5" = "9 1 0" ]; then
|
|
2247
|
+
pass "skill-listing.awk reproduces Claude Code's counter on its fixtures (9 · 20 · block lines · unlisted · em dash)"
|
|
2248
|
+
else fail "skill-listing.awk drifted from Claude Code's counter: got '$_c1' '$_c2' '$_c3' '$_c4' '$_c5', want '9 1 0' '20 2 0' '12 1 0' '9 1 0' '9 1 0'"; fi
|
|
2249
|
+
read -r SL SLN SLU <<EOF_SL
|
|
2250
|
+
$(LC_ALL=C awk -f "$SLA" "$SKILLS"/*/SKILL.md)
|
|
2251
|
+
EOF_SL
|
|
2252
|
+
[ "${SLU:-0}" = 0 ] && pass "no skill uses a listing shape the count was not measured on (when_to_use, folded description)" \
|
|
2253
|
+
|| fail "$SLU skill(s) use when_to_use or a folded description — measure that shape against Claude Code before trusting the listing count"
|
|
2254
|
+
fi
|
|
2254
2255
|
# The budget GATES Crewforth's payload (kit repo, IS_KIT). In an INSTALLED project the user's own agents/skills —
|
|
2255
2256
|
# including the ones adopt imports from a taken-over agent — legitimately add to the always-on cost (their choice),
|
|
2256
2257
|
# so there we REPORT the numbers instead of failing the suite.
|
|
@@ -2263,20 +2264,21 @@ done
|
|
|
2263
2264
|
#
|
|
2264
2265
|
# $4 is the number of carriage returns in the same text the budget was measured on, so the two numbers describe
|
|
2265
2266
|
# the same bytes. A real overrun still says "over budget"; only a CRLF one is renamed.
|
|
2266
|
-
bud(){ # $1 name $2 measured $3 budget $4 (optional) carriage returns in the measured text
|
|
2267
|
-
|
|
2267
|
+
bud(){ # $1 name $2 measured $3 budget $4 (optional) carriage returns in the measured text $5 (optional) unit
|
|
2268
|
+
local u="${5:-bytes}"
|
|
2269
|
+
if [ "$2" -le "$3" ]; then pass "$1 within budget ($2 ≤ $3 $u)"
|
|
2268
2270
|
elif [ "$IS_KIT" = 1 ]; then
|
|
2269
2271
|
local cr="${4:-0}"
|
|
2270
2272
|
if [ "$cr" -gt 0 ] && [ $(( $2 - cr )) -le "$3" ]; then
|
|
2271
2273
|
fail "$1 over budget ONLY because this checkout is CRLF: $2 > $3 bytes, and $cr of those bytes are carriage returns ($(( $2 - cr )) with LF endings, which is within budget). Re-check out the file rather than editing the budget."
|
|
2272
2274
|
else
|
|
2273
|
-
fail "$1 over budget: $2 > $3
|
|
2275
|
+
fail "$1 over budget: $2 > $3 $u"
|
|
2274
2276
|
fi
|
|
2275
|
-
else pass "$1 $2
|
|
2277
|
+
else pass "$1 $2 $u (over Crewforth's $3 baseline — your project's own additions, not gated in an install)"; fi; }
|
|
2276
2278
|
bud "discipline" "$DB" "$BUDGET_DISC" "$DBCR"
|
|
2277
2279
|
bud "agent descriptions" "$AB" "$BUDGET_AGENTS"
|
|
2278
|
-
bud "skill
|
|
2279
|
-
echo " always-on
|
|
2280
|
+
bud "skill listing" "$SL" "$BUDGET_SKILL_LISTING" 0 chars
|
|
2281
|
+
echo " always-on: discipline + agents $((DB+AB)) bytes (budget $((BUDGET_DISC+BUDGET_AGENTS))) · skill listing $SL chars for ${SLN:-0} skills (budget $BUDGET_SKILL_LISTING)"
|
|
2280
2282
|
# The diagnosis is pinned, not only written, on CRLF copies of this very file: the carriage-return count must cover
|
|
2281
2283
|
# exactly the measured lines, a copy whose text is exactly at the budget with LF endings must be named as CRLF with its
|
|
2282
2284
|
# figures, and one a byte past the budget must still read "over budget". All three failed while the count came from
|
|
@@ -4026,6 +4028,30 @@ case "$o" in *'"additionalContext"'*SESSION_STATE*) pass "handover present -> in
|
|
|
4026
4028
|
*) fail "session-rehydrate did not inject a pointer when SESSION_STATE.md exists" ;; esac
|
|
4027
4029
|
if [ -n "$JSONQ" ]; then printf '%s' "$o" | json_ok && pass "rehydrate output is valid JSON ($JSONQ)" || fail "rehydrate output is not valid JSON";
|
|
4028
4030
|
else skip tool "the rehydrate output JSON-validity check (no working jq)"; fi
|
|
4031
|
+
# The install language (RC-2 rehearsal: a bare /crew-doctor in a Turkish install came back in English). The hook
|
|
4032
|
+
# now runs on startup too and tells the model the install language; the handover is still offered only on a
|
|
4033
|
+
# boundary. Six states, each read by what the hook actually printed: a Turkish install gets the language line on
|
|
4034
|
+
# startup and on a boundary; an English install gets none; startup never offers the handover, a boundary does.
|
|
4035
|
+
# JSON validity is asked of Crewforth's own reader, so it holds on a machine without jq.
|
|
4036
|
+
_rh(){ printf '{"hook_event_name":"SessionStart","cwd":"%s","source":"%s"}' "$RHD" "$1" | CLAUDE_PROJECT_DIR= bash "$HOOKS/session-rehydrate.sh" 2>/dev/null; }
|
|
4037
|
+
_rk(){ case "$1" in *'handover'*) printf 'H' ;; esac; case "$1" in *'installed in Turkish'*) printf 'L' ;; esac; }
|
|
4038
|
+
mkdir -p "$RHD/.claude"
|
|
4039
|
+
printf 'lang=tr\n' > "$RHD/.claude/kit.conf"; _o1="$(_rh startup)"; _o2="$(_rh compact)"
|
|
4040
|
+
printf 'lang=en\n' > "$RHD/.claude/kit.conf"; _o3="$(_rh startup)"; _o4="$(_rh compact)"
|
|
4041
|
+
rm -f "$RHD/docs/SESSION_STATE.md"; _o5="$(_rh startup)"
|
|
4042
|
+
printf 'lang=tr\n' > "$RHD/.claude/kit.conf"; _o6="$(_rh clear)"
|
|
4043
|
+
_got="$(_rk "$_o1")/$(_rk "$_o2")/$(_rk "$_o3")/$(_rk "$_o4")/$(_rk "$_o5")/$(_rk "$_o6")"
|
|
4044
|
+
[ "$_got" = "L/HL//H//L" ] && pass "SessionStart names a Turkish install's language on startup and on a boundary; English adds nothing; startup never offers the handover (6 states)" \
|
|
4045
|
+
|| fail "session-rehydrate language/handover states read '$_got', want 'L/HL//H//L' (tr startup · tr compact+handover · en startup · en compact+handover · en startup no handover · tr clear no handover)"
|
|
4046
|
+
_rj=0; for _o in "$_o1" "$_o2" "$_o4" "$_o6"; do printf '%s' "$_o" > "$RHD/o.json"; awk -v op=validate -f "$ROOT/eval/lib/settings-json.awk" "$RHD/o.json" 2>/dev/null || _rj=$((_rj+1)); done
|
|
4047
|
+
[ "$_rj" = 0 ] && pass "each language/handover output is valid JSON (Crewforth's reader, 4 of 4)" || fail "$_rj session-rehydrate output(s) are not valid JSON"
|
|
4048
|
+
# Asked of the entry that carries session-rehydrate, not of the file: skill-trust's entry has the same matcher.
|
|
4049
|
+
_rw(){ _n="$(awk -v op=len -v path=hooks.SessionStart -f "$ROOT/eval/lib/settings-json.awk" "$1" 2>/dev/null)"; _i=0
|
|
4050
|
+
while [ "$_i" -lt "${_n:-0}" ]; do _e="$(awk -v op=get -v path="hooks.SessionStart.$_i" -f "$ROOT/eval/lib/settings-json.awk" "$1" 2>/dev/null)"
|
|
4051
|
+
case "$_e" in *session-rehydrate*) case "$_e" in *'"matcher"'*startup*) return 0 ;; esac ;; esac; _i=$((_i+1)); done; return 1; }
|
|
4052
|
+
sed 's/"startup|resume|clear|compact|fork"/"compact|clear|resume|fork"/' "$ROOT/settings.json" > "$RHD/s-twin.json"
|
|
4053
|
+
if _rw "$ROOT/settings.json" && ! _rw "$RHD/s-twin.json"; then pass "settings.json runs session-rehydrate on startup too; a copy wired as before (no startup) is caught"
|
|
4054
|
+
else fail "session-rehydrate is not wired on startup (or the check cannot tell) — a Turkish install's first session would not be told its language"; fi
|
|
4029
4055
|
rm -rf "$RHD"
|
|
4030
4056
|
# board-sync builds its JSON with one awk escaper on every machine. The no-jq escaper it replaced handled only the
|
|
4031
4057
|
# quote, the backslash and the newline, so a TAB in an item title produced JSON the CLI cannot parse and the board
|
|
@@ -4148,7 +4174,7 @@ mkdir -p "$DCR/.claude/skills/only-mine" "$DCR/.claude/hooks"
|
|
|
4148
4174
|
cp -R "$SKILLS/handoff" "$DCR/.claude/skills/" 2>/dev/null
|
|
4149
4175
|
printf '# x\n' > "$DCR/.claude/skills/only-mine/SKILL.md"
|
|
4150
4176
|
cp "$HOOKS"/*.sh "$DCR/.claude/hooks/" 2>/dev/null; chmod +x "$DCR/.claude/hooks/"*.sh 2>/dev/null
|
|
4151
|
-
_own(){ ( cd "$DCR" && bash "$ROOT/eval/doctor.sh" 2>&1 | grep -oE '[0-9]+ project-specific skill' | head -1 | cut -d' ' -f1 ); }
|
|
4177
|
+
_own(){ ( cd "$DCR" && CREW_LANG=en bash "$ROOT/eval/doctor.sh" 2>&1 | grep -oE '[0-9]+ project-specific skill' | head -1 | cut -d' ' -f1 ); }
|
|
4152
4178
|
printf 'skills/handoff\n' > "$DCR/.claude/kit-manifest.txt"; _lf="$(_own)"
|
|
4153
4179
|
printf 'skills/handoff\r\n' > "$DCR/.claude/kit-manifest.txt"; _crlf="$(_own)"
|
|
4154
4180
|
[ -n "$_lf" ] && [ "$_lf" = "$_crlf" ] \
|
|
@@ -4191,7 +4217,7 @@ bash "$ROOT/eval/doctor.sh" "$DOC" >/dev/null 2>&1 \
|
|
|
4191
4217
|
&& pass "doctor: CLAUDE.md with the @import -> exit 0" \
|
|
4192
4218
|
|| fail "doctor flagged a CLAUDE.md that DOES import the discipline"
|
|
4193
4219
|
# Readiness is ADVISORY: a bare project trips every readiness signal, and the verdict must stay exit 0.
|
|
4194
|
-
DOUT="$(bash "$ROOT/eval/doctor.sh" "$DOC" 2>&1)"; DRC=$?
|
|
4220
|
+
DOUT="$(CREW_LANG=en bash "$ROOT/eval/doctor.sh" "$DOC" 2>&1)"; DRC=$?
|
|
4195
4221
|
[ "$DRC" -eq 0 ] && pass "doctor: readiness gaps do NOT change the verdict (advisory)" || fail "readiness gaps changed doctor's exit code — it must stay a statement about the install"
|
|
4196
4222
|
case "$DOUT" in *"Readiness (advisory"*) pass "doctor prints the readiness block" ;; *) fail "doctor readiness block missing" ;; esac
|
|
4197
4223
|
case "$DOUT" in *"➖"*) pass "readiness flags gaps on a bare project (devcontainer/MCP/manifest absent)" ;; *) fail "readiness found no gap on a bare project — the signals are not firing" ;; esac
|
|
@@ -5468,13 +5494,13 @@ if [ -f "$GR" ]; then
|
|
|
5468
5494
|
DTMP="$(mktemp -d)"; mkdir -p "$DTMP/.claude"
|
|
5469
5495
|
for d in eval hooks skills agents; do [ -d "$ROOT/$d" ] && cp -R "$ROOT/$d" "$DTMP/.claude/$d"; done
|
|
5470
5496
|
cp "$ROOT/settings.json" "$DTMP/.claude/settings.json" 2>/dev/null
|
|
5471
|
-
DOUT="$(cd "$DTMP" && bash .claude/eval/doctor.sh 2>"$DTMP/err")"
|
|
5497
|
+
DOUT="$(cd "$DTMP" && CREW_LANG=en bash .claude/eval/doctor.sh 2>"$DTMP/err")"
|
|
5472
5498
|
# An install from before 2.5.0 keeps the old `Bash`-only matcher, and nothing in the session looks wrong
|
|
5473
5499
|
# while every PowerShell command walks past §4.5. doctor has to SAY so, so this drives the downgrade.
|
|
5474
5500
|
case "$DOUT" in *"watch both Bash and PowerShell"*) pass "doctor confirms the shell matcher covers PowerShell" ;;
|
|
5475
5501
|
*) fail "doctor did not report on the shell matcher" ;; esac
|
|
5476
5502
|
sed 's/"Bash|PowerShell"/"Bash"/' "$DTMP/.claude/settings.json" > "$DTMP/s.tmp" && mv "$DTMP/s.tmp" "$DTMP/.claude/settings.json"
|
|
5477
|
-
DOUT2="$(cd "$DTMP" && bash .claude/eval/doctor.sh 2>/dev/null)"
|
|
5503
|
+
DOUT2="$(cd "$DTMP" && CREW_LANG=en bash .claude/eval/doctor.sh 2>/dev/null)"
|
|
5478
5504
|
case "$DOUT2" in *"watch only Bash"*) pass "doctor flags a pre-2.5.0 Bash-only matcher as a failure" ;;
|
|
5479
5505
|
*) fail "doctor stayed quiet on a Bash-only matcher — the gap is invisible to an upgrader" ;; esac
|
|
5480
5506
|
grep -q 'command not found' "$DTMP/err" && fail "doctor.sh calls a helper before it is defined (see stderr)" \
|
|
@@ -5487,9 +5513,38 @@ if [ -f "$GR" ]; then
|
|
|
5487
5513
|
# that has it, so it only ever exercised one of doctor's four branches; CI has no CLI, took the fourth, and
|
|
5488
5514
|
# failed on a wording difference. Running it both ways is what makes the assertion about doctor rather than
|
|
5489
5515
|
# about the machine the suite happens to run on.
|
|
5490
|
-
DOUT3="$(cd "$DTMP" && PATH=/usr/bin:/bin bash .claude/eval/doctor.sh 2>/dev/null)"
|
|
5516
|
+
DOUT3="$(cd "$DTMP" && CREW_LANG=en PATH=/usr/bin:/bin bash .claude/eval/doctor.sh 2>/dev/null)"
|
|
5491
5517
|
case "$DOUT3" in *"auto-mode classifier"*) pass "doctor reports the auto-mode line with no claude CLI present" ;;
|
|
5492
5518
|
*) fail "doctor went silent on auto-mode when the claude CLI is absent" ;; esac
|
|
5519
|
+
# (f) doctor speaks the install's language. A Turkish install ran a Turkish installer and then got an English
|
|
5520
|
+
# doctor (RC-1 rehearsal); the language now comes from the `lang=` start.sh/adopt.sh record in kit.conf. Four
|
|
5521
|
+
# claims, driven on this fixture: the record picks the language with no variable set; no line the doctor
|
|
5522
|
+
# printed fell back to English (CREW_I18N_MISS names each key that had no row); every identifier the English
|
|
5523
|
+
# run printed — a path, a command, a settings key — is still there, untranslated, in the Turkish run; and an
|
|
5524
|
+
# English record gets English. The must-fail twin plants a line with no row in a copy of the doctor and
|
|
5525
|
+
# needs the miss list to name it, so an empty list means "all translated", not "the collector is dead".
|
|
5526
|
+
_dl(){ printf 'lang=%s\n' "$1" > "$DTMP/.claude/kit.conf"; }
|
|
5527
|
+
_dl tr; : > "$DTMP/miss"
|
|
5528
|
+
DTR="$(cd "$DTMP" && env -u CREW_LANG CREW_I18N_MISS="$DTMP/miss" bash .claude/eval/doctor.sh 2>/dev/null)"
|
|
5529
|
+
_dl en; DEN="$(cd "$DTMP" && env -u CREW_LANG bash .claude/eval/doctor.sh 2>/dev/null)"
|
|
5530
|
+
case "$DTR" in *"kurulum denetimi"*) pass "doctor speaks Turkish when the install recorded lang=tr (no variable set)" ;;
|
|
5531
|
+
*) fail "doctor ignored lang=tr in kit.conf — a Turkish install gets an English doctor" ;; esac
|
|
5532
|
+
case "$DEN" in *"install doctor"*) pass "doctor speaks English when the install recorded lang=en" ;;
|
|
5533
|
+
*) fail "doctor did not speak English for lang=en" ;; esac
|
|
5534
|
+
[ ! -s "$DTMP/miss" ] && pass "every line the Turkish doctor printed has a translation (miss list empty)" \
|
|
5535
|
+
|| fail "the Turkish doctor fell back to English for: $(tr '\n' '|' < "$DTMP/miss")"
|
|
5536
|
+
_idm=""; _idn=0
|
|
5537
|
+
for _id in $(printf '%s\n' "$DEN" | grep -oE '\.claude/[A-Za-z0-9_./*-]+|settings\.json|core\.hooksPath|/crew-[a-z-]+|npx crewforth [a-z]+|skillListingBudgetFraction|PreToolUse|UserPromptSubmit|SessionStart' | sort -u); do
|
|
5538
|
+
_idn=$((_idn + 1)); case "$DTR" in *"$_id"*) ;; *) _idm="$_idm $_id" ;; esac
|
|
5539
|
+
done
|
|
5540
|
+
if [ "$_idn" -lt 5 ]; then fail "identifier check read only $_idn identifier(s) from the English doctor — the extractor is broken, not the doctor"
|
|
5541
|
+
elif [ -z "$_idm" ]; then pass "the Turkish doctor keeps all $_idn identifiers of the English one untranslated (paths, commands, keys)"
|
|
5542
|
+
else fail "the Turkish doctor lost or translated identifier(s):$_idm"; fi
|
|
5543
|
+
sed 's/^_mt "== Crewforth — install doctor =="; echo "$_M"$/&; ok "planted line with no translation"/' "$DTMP/.claude/eval/doctor.sh" > "$DTMP/.claude/eval/doctor-twin.sh"
|
|
5544
|
+
_dl tr; : > "$DTMP/miss2"
|
|
5545
|
+
( cd "$DTMP" && env -u CREW_LANG CREW_I18N_MISS="$DTMP/miss2" bash .claude/eval/doctor-twin.sh >/dev/null 2>&1 )
|
|
5546
|
+
grep -qx 'planted line with no translation' "$DTMP/miss2" && pass "twin: a planted untranslated line is named by the miss list" \
|
|
5547
|
+
|| fail "twin: the miss list did not name a planted untranslated line — the empty list above proves nothing"
|
|
5493
5548
|
rm -rf "$DTMP" "$GTMP"
|
|
5494
5549
|
else
|
|
5495
5550
|
fail "eval/gate-report.sh missing from the payload"
|
|
@@ -6237,10 +6292,12 @@ if [ "$IS_KIT" = 1 ]; then
|
|
|
6237
6292
|
command -v node >/dev/null 2>&1 && node "$KR/bin/cli.js" --help; } > "$_kd/out.txt" 2>&1
|
|
6238
6293
|
_kc="$(kw_lines "$_kd/out.txt" | sed "s|^$_kd/||")"; _kn="$(grep -c . "$_kd/out.txt")"
|
|
6239
6294
|
# Twins. Must fail: a start.sh whose table says "full kit" again, and output that says "installing Crewforth.".
|
|
6240
|
-
# Must pass: output that only names paths — .claude/kit.conf,
|
|
6295
|
+
# Must pass: output that only names paths — .claude/kit.conf, the folder in the archive's own error, kit-manifest.txt,
|
|
6296
|
+
# drizzle-kit, KIT_X. ("kit/ deleted" used to sit here as a path; it was the last line of every install, and the
|
|
6297
|
+
# RC-1 rehearsal read it as the old word. The closing lines are held to it separately below.)
|
|
6241
6298
|
sed 's/"full install") s=/"full kit") s=/' "$KR/start.sh" > "$_kd/mut-start.sh"
|
|
6242
6299
|
_ok=kit; printf 'Done: installing the %s.\nKurulum: %sin dosyaları hazır\n' "$_ok" "$_ok" > "$_kd/bad.txt"
|
|
6243
|
-
printf 'wrote .claude/kit.conf\
|
|
6300
|
+
printf 'wrote .claude/kit.conf\nERROR: kit/ not found\nsee .claude/kit-manifest.txt and drizzle-kit status\nKIT_X=1\n' > "$_kd/good.txt"
|
|
6244
6301
|
if [ -n "$_ka$_kb$_kc" ]; then fail "a printed line still says kit — say Crewforth:
|
|
6245
6302
|
$(printf '%s\n%s\n%s\n' "$_ka" "$_kb" "$_kc" | grep . | head -n 6 | sed "s|$KR/||; s|^| |")"
|
|
6246
6303
|
elif [ "$_kn" -lt 30 ]; then fail "FIXTURE: the captured help and preflight output is only $_kn line(s) — the run broke, not the wording"
|
|
@@ -6248,6 +6305,16 @@ $(printf '%s\n%s\n%s\n' "$_ka" "$_kb" "$_kc" | grep . | head -n 6 | sed "s|$KR/|
|
|
|
6248
6305
|
elif [ "$(kw_lines "$_kd/bad.txt" | grep -c .)" != 2 ]; then fail "Crewforth-word matcher missed a planted old-name word (English or Turkish) in the output"
|
|
6249
6306
|
elif [ -n "$(kw_lines "$_kd/good.txt")" ]; then fail "Crewforth-word matcher flagged a path or identifier: $(kw_lines "$_kd/good.txt" | head -n 2 | tr '\n' ' ')"
|
|
6250
6307
|
else pass "no printed line says kit: terminal-only scripts, the installers' tables/echo/usage and cli --help, plus $_kn lines of real help and preflight output (EN+TR); a reverted table entry and planted words are caught, paths are not"; fi
|
|
6308
|
+
# The closing line of an install is the one line every user reads. It may name no kit at all, not even as a path:
|
|
6309
|
+
# the key and its Turkish value are both read, and a planted "kit/ deleted" in a copy must be caught.
|
|
6310
|
+
_dn(){ grep -hE '"Done\.|'"'"'Done\.' "$@" 2>/dev/null | grep -iE '(^|[^a-z_.-])kit([^a-z_-]|$)'; }
|
|
6311
|
+
if [ ! -f "$SGR/start.sh" ]; then :
|
|
6312
|
+
elif [ -n "$(_dn "$SGR/start.sh" "$SGR/adopt.sh")" ]; then fail "an installer's closing line still names kit: $(_dn "$SGR/start.sh" "$SGR/adopt.sh" | head -n 1 | cut -c1-120)"
|
|
6313
|
+
else
|
|
6314
|
+
sed 's/the installer files are removed\./kit\/ deleted./' "$SGR/start.sh" > "$_kd/done-twin.sh"
|
|
6315
|
+
[ -n "$(_dn "$_kd/done-twin.sh")" ] && pass "the installers' closing lines name no kit (EN key and TR value); a planted 'kit/ deleted' is caught" \
|
|
6316
|
+
|| fail "twin: the closing-line check missed a planted 'kit/ deleted' — it reads nothing"
|
|
6317
|
+
fi
|
|
6251
6318
|
rm -rf "$_kd"
|
|
6252
6319
|
else
|
|
6253
6320
|
skip scope "printed-text wording check skipped (installed project — the installers live in the source repository)"
|
package/kit/hooks/guard-bash.sh
CHANGED
|
@@ -1390,7 +1390,9 @@ if git_has "$CMD" 'commit|push'; then
|
|
|
1390
1390
|
# §4.4 branch guard: committing straight onto main/master is not blocked (a fresh project legitimately
|
|
1391
1391
|
# lives on main), but it is surfaced in the approval prompt so the user can send it to a branch instead.
|
|
1392
1392
|
BRANCH_WARN=""
|
|
1393
|
-
|
|
1393
|
+
# symbolic-ref names the branch even before its first commit, where rev-parse answers "HEAD" and the very
|
|
1394
|
+
# first commit onto main went by without this line. A detached HEAD reads empty: not the default branch.
|
|
1395
|
+
case "$(git symbolic-ref --short -q HEAD 2>/dev/null)" in
|
|
1394
1396
|
main|master) BRANCH_WARN="⚠️ This commits DIRECTLY to the default branch. Prefer a feature branch unless you meant to.
|
|
1395
1397
|
|
|
1396
1398
|
" ;;
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
# - SessionStart is the right event: its stdout / hookSpecificOutput.additionalContext is injected into the
|
|
9
9
|
# model's context (docs: "Re-inject context after compaction"). PreCompact is NOT used — it can only block
|
|
10
10
|
# compaction, not add instructions to the summary, and blocking is not what we want.
|
|
11
|
-
# -
|
|
12
|
-
# startup fires on every unrelated session open and would keep surfacing a stale
|
|
11
|
+
# - The HANDOVER is offered on compact|clear|resume|fork only, NOT on startup: those are explicit "continue where I
|
|
12
|
+
# left off" boundaries, while startup fires on every unrelated session open and would keep surfacing a stale
|
|
13
|
+
# handover — that would nag. The hook itself also runs on startup, for the language line below; it reads the
|
|
14
|
+
# payload's `source` to tell the two apart (no `source` -> treated as a boundary, the pre-3.0 payload shape).
|
|
15
|
+
# - THE LANGUAGE LINE. A project installed in Turkish (`lang=tr` in .claude/kit.conf) is told so at every session
|
|
16
|
+
# start: reply in Turkish unless the user writes in another language. The RC-2 rehearsal typed a bare
|
|
17
|
+
# `/crew-doctor` in a Turkish install: the doctor printed Turkish and the summary came back in English, because a
|
|
18
|
+
# bare command has no word to take the language from. An English install adds nothing (English is the default).
|
|
13
19
|
# - CLAUDE.md itself reloads on /compact and /clear on its own, so this hook does NOT re-inject the discipline —
|
|
14
20
|
# only the session-specific state the reload can't recover.
|
|
15
21
|
# - Fails OPEN and SILENT: no handover file, no output. It never blocks the session (always exits 0).
|
|
@@ -28,13 +34,26 @@ fi
|
|
|
28
34
|
# Undo the JSON escaping first (`\\`), then fold any lone separator. Both are no-ops on a POSIX path.
|
|
29
35
|
ROOT="${ROOT//\\\\//}"; ROOT="${ROOT//\\//}"
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
SRC=""
|
|
38
|
+
case "$IN" in *'"source"'*) SRC="${IN#*\"source\"}"; SRC="${SRC#*\"}"; SRC="${SRC%%\"*}" ;; esac
|
|
39
|
+
|
|
40
|
+
LANGMSG=""
|
|
41
|
+
if [ -f "$ROOT/.claude/kit.conf" ]; then
|
|
42
|
+
while IFS= read -r _l || [ -n "$_l" ]; do
|
|
43
|
+
case "${_l%$'\r'}" in lang=tr) LANGMSG="This project was installed in Turkish (lang=tr in .claude/kit.conf). Reply in Turkish unless the user writes in another language, including after a bare command such as /crew-doctor. Commands, paths, file names and identifiers stay as written." ;; esac
|
|
44
|
+
done < "$ROOT/.claude/kit.conf"
|
|
45
|
+
fi
|
|
33
46
|
|
|
47
|
+
MSG=""
|
|
48
|
+
STATE="$ROOT/docs/SESSION_STATE.md"
|
|
49
|
+
if [ "$SRC" != startup ] && [ -s "$STATE" ]; then
|
|
34
50
|
MSG="A session handover from before this context boundary exists at docs/SESSION_STATE.md. If the user's request continues that work, read it first — it holds the in-progress task state, open decisions, and the intended next step — and do not restart from scratch. If the request is about something else, do not bring it up."
|
|
51
|
+
fi
|
|
52
|
+
[ -n "$LANGMSG" ] && MSG="${MSG:+$MSG }$LANGMSG"
|
|
53
|
+
[ -n "$MSG" ] || exit 0 # no handover to offer and nothing to say about language -> stay silent
|
|
35
54
|
|
|
36
55
|
# hookSpecificOutput.additionalContext is the documented channel that injects text into the model's context.
|
|
37
|
-
# ONE PATH, no jq: MSG is
|
|
56
|
+
# ONE PATH, no jq: MSG is built from fixed constants with no quote, backslash, control character or newline, so it needs no
|
|
38
57
|
# escaping and every machine emits the same bytes. If you ever make MSG dynamic, escape it the way board-sync.sh
|
|
39
58
|
# does (jq-identical, measured) — a raw quote or tab here makes JSON the CLI silently drops.
|
|
40
59
|
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$MSG"
|
package/kit/hooks/skill-trust.sh
CHANGED
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
# never fail to start because of this.
|
|
22
22
|
set -uo pipefail
|
|
23
23
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
24
|
-
MODE=""
|
|
25
|
-
|
|
24
|
+
MODE=""; ONE=""
|
|
25
|
+
# --trust-one <skills/name | agents/name.md>: record ONE component as accepted, and nothing else. The updater uses it
|
|
26
|
+
# for the skill it turned into a project skill itself (cqrs-aop-module, which Crewforth shipped until 3.0): flagging
|
|
27
|
+
# the updater's own move as unvetted was the first thing an updated session said (RC-1 rehearsal). --trust would
|
|
28
|
+
# have accepted every other foreign component too, including ones the user never looked at.
|
|
29
|
+
case "${1:-}" in --trust) MODE=trust ;; --trust-one) MODE=one; ONE="${2:-}" ;; esac
|
|
26
30
|
|
|
27
31
|
IN=""
|
|
28
|
-
[ ! -t 0 ] && [ "$MODE"
|
|
32
|
+
[ ! -t 0 ] && [ -z "$MODE" ] && IN="$(cat 2>/dev/null || true)"
|
|
29
33
|
ROOT="${CLAUDE_PROJECT_DIR:-}"
|
|
30
34
|
[ -n "$ROOT" ] || ROOT="$(printf '%s' "$IN" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)"
|
|
31
35
|
[ -n "$ROOT" ] || ROOT="$PWD"
|
|
@@ -58,6 +62,15 @@ digest(){
|
|
|
58
62
|
else cksum "$1" 2>/dev/null | tr -s ' ' | cut -d' ' -f1,2 | tr ' ' '-'; fi
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
if [ "$MODE" = one ]; then
|
|
66
|
+
case "$ONE" in skills/*/*|agents/*/*) exit 1 ;; skills/?*) p="$CL/$ONE/SKILL.md" ;; agents/?*.md) p="$CL/$ONE" ;; *) exit 1 ;; esac
|
|
67
|
+
[ -f "$p" ] || exit 1
|
|
68
|
+
dg="$(digest "$p")"; [ -n "$dg" ] || exit 1
|
|
69
|
+
[ -f "$TRUST" ] || printf '# Components reviewed and accepted by the user. Regenerate with: bash skill-trust.sh --trust\n' > "$TRUST" 2>/dev/null || exit 1
|
|
70
|
+
grep -qxF "$dg $ONE" "$TRUST" 2>/dev/null || printf '%s %s\n' "$dg" "$ONE" >> "$TRUST"
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
61
74
|
# The component list: a skill is its SKILL.md, an agent is its file. Only those Crewforth does NOT ship —
|
|
62
75
|
# without a manifest we cannot tell kit-owned from project-owned, so we stay silent rather than guess.
|
|
63
76
|
[ -f "$MAN" ] || exit 0
|
package/kit/settings.json
CHANGED
|
@@ -27,10 +27,20 @@ Verify Crewforth is actually *active* in this project (not just present on disk)
|
|
|
27
27
|
it prints once per Crewforth version, and it is meant for the user, not for you.
|
|
28
28
|
|
|
29
29
|
If `.claude/eval/doctor.sh` doesn't exist, this is not a full (start.sh / adopt.sh) install — Crewforth is likely
|
|
30
|
-
running as a **plugin**, whose hooks are managed by Claude Code itself; there's nothing for the doctor to check
|
|
30
|
+
running as a **plugin**, whose hooks are managed by Claude Code itself; there's nothing for the doctor to check,
|
|
31
|
+
with one exception: the skill listing, which a plugin cannot size for itself (its settings cannot set
|
|
32
|
+
`skillListingBudgetFraction`). Count it the way the full install's doctor does:
|
|
33
|
+
`LC_ALL=C awk -f "${CLAUDE_PLUGIN_ROOT}/eval/lib/skill-listing.awk" "${CLAUDE_PLUGIN_ROOT}"/skills/*/SKILL.md`
|
|
34
|
+
(prints characters, then skills). At the default fraction a 200,000-token model has 8,000 characters for the whole
|
|
35
|
+
listing, and Claude Code's own skills take about 5,900 of them (measured on v2.1.282). If Crewforth's number plus
|
|
36
|
+
5,900 is over 8,000, tell the user plainly: on such a model the least-used skills lose their descriptions and stop
|
|
37
|
+
being picked; one line in `~/.claude/settings.json` fixes it — `"skillListingBudgetFraction": 0.04`; the cost is
|
|
38
|
+
that the listing is then sent whole every turn, about (that total ÷ 4) tokens, (tokens ÷ 2,000)% of a 200k window.
|
|
39
|
+
It is their call — show the line and the cost, do not edit their settings. On a 1,000,000-token model the default
|
|
40
|
+
already fits.
|
|
31
41
|
|
|
32
42
|
**The eval scripts are installer-only, by decision.** `eval/` — `doctor.sh`, `smoke-test.sh`, `routing-eval.sh`,
|
|
33
43
|
`scan-skill.sh`, `utilization.sh` — ships with `start.sh` and `adopt.sh` and NOT with the plugin edition. The eval
|
|
34
44
|
scripts are developer instruments: they inspect an installation from outside it, and the plugin edition has no
|
|
35
|
-
installation to inspect. Say this plainly when someone asks why `/crew-doctor` reports
|
|
45
|
+
installation to inspect. Say this plainly when someone asks why `/crew-doctor` reports only the listing on a plugin install,
|
|
36
46
|
rather than treating it as a defect. The Studio panel ships in both editions (`/crew-studio`).
|
|
@@ -14,10 +14,13 @@ Run the change set through the review trio (read-only). **The audits go out in p
|
|
|
14
14
|
- @agent-crew-privacy-agent (`privacy-compliance`) — **when the diff touches personal data**: legal basis,
|
|
15
15
|
minimisation, retention, transfer.
|
|
16
16
|
2. (if SonarQube is in use) **sonarqube-check** — 0/0/0/0 gate (language-agnostic).
|
|
17
|
-
3. **Last,
|
|
18
|
-
health"
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
3. **Last, once 1-2 leave no blocker:** @agent-crew-review-agent (crew-code-review) — "does it improve overall
|
|
18
|
+
code health": the plan (what changed, the risks), then findings with a severity and a category. "Clean" means
|
|
19
|
+
what crew-code-review means by it: **no critical or high finding open**. A medium or low audit finding does not
|
|
20
|
+
hold the reviewer back; hand it over in the reviewer's prompt and keep it in the report. A critical or high one
|
|
21
|
+
stops here: report it, leave the fix to its owner, and run the reviewer on the fixed diff. It is the closing
|
|
22
|
+
reviewer in every writing agent's Coordination ("at closure, report findings to crew-review-agent"), so it
|
|
23
|
+
reads a diff the audits have already cleared of blockers — not the other way round.
|
|
21
24
|
|
|
22
25
|
Each agent returns a **short summary** to the main thread; raw output goes to `docs/` if needed. Do NOT modify code;
|
|
23
26
|
collect findings in severity order, and leave the fix to the relevant expert.
|
|
@@ -43,7 +43,8 @@ Skipping one of these is how a component ships half-installed:
|
|
|
43
43
|
`node packaging/gen-diagrams.mjs` writes `assets/`, and smoke fails while any of them is stale.
|
|
44
44
|
4. **Plugin edition** *(Crewforth repository only)*. `bash packaging/build-plugin.sh` — in the same commit, or the
|
|
45
45
|
release stops at the sync gate.
|
|
46
|
-
5. **Budget.** A new skill moves `
|
|
46
|
+
5. **Budget.** A new skill moves `BUDGET_SKILL_LISTING` (the skill listing in characters, counted like Claude Code
|
|
47
|
+
counts it; `/crew-doctor` reports the same number). Raise it in the same commit with the justification comment the
|
|
47
48
|
file's convention requires. Never raise it to make a red gate green without saying why.
|
|
48
49
|
|
|
49
50
|
## 4. Prove it — run all four, in this order
|
package/package.json
CHANGED
package/start.sh
CHANGED
|
@@ -123,16 +123,16 @@ _mt() { # $1 = English text (the key); further args fill %s; result in _M
|
|
|
123
123
|
"no effect:") s='etkisi yok:' ;;
|
|
124
124
|
"Installing:") s='Kuruluyor:' ;;
|
|
125
125
|
"Tip: open Claude Code and run /crew-doctor — it checks the install is wired (hooks executable, core.hooksPath set, discipline imported) and scores the project's readiness. CLAUDE.md loads the discipline every session.") s="İpucu: Claude Code'u açıp /crew-doctor çalıştırın — kurulumun eksiksiz bağlandığını denetler (hook'lar çalıştırılabilir mi, core.hooksPath ayarlı mı, disiplin import edilmiş mi) ve projenin ne kadar hazır olduğunu puanlar. Disiplin, CLAUDE.md sayesinde her oturumda yüklenir." ;;
|
|
126
|
-
"%s agents, %s skills installed.") s="%s ajan ve %s
|
|
126
|
+
"%s agents, %s skills and %s commands installed.") s="%s ajan, %s skill ve %s komut kuruldu." ;;
|
|
127
127
|
".claude/DISCIPLINE.md written — owned by Crewforth; an update overwrites it, so keep your own rules out of it.") s=".claude/DISCIPLINE.md yazıldı. Bu dosya Crewforth'a ait ve her güncellemede yeniden yazılır; kendi kurallarınızı buraya eklemeyin." ;;
|
|
128
128
|
"./CLAUDE.md created — EDIT the project section.") s='./CLAUDE.md oluşturuldu — proje bölümünü sizin DOLDURMANIZ gerekiyor.' ;;
|
|
129
129
|
"trace scan: core.hooksPath -> .claude/hooks (§4.1/§4.2 commit gate active)") s='iz taraması: core.hooksPath -> .claude/hooks (§4.1/§4.2 commit kapısı açık)' ;;
|
|
130
|
-
"Done. ./.claude + ./CLAUDE.md ready (full install);
|
|
130
|
+
"Done. ./.claude + ./CLAUDE.md ready (full install); the installer files are removed.") s='Tamamlandı. ./.claude ve ./CLAUDE.md hazır (tam kurulum); kurulum dosyaları silindi.' ;;
|
|
131
131
|
"Next: 1) fill in the CLAUDE.md project section 2) open Claude Code at the repo root") s="Sıradaki adımlar: 1) CLAUDE.md'deki proje bölümünü doldurun 2) Claude Code'u deponun kökünde açın" ;;
|
|
132
132
|
"Note: if Claude Code is ALREADY running here, restart it — CLAUDE.md and the discipline load at session start.") s='Not: Claude Code bu klasörde ZATEN açıksa yeniden başlatın — CLAUDE.md ve disiplin oturum açılırken yüklenir.' ;;
|
|
133
133
|
"Panel: /crew-studio opens the Studio panel from this project (or: node .claude/studio/server/index.js --open).") s='Panel: /crew-studio komutu Studio panelini bu projeden açar (alternatif: node .claude/studio/server/index.js --open).' ;;
|
|
134
134
|
"— backend + web + mobile (RN/Expo), every agent and skill") s="— backend, web ve mobil (RN/Expo); tüm ajanlar ve skill'ler" ;;
|
|
135
|
-
"%s agents · %s skills will be installed") s='%s ajan · %s skill' ;;
|
|
135
|
+
"%s agents · %s skills · %s commands will be installed") s='%s ajan · %s skill · %s komut' ;;
|
|
136
136
|
"(shared: .claude/ and CLAUDE.md stay committable)") s="(paylaşımlı: .claude/ ve CLAUDE.md commit'lenebilir kalır)" ;;
|
|
137
137
|
"(default — pass --shared to commit .claude/ and CLAUDE.md)") s="(varsayılan — .claude/ ve CLAUDE.md'yi commit'lemek için --shared verin)" ;;
|
|
138
138
|
"no") s='hayır' ;;
|
|
@@ -548,13 +548,28 @@ count_installed() { # $1=glob -> count to install
|
|
|
548
548
|
printf '%s' "$n"
|
|
549
549
|
}
|
|
550
550
|
N_AG="$(count_installed "$SRC/agents/*.md")"
|
|
551
|
-
|
|
551
|
+
# Skills and commands the way the README and crewforth.com count them, so the install does not print a third number:
|
|
552
|
+
# a skill whose metadata says `kind: command` is a command, one marked `experimental: true` is neither (it is
|
|
553
|
+
# installed and works when typed, but nothing advertises it). Read from the frontmatter with builtins only.
|
|
554
|
+
count_showcase() { # $1 = a skills directory -> N_SKL N_CMD
|
|
555
|
+
N_SKL=0; N_CMD=0; local f l k e d
|
|
556
|
+
for f in "$1"/*/SKILL.md; do
|
|
557
|
+
[ -f "$f" ] || continue; k=0; e=0; d=0
|
|
558
|
+
while IFS= read -r l || [ -n "$l" ]; do
|
|
559
|
+
l="${l%$'\r'}"
|
|
560
|
+
case "$l" in ---) d=$((d+1)); [ "$d" -ge 2 ] && break ;; " kind: command") k=1 ;; " experimental: true") e=1 ;; esac
|
|
561
|
+
done < "$f"
|
|
562
|
+
[ "$e" = 1 ] && continue
|
|
563
|
+
if [ "$k" = 1 ]; then N_CMD=$((N_CMD+1)); else N_SKL=$((N_SKL+1)); fi
|
|
564
|
+
done
|
|
565
|
+
}
|
|
566
|
+
count_showcase "$SRC/skills"
|
|
552
567
|
|
|
553
568
|
h1 '[2/2] Summary · see what will be installed before you confirm'
|
|
554
569
|
echo
|
|
555
570
|
_mt 'full install'; _a="$_M"; _mt '— backend + web + mobile (RN/Expo), every agent and skill'
|
|
556
571
|
row 'Scope' "${B}${_a}${D} ${_M}${R}"
|
|
557
|
-
_mt '%s agents · %s skills will be installed' "${MG}${B}${N_AG}${R}" "${MG}${B}${
|
|
572
|
+
_mt '%s agents · %s skills · %s commands will be installed' "${MG}${B}${N_AG}${R}" "${MG}${B}${N_SKL}${R}" "${MG}${B}${N_CMD}${R}"
|
|
558
573
|
row 'Included' "$_M"
|
|
559
574
|
_mt 'stack-agnostic — the stack comes from CLAUDE.md ## Stack or the repo (backend-architecture)'
|
|
560
575
|
row 'Backend pattern' "$_M"
|
|
@@ -607,7 +622,8 @@ cp -R "$SRC/eval/." .claude/eval/ 2>/dev/null || true
|
|
|
607
622
|
# outside the payload, because kit/ ships whole and 104 KB of test code
|
|
608
623
|
# would travel through all four channels only to be removed on arrival.
|
|
609
624
|
cp -R "$SRC/studio/." .claude/studio/ 2>/dev/null || true
|
|
610
|
-
|
|
625
|
+
count_showcase .claude/skills
|
|
626
|
+
{ _mt "%s agents, %s skills and %s commands installed." "$(ls .claude/agents/*.md 2>/dev/null | wc -l | tr -d ' ')" "$N_SKL" "$N_CMD"; echo " ${_M}"; }
|
|
611
627
|
[ -f "$SRC/settings.json" ] && cp "$SRC/settings.json" .claude/settings.json
|
|
612
628
|
[ -f "$HERE/VERSION" ] && cp "$HERE/VERSION" .claude/VERSION # make the kit version trackable in the installed project
|
|
613
629
|
# Glob form so every shipped hook/eval is made executable — including ones added later (guard-write.sh,
|
|
@@ -721,7 +737,7 @@ else
|
|
|
721
737
|
fi
|
|
722
738
|
rm -rf "$SRC"
|
|
723
739
|
echo
|
|
724
|
-
{ _mt 'Done. ./.claude + ./CLAUDE.md ready (full install);
|
|
740
|
+
{ _mt 'Done. ./.claude + ./CLAUDE.md ready (full install); the installer files are removed.'; echo "== ${_M} =="; }
|
|
725
741
|
{ _mt 'Next: 1) fill in the CLAUDE.md project section 2) open Claude Code at the repo root'; echo "${_M}"; }
|
|
726
742
|
{ _mt 'Note: if Claude Code is ALREADY running here, restart it — CLAUDE.md and the discipline load at session start.'; echo "${_M}"; }
|
|
727
743
|
{ _mt "Tip: open Claude Code and run /crew-doctor — it checks the install is wired (hooks executable, core.hooksPath set, discipline imported) and scores the project's readiness. CLAUDE.md loads the discipline every session."; echo "${_M}"; }
|