crewforth 0.0.1 → 3.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +3784 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -7
  4. package/VERSION +1 -0
  5. package/adopt.sh +1670 -0
  6. package/bin/cli.js +497 -0
  7. package/kit/AGENT_TEMPLATE.md +92 -0
  8. package/kit/CLAUDE.md +228 -0
  9. package/kit/README.md +107 -0
  10. package/kit/agents/crew-backend-expert.md +95 -0
  11. package/kit/agents/crew-commit-agent.md +96 -0
  12. package/kit/agents/crew-database-expert.md +84 -0
  13. package/kit/agents/crew-devops-expert.md +82 -0
  14. package/kit/agents/crew-frontend-expert.md +85 -0
  15. package/kit/agents/crew-performance-expert.md +78 -0
  16. package/kit/agents/crew-planner.md +60 -0
  17. package/kit/agents/crew-privacy-agent.md +71 -0
  18. package/kit/agents/crew-review-agent.md +95 -0
  19. package/kit/agents/crew-security-expert.md +73 -0
  20. package/kit/agents/crew-session-manager.md +77 -0
  21. package/kit/agents/crew-test-expert.md +64 -0
  22. package/kit/eval/doctor.sh +465 -0
  23. package/kit/eval/gate-report.sh +137 -0
  24. package/kit/eval/golden-holdout.txt +53 -0
  25. package/kit/eval/golden-routing.txt +199 -0
  26. package/kit/eval/lib/crew-env.sh +14 -0
  27. package/kit/eval/lib/settings-json.awk +154 -0
  28. package/kit/eval/lib/star.sh +70 -0
  29. package/kit/eval/parser-conformance.sh +612 -0
  30. package/kit/eval/preflight.sh +186 -0
  31. package/kit/eval/routing-eval.sh +209 -0
  32. package/kit/eval/scan-skill.sh +162 -0
  33. package/kit/eval/smoke-test.sh +6693 -0
  34. package/kit/eval/update-guard.sh +81 -0
  35. package/kit/eval/utilization.sh +130 -0
  36. package/kit/hooks/board-sync.sh +106 -0
  37. package/kit/hooks/board.sh +962 -0
  38. package/kit/hooks/commit-msg +49 -0
  39. package/kit/hooks/context-usage.sh +353 -0
  40. package/kit/hooks/floor-blocklist.txt +124 -0
  41. package/kit/hooks/guard-bash.sh +1431 -0
  42. package/kit/hooks/guard-commit-scan.sh +511 -0
  43. package/kit/hooks/guard-write.sh +513 -0
  44. package/kit/hooks/pre-commit +577 -0
  45. package/kit/hooks/route-hint.sh +185 -0
  46. package/kit/hooks/secret-blocklist.txt +82 -0
  47. package/kit/hooks/session-guard.sh +149 -0
  48. package/kit/hooks/session-rehydrate.sh +41 -0
  49. package/kit/hooks/session-stats.sh +252 -0
  50. package/kit/hooks/session-update-check.sh +236 -0
  51. package/kit/hooks/skill-trust.sh +150 -0
  52. package/kit/hooks/trace-blocklist.txt +53 -0
  53. package/kit/settings.json +132 -0
  54. package/kit/skills/a11y/SKILL.md +44 -0
  55. package/kit/skills/adr/SKILL.md +74 -0
  56. package/kit/skills/api-design/SKILL.md +50 -0
  57. package/kit/skills/automode-policy/SKILL.md +91 -0
  58. package/kit/skills/automode-policy/references/policy.json +13 -0
  59. package/kit/skills/automode-policy/scripts/apply.sh +84 -0
  60. package/kit/skills/automode-policy/scripts/check.sh +102 -0
  61. package/kit/skills/backend-architecture/SKILL.md +103 -0
  62. package/kit/skills/brainstorm/SKILL.md +47 -0
  63. package/kit/skills/ci-pipeline/SKILL.md +38 -0
  64. package/kit/skills/commit-message/SKILL.md +82 -0
  65. package/kit/skills/commit-message/references/examples.md +8 -0
  66. package/kit/skills/confidence-check/SKILL.md +86 -0
  67. package/kit/skills/crew-board/SKILL.md +55 -0
  68. package/kit/skills/crew-brainstorm/SKILL.md +19 -0
  69. package/kit/skills/crew-code-review/SKILL.md +151 -0
  70. package/kit/skills/crew-code-review/references/history-search.md +19 -0
  71. package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
  72. package/kit/skills/crew-code-review/references/receiving.md +23 -0
  73. package/kit/skills/crew-doctor/SKILL.md +36 -0
  74. package/kit/skills/crew-gates/SKILL.md +25 -0
  75. package/kit/skills/crew-handoff/SKILL.md +14 -0
  76. package/kit/skills/crew-plan/SKILL.md +18 -0
  77. package/kit/skills/crew-review/SKILL.md +23 -0
  78. package/kit/skills/crew-ship/SKILL.md +19 -0
  79. package/kit/skills/crew-skill/SKILL.md +61 -0
  80. package/kit/skills/crew-studio/SKILL.md +129 -0
  81. package/kit/skills/crew-update/SKILL.md +43 -0
  82. package/kit/skills/db-migration/SKILL.md +120 -0
  83. package/kit/skills/db-migration/references/backup-restore.md +33 -0
  84. package/kit/skills/db-migration/references/raw-sql.md +30 -0
  85. package/kit/skills/db-migration/references/tool-matrix.md +35 -0
  86. package/kit/skills/dependency-audit/SKILL.md +61 -0
  87. package/kit/skills/dependency-upgrade/SKILL.md +82 -0
  88. package/kit/skills/deploy/SKILL.md +100 -0
  89. package/kit/skills/deploy/references/proxy-ssl.md +56 -0
  90. package/kit/skills/deploy/references/rollback.md +18 -0
  91. package/kit/skills/deploy/references/scripts.md +9 -0
  92. package/kit/skills/deploy/references/topology-a.md +128 -0
  93. package/kit/skills/deploy/references/topology-b.md +44 -0
  94. package/kit/skills/docs-writer/SKILL.md +53 -0
  95. package/kit/skills/docs-writer/references/coauthoring.md +34 -0
  96. package/kit/skills/eval-grader/SKILL.md +75 -0
  97. package/kit/skills/eval-grader/references/method.md +51 -0
  98. package/kit/skills/frontend/SKILL.md +56 -0
  99. package/kit/skills/frontend/references/verify-contract.md +41 -0
  100. package/kit/skills/frontend-design/SKILL.md +71 -0
  101. package/kit/skills/frontend-design/references/design-review.md +34 -0
  102. package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
  103. package/kit/skills/handoff/SKILL.md +71 -0
  104. package/kit/skills/handoff/references/cold-reader.md +51 -0
  105. package/kit/skills/i18n-integrity/SKILL.md +31 -0
  106. package/kit/skills/incident-runbook/SKILL.md +40 -0
  107. package/kit/skills/incident-runbook/references/postmortem.md +16 -0
  108. package/kit/skills/iterate/SKILL.md +40 -0
  109. package/kit/skills/mcp-builder/SKILL.md +54 -0
  110. package/kit/skills/mcp-builder/references/building.md +71 -0
  111. package/kit/skills/observability/SKILL.md +50 -0
  112. package/kit/skills/performance/SKILL.md +38 -0
  113. package/kit/skills/performance/references/bottlenecks.md +10 -0
  114. package/kit/skills/privacy-compliance/SKILL.md +75 -0
  115. package/kit/skills/red-team/SKILL.md +48 -0
  116. package/kit/skills/reflect/SKILL.md +57 -0
  117. package/kit/skills/release/SKILL.md +44 -0
  118. package/kit/skills/security-scan/SKILL.md +99 -0
  119. package/kit/skills/security-scan/references/ai-agents.md +107 -0
  120. package/kit/skills/security-scan/references/fronts.md +99 -0
  121. package/kit/skills/security-scan/references/prompting.md +23 -0
  122. package/kit/skills/security-scan/references/reporting.md +75 -0
  123. package/kit/skills/security-scan/references/verify.md +131 -0
  124. package/kit/skills/sonarqube-check/SKILL.md +108 -0
  125. package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
  126. package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
  127. package/kit/skills/spec-planning/SKILL.md +92 -0
  128. package/kit/skills/systematic-debugging/SKILL.md +57 -0
  129. package/kit/skills/systematic-debugging/references/techniques.md +44 -0
  130. package/kit/skills/teamboard/SKILL.md +88 -0
  131. package/kit/skills/teamboard/references/setup.md +37 -0
  132. package/kit/skills/testing/SKILL.md +49 -0
  133. package/kit/skills/testing/references/flaky-triage.md +51 -0
  134. package/kit/skills/threat-model/SKILL.md +57 -0
  135. package/kit/skills/threat-model/references/interview.md +51 -0
  136. package/kit/skills/threat-model/references/schema.md +42 -0
  137. package/kit/skills/token-budget/SKILL.md +51 -0
  138. package/kit/skills/trace-scan/SKILL.md +43 -0
  139. package/kit/skills/worktree/SKILL.md +40 -0
  140. package/kit/studio/README.md +266 -0
  141. package/kit/studio/ensure-node.sh +471 -0
  142. package/kit/studio/package.json +14 -0
  143. package/kit/studio/server/hooks/studio-gate.sh +169 -0
  144. package/kit/studio/server/index.js +902 -0
  145. package/kit/studio/server/lib/crew-env.js +11 -0
  146. package/kit/studio/server/lib/fleet.js +146 -0
  147. package/kit/studio/server/lib/graph.js +533 -0
  148. package/kit/studio/server/lib/instance.js +135 -0
  149. package/kit/studio/server/lib/kit-telemetry.js +182 -0
  150. package/kit/studio/server/lib/kit.js +158 -0
  151. package/kit/studio/server/lib/palette.js +118 -0
  152. package/kit/studio/server/lib/peers.js +72 -0
  153. package/kit/studio/server/lib/permissions.js +150 -0
  154. package/kit/studio/server/lib/projects.js +294 -0
  155. package/kit/studio/server/lib/roster.js +191 -0
  156. package/kit/studio/server/lib/session.js +309 -0
  157. package/kit/studio/server/lib/terminal.js +91 -0
  158. package/kit/studio/server/lib/transcript.js +100 -0
  159. package/kit/studio/web/app.js +1098 -0
  160. package/kit/studio/web/canvas.js +1179 -0
  161. package/kit/studio/web/chat.js +543 -0
  162. package/kit/studio/web/index.html +86 -0
  163. package/kit/studio/web/md.js +110 -0
  164. package/kit/studio/web/storage-migrate.js +23 -0
  165. package/kit/studio/web/style.css +1453 -0
  166. package/package.json +46 -8
  167. package/start.sh +748 -0
  168. package/bin/crewforth.js +0 -10
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env bash
2
+ # What did the gates actually DO? — reads CREW_GATE_LOG and reports it against the rule inventory.
3
+ #
4
+ # Crewforth's claim is that rules are enforced at the tool level rather than remembered. That claim is only worth
5
+ # what its evidence is worth, and until now the evidence was a green test suite: proof the gates CAN fire, never
6
+ # a record that they DID. This reads the other half.
7
+ #
8
+ # Two failure modes it is built around, both learned here the hard way:
9
+ # 1. "0 firings" and "never measured" leave identical artifacts. So a rule is reported as `not observed`, never
10
+ # as dead, and a missing log is reported as NOT MEASURED — never as zero.
11
+ # 2. A hand-maintained rule list drifts from the rules. So the inventory is DERIVED from the installed hooks
12
+ # on every run: a rule added to guard-bash.sh shows up here without anyone remembering to add it.
13
+ #
14
+ # Usage: gate-report.sh [--log <path>] [--json]
15
+ # Exit: 0 report produced · 3 no log to read (not an error: logging is opt-in) · 4 hooks not found
16
+ set -u
17
+ # The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
18
+ _crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
19
+ [ -f "$_crew_d/lib/crew-env.sh" ] && . "$_crew_d/lib/crew-env.sh"; unset _crew_d
20
+
21
+ LOG="${CREW_GATE_LOG:-}"; JSON=0
22
+ while [ $# -gt 0 ]; do case "$1" in
23
+ --log) LOG="${2:-}"; shift 2 ;;
24
+ --json) JSON=1; shift ;;
25
+ *) echo "gate-report.sh: unknown argument '$1'" >&2; exit 64 ;;
26
+ esac; done
27
+
28
+ HOOKS="./.claude/hooks"; [ -d "$HOOKS" ] || HOOKS="./hooks"
29
+ [ -f "$HOOKS/guard-bash.sh" ] || { echo "gate-report: guard-bash.sh not found (run from the project root)" >&2; exit 4; }
30
+
31
+ # Default log location, so `--log` is not required once logging is on.
32
+ [ -z "$LOG" ] && [ -f "./.claude/gate-log.tsv" ] && LOG="./.claude/gate-log.tsv"
33
+
34
+ TMP="${TMPDIR:-/tmp}/crew-gate-$$"; mkdir -p "$TMP"; trap 'rm -rf "$TMP"' EXIT
35
+
36
+ # Match on a KEY, display the full label. A rule label can carry a trailing parenthetical, and one of them
37
+ # interpolates a shell variable ("… that cannot prompt (${PERM_MODE:-unknown})"): the inventory reads the
38
+ # source, the log holds the expanded value, so the two never compare equal and the rule is reported as never
39
+ # observed forever — a false negative in the one direction that matters. Both sides drop the trailing (...).
40
+ key(){ printf '%s' "$1" | sed 's/[[:space:]]*([^()]*)[[:space:]]*$//'; }
41
+
42
+ # ---- inventory: derived from the hooks, one awk pass per file (no per-rule subprocess: Windows forks cost 62-135 ms)
43
+ awk '
44
+ # 23 rules of the form: … && block "RULE" "SECTION"
45
+ match($0, /&& *block +"[^"]*" +"[^"]*"/) {
46
+ s=substr($0,RSTART,RLENGTH); n=split(s,q,"\""); print q[4] "\t" q[2]; next
47
+ }
48
+ # direct calls: gatelog VERDICT SECTION "RULE"
49
+ /^[[:space:]]*gatelog +[A-Z]+ +[0-9.]+ +"/ {
50
+ n=split($0,q,"\""); split($0,f," "); print f[3] "\t" q[2]; next
51
+ }
52
+ ' "$HOOKS/guard-bash.sh" > "$TMP/inv"
53
+ # guard-write.sh emits its one line inline rather than through a helper, so read the label out of the printf
54
+ # arguments the same way — the earlier version skipped the very line it needed (it matched CREW_GATE_LOG first)
55
+ # and the rule silently never appeared in the inventory, which reads exactly like a rule that never fires.
56
+ # guard-write.sh emits its one line inline rather than through a helper, so read the label out of the printf
57
+ # arguments. Two traps here, both hit on the way: the line also mentions CREW_GATE_LOG (skipping on that name
58
+ # skipped the rule itself), and the FIRST quoted field on the line is `${CREW_GATE_LOG:-}`, not the label — so
59
+ # take the last quoted field that is not a variable reference.
60
+ [ -f "$HOOKS/guard-write.sh" ] && awk '
61
+ /printf .BLOCK\\t/ {
62
+ n=split($0,q,"\""); lab=""
63
+ for (i=2; i<=n; i+=2) if (q[i] !~ /^\$/ && q[i] != "") lab=q[i]
64
+ if (lab != "") print "4.5\t" lab
65
+ }' "$HOOKS/guard-write.sh" >> "$TMP/inv"
66
+
67
+ sort -u "$TMP/inv" -o "$TMP/inv"
68
+ NRULES=$(grep -c . "$TMP/inv" 2>/dev/null); NRULES=$(( ${NRULES:-0} + 0 ))
69
+
70
+ # ---- no log. Since 2.5.0 recording is ON by default, so the absence of a file means one of two DIFFERENT
71
+ # things and the report must not blur them:
72
+ # .claude/ exists -> recording had somewhere to go and nothing has tripped a gate yet. That is a real
73
+ # measurement of zero, and it is the healthy reading.
74
+ # .claude/ missing -> there was nowhere to write. Nothing was measured; zero would be a lie.
75
+ if [ -z "$LOG" ] || [ ! -f "$LOG" ]; then
76
+ if [ -d "./.claude" ]; then
77
+ if [ "$JSON" = 1 ]; then printf '{"measured":true,"rules":%s,"decisions":0}\n' "$NRULES"; else
78
+ echo "== gate report =="
79
+ echo " ✅ no gate has fired in this project yet — $NRULES rules wired, recording on,"
80
+ echo " nothing has tripped one. (Recording writes .claude/gate-log.tsv, which is gitignored;"
81
+ echo " rule names and verdicts only, never the command — CREW_GATE_LOG_CMD=1 adds it for debugging.)"
82
+ fi
83
+ exit 0
84
+ fi
85
+ if [ "$JSON" = 1 ]; then printf '{"measured":false,"rules":%s,"reason":"nowhere to record"}\n' "$NRULES"; else
86
+ echo "== gate report =="
87
+ echo " · NOT MEASURED — no .claude/ to record into. $NRULES rules are wired; how often they fire is unknown."
88
+ echo " ↳ point it somewhere: export CREW_GATE_LOG=\"\$PWD/gate-log.tsv\""
89
+ fi
90
+ exit 3
91
+ fi
92
+
93
+ # ---- read the log: VERDICT \t §SECTION \t RULE \t COMMAND
94
+ awk -F'\t' 'NF>=3 { k=$3; sub(/[[:space:]]*\([^()]*\)[[:space:]]*$/,"",k); c[k]++; lab[k]=$3; v[$1]++; tot++ } END {
95
+ print "TOTAL\t" tot+0
96
+ for (k in v) print "VERDICT\t" k "\t" v[k]
97
+ for (k in c) print "RULE\t" k "\t" c[k] "\t" lab[k]
98
+ }' "$LOG" > "$TMP/agg"
99
+
100
+ TOT=$(awk -F'\t' '$1=="TOTAL"{print $2}' "$TMP/agg"); TOT="${TOT:-0}"
101
+ FIRST=$(head -1 "$LOG" 2>/dev/null | cut -f3); LINES=$(grep -c . "$LOG" 2>/dev/null); LINES=$(( ${LINES:-0} + 0 ))
102
+
103
+ if [ "$JSON" = 1 ]; then
104
+ awk -F'\t' -v n="$NRULES" -v t="$TOT" '
105
+ $1=="RULE" { r=r sep "{\"rule\":\"" $4 "\",\"count\":" $3 "}"; sep="," }
106
+ END { printf "{\"measured\":true,\"rules\":%s,\"decisions\":%s,\"fired\":[%s]}\n", n, t, r }' "$TMP/agg"
107
+ exit 0
108
+ fi
109
+
110
+ echo "== gate report =="
111
+ echo " log: $LOG ($LINES line(s), $TOT decision(s))"
112
+ echo
113
+ echo " verdicts:"
114
+ awk -F'\t' '$1=="VERDICT"{printf " %-6s %s\n",$2,$3}' "$TMP/agg" | sort
115
+ echo
116
+ echo " rules that fired:"
117
+ awk -F'\t' '$1=="RULE"{printf " %5s %s\n",$3,$4}' "$TMP/agg" | sort -rn || true
118
+ [ "$TOT" = 0 ] && echo " (none)"
119
+ echo
120
+ # One awk over both files instead of sort|comm|join: that chain compared a two-field inventory line against a
121
+ # one-field fired list, so a rule could land in BOTH lists at once (it did — `chmod world-writable` showed as
122
+ # fired and as unseen in the same report). It also spawned four processes per run, which is real cost on
123
+ # Windows. Here the fired set and the inventory meet in one pass, keyed the same way.
124
+ UNSEEN="$(awk -F'\t' '
125
+ FILENAME==f1 && $1=="RULE" { fired[$2]=1; next }
126
+ FILENAME==f2 {
127
+ lbl=$2; k=lbl; sub(/[[:space:]]*\([^()]*\)[[:space:]]*$/,"",k)
128
+ if (!(k in fired) && !(k in seen)) { seen[k]=1; printf " \302\267 %s\n", lbl }
129
+ }' f1="$TMP/agg" f2="$TMP/inv" "$TMP/agg" "$TMP/inv")"
130
+ NUNSEEN=$(printf '%s' "$UNSEEN" | grep -c . 2>/dev/null || true); NUNSEEN="${NUNSEEN:-0}"
131
+ echo " wired but not observed in this log ($NUNSEEN of $NRULES):"
132
+ [ -n "$UNSEEN" ] && printf '%s\n' "$UNSEEN"
133
+
134
+ echo
135
+ echo " Not observed is NOT dead. It means one of: nobody attempted it, the model never reached for that"
136
+ echo " command, or the rule cannot match. Only the first two are healthy, and this report cannot tell them"
137
+ echo " apart — smoke-test is what proves a rule CAN fire; this shows whether anything tripped it."
@@ -0,0 +1,53 @@
1
+ # HELD-OUT routing set: <example user prompt>|<expected target>
2
+ #
3
+ # Same format as golden-routing.txt and read by the same matcher. The difference is the method, and it is the
4
+ # whole point: these prompts were written from the TASK a user would describe, in the words a user would use,
5
+ # WITHOUT reading the target's trigger phrases first. In the working set the prompt is where the trigger came
6
+ # from, so a passing row proves only that the author copied one into the other. Here the two were produced
7
+ # independently, so a row can fail — and a failure means the trigger is too narrow to reach a request nobody
8
+ # tuned it for, which is the only thing this eval was ever supposed to measure.
9
+ #
10
+ # WHEN A ROW FAILS, the fix is a decision, not a reflex:
11
+ # · widen the trigger — and re-run, because the negative rows in golden-routing.txt exist to catch a widening
12
+ # that starts stealing a neighbour's work;
13
+ # · or judge the prompt unreasonable for that target and say so here, in a comment, rather than deleting it
14
+ # quietly. A row removed without a reason is how a held-out set decays back into a working set.
15
+ # Never "fix" a failure by editing the prompt toward the trigger. That is the thing being measured.
16
+
17
+ my checkout is failing on the pipeline and I cannot tell why|ci-pipeline
18
+ the page is unusable with a keyboard, tab order jumps everywhere|a11y
19
+ we are adding a column to the users table next week|db-migration
20
+ customers in Germany are asking what we store about them|privacy-compliance
21
+ someone pasted an API key into a config file, what now|security-scan
22
+ the app takes eight seconds to open the dashboard|performance
23
+ I need to cut a new version and tell people what changed|release
24
+ this endpoint returns different shapes depending on the caller|api-design
25
+ we keep discovering the same outage twice a month|incident-runbook
26
+ I want to know if this library is still maintained|dependency-audit
27
+ three of us are working on the same repo and duplicating work|teamboard
28
+ I am about to run out of room in this conversation|handoff
29
+ nobody knows why we chose this queue over the other one|adr
30
+ the tests pass locally and fail in the pipeline every third run|testing
31
+ I want to be sure this change did not make anything slower|performance
32
+ we need to put this build on the server tonight|deploy
33
+
34
+ # --- REJECTED, with the reason. Not deleted: a row removed without a reason is how a held-out set decays -----
35
+ #
36
+ # "the German strings are missing on the checkout page" -> i18n-integrity
37
+ # The only phrase that could carry it is "strings are missing", which is a verbatim slice of this sentence —
38
+ # memorising the test, not widening the trigger — AND it steals: "the connection strings are missing from
39
+ # appsettings" then routes to the translation skill. Matching is contiguous, so "missing string" never
40
+ # reaches it either. The class is real; no phrase separates it from a config problem. Reported, not forced.
41
+ #
42
+ # "the model keeps ignoring my instructions when it reads a file" -> red-team
43
+ # The intent (indirect prompt injection) does belong here, but the only substrings that could carry it —
44
+ # "my instructions", "ignoring my instructions", "reads a file" — are indistinguishable from two far more
45
+ # common requests: a user saying the assistant is not following instructions, and a parser bug on file read.
46
+ # Any phrase that catches this prompt takes both of those with it.
47
+ #
48
+ # Both stay listed so the next person sees the class was considered and why the trigger cannot express it. If
49
+ # route-hint ever scores on more than contiguous substrings, reopen them.
50
+ # 3.0 rows for backend-architecture. Honesty note: written by the same person who wrote that skill's triggers,
51
+ # in the same change — so these are weaker held-out evidence than the rows above.
52
+ empty repo, nobody has picked a language or framework for the api yet|backend-architecture
53
+ we cannot agree whether this service should be layered or split per feature|backend-architecture
@@ -0,0 +1,199 @@
1
+ # Golden routing: <example user prompt>|<expected target agent/skill>
2
+ #
3
+ # THIS FILE IS THE WORKING SET, and what it can and cannot prove is worth stating. AGENT_TEMPLATE.md tells the
4
+ # author to add the line FIRST and then choose trigger phrases against it — "you're designing the description
5
+ # against a concrete prompt" — and the matcher below is a substring test. So a row here proves the trigger was
6
+ # chosen for this prompt; it cannot prove the trigger generalises, because the prompt is where the trigger came
7
+ # from. `golden-holdout.txt` is the other half: prompts written for the same target WITHOUT looking at its
8
+ # triggers. Only a holdout row can fail for the reason this eval exists.
9
+ # routing-eval.sh reads this: the expected target's trigger phrase must appear in the prompt (substring).
10
+ # If the target isn't installed in this profile, the line is SKIPPED (won't fail a pruned install).
11
+ # NEGATIVE line: <prompt>|!<target> asserts the prompt must NOT route to <target> (none of its triggers match) —
12
+ # a guard against over-broad triggers. See the "must NOT route" block at the end of this file.
13
+ add a new handler for the order endpoint|crew-backend-expert
14
+ write a migration for this table|crew-database-expert
15
+ build a responsive component for the login screen|crew-frontend-expert
16
+ # Positive twins of the seven over-broad-word rows below: the replacement phrase must still route.
17
+ cut a release and tag it|release
18
+ review the code in this PR|crew-code-review
19
+ the page layout is broken on mobile|frontend-design
20
+ requests are timing out under load|crew-performance-expert
21
+ add client routing to the app|crew-frontend-expert
22
+ add a new rn screen with expo router|frontend-rn-expo
23
+ run an OWASP check and a full security scan|crew-security-expert
24
+ we need a kvkk privacy audit|crew-privacy-agent
25
+ commit the changes|crew-commit-agent
26
+ run tests and give me a coverage report|crew-test-expert
27
+ do a PR review and look at the diff|crew-review-agent
28
+ plan first and produce a task breakdown|crew-planner
29
+ deploy this and go to production on the VPS|deploy
30
+ prepare the changelog and cut a tag|release
31
+ set up a ci pipeline with github actions|ci-pipeline
32
+ write an adr for this architecture decision|adr
33
+ run npm audit for a dependency audit|dependency-audit
34
+ check i18n for any missing translation|i18n-integrity
35
+ run the sonarqube quality gate|sonarqube-check
36
+ prepare a handoff with the current session state|handoff
37
+ add a structured log with a correlation id|observability
38
+ update the README and write the API docs|docs-writer
39
+ run an accessibility audit and check WCAG|a11y
40
+ handle api versioning with an openapi contract|api-design
41
+ the page is slow, do a performance optimization|performance
42
+ write an incident runbook and a postmortem|incident-runbook
43
+ red team the prompt injection defenses|red-team
44
+ ship to prod and be ready to rollback|crew-devops-expert
45
+ set up a reverse proxy and ssl|crew-devops-expert
46
+ keep going until the tests pass and the review is clean|iterate
47
+ debug this and find the root cause of the intermittent bug|systematic-debugging
48
+ improve the visual design — the spacing and typography look off|frontend-design
49
+ build an MCP server to expose these tools to Claude|mcp-builder
50
+ build a threat model and map the attack surface before we scan|threat-model
51
+ set up an eval to measure output quality against a baseline|eval-grader
52
+
53
+ # --- How a user actually phrases it -> the AGENT that must own the work ---
54
+ # Added after a real install where a design request produced good analysis and no delegation. Every case here
55
+ # asserts an AGENT, deliberately: the skills already carried this vocabulary ("visual design", "typography",
56
+ # "memory leak") so the skill fired and the gate stayed green, while the agent that OWNS the work was
57
+ # unreachable by the words anyone actually types. Testing the skill proved the mapping existed; it never proved
58
+ # a real sentence reached the delegation layer.
59
+ the app does not look premium, improve the visual design|crew-frontend-expert
60
+ add a dark mode and a design token layer|crew-frontend-expert
61
+ the icons are inconsistent, we need a design system|crew-frontend-expert
62
+ we have a memory leak in production|crew-performance-expert
63
+ the app feels laggy after the last release|crew-performance-expert
64
+ users report the app crashes on startup|systematic-debugging
65
+ the login is broken and I do not know why|systematic-debugging
66
+ # Narrowing a trigger has to be paid for with a positive case, or the fix for a false route quietly creates an
67
+ # unreachable component. token-budget had NO positive case at all before this line.
68
+ the context is full, check the token budget before we continue|token-budget
69
+
70
+ # --- Core components that had NO positive case until 1.11.0 ---
71
+ # An audit found 12 skills and one agent that the routing gate never checked — including security-scan,
72
+ # code-review, testing and spec-planning, i.e. most of what Crewforth advertises. A component with no case is a
73
+ # component whose reachability is assumed, which is exactly how crew-frontend-expert went unreachable for a whole
74
+ # class of request while every gate stayed green.
75
+ i am not sure what we want here yet, help me scope it|brainstorm
76
+ review the code I just wrote before I push|crew-code-review
77
+ write a commit message for the staged changes|commit-message
78
+ add column last_login to the users table|db-migration
79
+ add a query handler with a validator for this module|backend-architecture
80
+ the settings screen needs a state management fix|frontend
81
+ we need a consent flow before we store any of this|privacy-compliance
82
+ let us step back and see what we missed this week|reflect
83
+ scan for vulnerabilities in the auth flow|security-scan
84
+ how do we split this into tasks with acceptance criteria|spec-planning
85
+ are the tests green after that change|testing
86
+ check for AI trace before the commit|trace-scan
87
+ is a handover needed, the context status looks high|crew-session-manager
88
+
89
+ # --- 3.0: the stack is resolved per project by backend-architecture (it replaced cqrs-aop-module) ---
90
+ which stack should we use for this backend|backend-architecture
91
+ set up the project structure for a new API|backend-architecture
92
+ choose a database for this service|backend-architecture
93
+
94
+ # --- Negative: prompt must NOT route to target (over-broad-trigger guard) ---
95
+ #
96
+ # The seven rows below are the ones that were REPORTED FROM LIVE USE, not invented. Each generic English word
97
+ # was a standalone trigger, and a single trigger of six characters or more clears the score floor on its own —
98
+ # so any sentence containing the word routed somewhere unrelated. Measured before the fix: all seven misrouted.
99
+ # "context" -> token-budget "version" -> release "review" -> crew-review-agent
100
+ # "routing" -> crew-frontend-expert "timeout" -> performance-expert "screen" -> crew-frontend-expert
101
+ # "layout" -> frontend-design
102
+ # None of them came from this release's widening: all seven were triggers in 2.6.0 and earlier, which the diff
103
+ # against v2.6.0 confirms. Each was replaced by the phrase that carries the intent rather than deleted, and the
104
+ # POSITIVE twin of every row sits in the block above — a guard that only proves silence would be satisfied by
105
+ # a component with no triggers at all.
106
+ give me more context on this bug|!token-budget
107
+ review the jira ticket before I send it|!crew-review-agent
108
+ the image version is 3.2, rebuild it|!release
109
+ the routing table is misconfigured|!crew-frontend-expert
110
+ the request timeout is 30 seconds|!crew-performance-expert
111
+ the screen froze during the demo|!crew-frontend-expert
112
+ fix the layout of this json file|!frontend-design
113
+ design the database schema|!frontend-design
114
+ read the model context and summarize it|!mcp-builder
115
+ optimize the slow sql query performance|!systematic-debugging
116
+ # `build` contains `ui`, and `UI` used to be a trigger, so a CI failure routed to the frontend expert. A wrong
117
+ # route is worse than none: it looks like Crewforth worked. Fixed by word-bounded matching AND by retiring the
118
+ # bare `UI` trigger for `UI polish`; this line fails if either regresses.
119
+ the build fails on CI|!crew-frontend-expert
120
+ the docker build is broken in the pipeline|!crew-frontend-expert
121
+ # Same class, found while verifying the fix above: a bare `token` trigger sent "design token layer" to the
122
+ # session-context skill. A one-word trigger for a word that lives in two vocabularies is not a trigger.
123
+ add a dark mode and a design token layer|!token-budget
124
+ isolate this work in a git worktree before the parallel agents run|worktree
125
+ before I start, am I sure enough to implement this|confidence-check
126
+ upgrade dependencies to current versions|dependency-upgrade
127
+ is this endpoint fast enough on the hot path|crew-performance-expert
128
+ who is working on which sprint item right now|teamboard
129
+ audit this against ccpa and lgpd for data protection regulation|crew-privacy-agent
130
+ is my auto mode classifier policy still intact|automode-policy
131
+
132
+ # --- negative guards added with the trigger widening -------------------------------------------------
133
+ # Each widening below opened a class of request; each row here is a request that belongs to a NEIGHBOUR
134
+ # and must NOT reach the widened component. A widening without its guard is a steal nobody would notice.
135
+ add keyboard shortcuts for power users on the dashboard|!a11y
136
+ pick a color palette for the dark mode|!a11y
137
+ the focus this sprint is the new checkout component|!a11y
138
+ add a mouse hover state to the card|!a11y
139
+ reorder the tab bar items on the profile screen|!a11y
140
+ bump the default text size in the design system|!a11y
141
+ add a motion blur transition between routes|!a11y
142
+ make the empty state look more premium on the profile page|!a11y
143
+ is the lodash version in our lockfile affected by a published CVE|!security-scan
144
+ payments are down in production, who owns the incident right now|!security-scan
145
+ we have a memory leak in the worker process after the release|!security-scan
146
+ map the trust boundaries and entry points before we start the audit|!security-scan
147
+ the goroutine leaked memory and the pod restarted|!security-scan
148
+ the connection pool leaked connections under load|!security-scan
149
+ trim what we store in the session cache so redis stops growing|!privacy-compliance
150
+ someone pushed a database password to the repo, rotate the credentials|!privacy-compliance
151
+ delete the old event rows from the analytics table to save disk|!privacy-compliance
152
+ fix the sql injection in the search filter before release|!red-team
153
+ you keep ignoring my instructions about the commit format|!red-team
154
+ the importer crashes when it reads a large file|!red-team
155
+ the ci build takes 20 minutes and blocks every pr|!performance
156
+ the webpack build takes ninety seconds to finish in the pipeline|!performance
157
+ add a duration metric and a correlation id to this handler|!performance
158
+ the github actions workflow needs a build and test stage before the pr gate|!testing
159
+ the docker build step fails in the pipeline but works on my machine|!testing
160
+ users hit an intermittent bug on checkout that I cannot reproduce|!testing
161
+ add a column chart to the analytics dashboard|!db-migration
162
+ the results grid needs an extra column on the settings screen|!db-migration
163
+ we are migrating the frontend build to vite next week|!db-migration
164
+ the login endpoint throws a 500 for half the requests|!api-design
165
+ add a new endpoint that returns the user profile|!api-design
166
+ upgrading the sdk to v4 is not backward compatible|!api-design
167
+ users get a different response from the cache than the database|!api-design
168
+ bump versions of the outdated packages and verify the build|!dependency-audit
169
+ wire up a new dependency injection container for the module|!dependency-audit
170
+ we send analytics events to a third party service|!dependency-audit
171
+ the npm ci step fails on the windows runner|!dependency-audit
172
+ the context is full, check the token budget before we continue|!handoff
173
+ is it time to clear, how healthy is this session|!handoff
174
+ the disk is about to run out of space on the server tonight|!handoff
175
+ resume the half-finished migration on the users table|!handoff
176
+ run two agents in parallel without them touching the same files|!teamboard
177
+ isolate this refactor in a sandbox worktree so the parallel edits do not collide|!teamboard
178
+ break the release work down into tasks with acceptance criteria|!teamboard
179
+ my two agents are working on the same files|!teamboard
180
+ nobody knows why the nightly job fails every other night|!adr
181
+ write the README section explaining how the retry queue works|!adr
182
+ how do we split this into tasks with acceptance criteria|!adr
183
+ explain the reasoning behind this stack trace|!adr
184
+ the unit tests are failing after my refactor|!ci-pipeline
185
+ cut the release, write the changelog and tag it|!ci-pipeline
186
+ the deploy to the server failed last night|!ci-pipeline
187
+ add a build step to the github actions workflow|!deploy
188
+ we have an outage in production, start the incident runbook|!deploy
189
+ bump the version and add the changelog entry|!deploy
190
+ tail the nginx logs on the server for me|!deploy
191
+ the cron job on the server never fires|!deploy
192
+ the tests fail on the server but pass in my ide|!deploy
193
+ we push to produce a release candidate every friday|!deploy
194
+ add a link to product detail from the search results|!deploy
195
+ build a responsive component for the login screen|!i18n-integrity
196
+ we picked the wrong language for this service, write an adr|!i18n-integrity
197
+ write the README in english for the public repo|!i18n-integrity
198
+ the connection strings are missing from appsettings|!i18n-integrity
199
+ add language support for kotlin to the parser|!i18n-integrity
@@ -0,0 +1,14 @@
1
+ # Sourced, not run. The ONE bash place that still reads the pre-3.0 CSK_* names.
2
+ #
3
+ # 3.0 renamed every CSK_* variable to CREW_*. The ones a user can have set — because a README, --help, a command
4
+ # file or a message names them — keep working for the whole 3.x line: when CREW_<X> is not set at all and CSK_<X>
5
+ # is, CREW_<X> takes its value. A CREW_<X> that is set, even to 0 or empty, wins. Internal and test variables were
6
+ # renamed with no fallback. The old names go in 4.0.
7
+ #
8
+ # Builtins only (a loop of `eval`s): sourcing this costs no process, which matters in hooks on Git Bash.
9
+ # Kept after this file runs: the updater and doctor read it to say which set 2.x names still work.
10
+ _crew_legacy=" LANG NO_STAR NO_UPDATE_CHECK NO_BOARD GATE_LOG GATE_LOG_CMD STUDIO_TOKEN STUDIO_PEERS STUDIO_RUNTIME MAX_FILE_BYTES ALLOW_SOURCE_INSTALL "
11
+ for _crew_v in $_crew_legacy; do
12
+ eval "if [ -z \"\${CREW_$_crew_v+x}\" ] && [ -n \"\${CSK_$_crew_v+x}\" ]; then CREW_$_crew_v=\"\$CSK_$_crew_v\"; export CREW_$_crew_v; fi"
13
+ done
14
+ unset _crew_v
@@ -0,0 +1,154 @@
1
+ # settings-json.awk — Crewforth's one JSON reader for settings.json, POSIX awk only (no jq, no python3), so the
2
+ # same code runs on macOS BSD awk, Linux mawk/gawk and Windows Git-Bash gawk.
3
+ #
4
+ # awk -v op=validate -f settings-json.awk FILE rc 0 = FILE is a JSON object, 1 = it is not
5
+ # awk -v op=get -v path=hooks.Stop.0 -f settings-json.awk FILE the value there, as the merge would print it
6
+ # awk -v op=len -v path=hooks.Stop -f settings-json.awk FILE how many entries an array/object holds
7
+ # awk -v op=strings -v path=permissions.deny -f settings-json.awk FILE each string element of that array, one per
8
+ # line, quotes off, escapes as written (rc 1 = not an array)
9
+ # path = dot-separated keys, a number indexes an array (0-based), empty = the root. rc 1 = no such path
10
+ # (or, for len, a scalar there), 10 = FILE is not a JSON object.
11
+ # awk -v op=overlay -v key=K [-v setk=M -v setv=RAWJSON] -f settings-json.awk FILE1 FILE2
12
+ # FILE1 with FILE1.K shallow-merged from FILE2.K (FILE2 wins, FILE1's other members and order kept), then
13
+ # member M set to RAWJSON. rc 10 = FILE1 (or its K) is not an object, 11 = FILE2 has no object at K.
14
+ # awk -v op=merge -v retired='R1|R2' -f settings-json.awk KIT PROJECT > OUT
15
+ # rc 0 = merged JSON on stdout · 10 = PROJECT is not a JSON object · 11 = KIT is not · 12 = "hooks" has a
16
+ # shape the merge cannot place (not an object of arrays). Nothing is printed on a non-zero rc. The codes sit
17
+ # clear of awk's own 2 (bad program, unreadable file), so a caller never reads a missing file as bad JSON.
18
+ #
19
+ # Reader contract: a recursive-descent parse into nodes (T = o/a/s, N = child count, K = key, C = child, V = raw
20
+ # scalar text). Strings are kept as their raw token, escapes included and never decoded, so they are written back
21
+ # byte-for-byte; two spellings of one string (\u0041 vs A) therefore count as different. Object key order and
22
+ # array order are preserved. Numbers are checked against the JSON grammar and kept as written.
23
+ #
24
+ # Merge semantics (what the former jq program in adopt.sh did, held to it by the parity fixture in
25
+ # packaging/e2e.sh): objects deep-merge, the PROJECT's scalar wins, arrays concat+dedup with Crewforth's entries
26
+ # first, and "hooks" is rebuilt per event as Crewforth's entries followed by the project's entries that do not
27
+ # reference .claude/hooks/ (in "command" or "args"). permissions.ask then loses every rule named in -v retired.
28
+ # Output is pretty-printed with two-space indent, the layout jq writes, so merging Crewforth onto itself gives its
29
+ # own bytes back.
30
+ # The text is split into CH[] once. substr(S,P,1) is O(len) per call on BSD awk, which made a 1 MB file take ~20 s;
31
+ # array indexing is O(1), so parsing is linear. split(s, CH, "") splits into characters in onetrue/BSD awk, gawk
32
+ # and mawk alike.
33
+ function sk(){ while(P<=L && index(" \t\r\n", CH[P])) P++ }
34
+ # Strings are held to RFC 8259, the grammar Claude Code's JSON.parse applies: an escape is one of \" \\ \/ \b \f \n
35
+ # \r \t or \u + 4 hex digits, and no raw control character (below 0x20). The raw token is returned, escapes kept.
36
+ function ps( t,c,i){ t=CH[P]; P++
37
+ while(P<=L){ c=CH[P]; P++
38
+ if(c=="\\"){ if(P>L) return ""; c=CH[P]; P++
39
+ if(c=="u"){ t=t "\\u"; for(i=0;i<4;i++){ if(P>L || !index("0123456789abcdefABCDEF", CH[P])) return ""; t=t CH[P]; P++ }; continue }
40
+ if(!index("\"\\/bfnrt", c)) return ""; t=t "\\" c; continue }
41
+ if(index(CTL, c)) return ""
42
+ t=t c; if(c=="\"") return t }
43
+ return "" }
44
+ function pv( id,c,k,q,st){ sk(); c=CH[P]; id=++NN; N[id]=0
45
+ if(c=="{" || c=="["){ T[id]=(c=="{" ? "o" : "a"); P++; sk()
46
+ if(CH[P]==(c=="{" ? "}" : "]")){ P++; return id }
47
+ while(1){ k=""
48
+ if(c=="{"){ sk(); if(CH[P]!="\"" || (k=ps())=="") return 0; sk(); if(CH[P++]!=":") return 0 }
49
+ if(!(q=pv())) return 0
50
+ N[id]++; K[id,N[id]]=k; C[id,N[id]]=q; sk(); st=CH[P++]
51
+ if(st==(c=="{" ? "}" : "]")) return id
52
+ if(st!=",") return 0 } }
53
+ T[id]="s"
54
+ if(c=="\""){ V[id]=ps(); return (V[id]=="" ? 0 : id) }
55
+ st=""; while(P<=L && CH[P] ~ /[-+.0-9A-Za-z]/){ st=st CH[P]; P++ }; V[id]=st
56
+ return (V[id] ~ /^(true|false|null|-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][-+]?[0-9]+)?)$/ ? id : 0) }
57
+ # doc(f): parse file number f. CH[] is built from that file's lines directly: gluing the lines into one string
58
+ # first was itself quadratic (each append copies the whole buffer) on a many-line file.
59
+ function doc(f, r,i,j,n,tc){ split("",CH); L=0
60
+ for(i=1;i<=NL[f];i++){ n=split(LN[f,i],tc,""); for(j=1;j<=n;j++) CH[++L]=tc[j]; CH[++L]="\n" }
61
+ P=1
62
+ if(CTL==""){ for(r=1;r<32;r++) CTL=CTL sprintf("%c",r) }
63
+ r=pv(); sk(); return (r && P>L && T[r]=="o") ? r : 0 }
64
+ # A duplicated key: the LAST value wins, as in JSON.parse and jq — the value Claude Code actually reads.
65
+ function get(o,k, i){ for(i=N[o];i>=1;i--) if(K[o,i]==k) return C[o,i]; return 0 }
66
+ function isnull(id){ return T[id]=="s" && V[id]=="null" }
67
+ function at(id,path, n,sg,i){ n=split(path,sg,".")
68
+ for(i=1;i<=n && id;i++) id=(T[id]=="o") ? get(id,"\"" sg[i] "\"") : (T[id]=="a" && sg[i] ~ /^[0-9]+$/ && sg[i]<N[id]) ? C[id,sg[i]+1] : 0
69
+ return id }
70
+ function mk(t){ NN++; T[NN]=t; N[NN]=0; return NN }
71
+ function add(o,k,c){ N[o]++; K[o,N[o]]=k; C[o,N[o]]=c }
72
+ function canon(id, i,j,n,s,t,ord){
73
+ if(T[id]=="s") return V[id]
74
+ if(T[id]=="a"){ s="["; for(i=1;i<=N[id];i++) s=s (i>1 ? "," : "") canon(C[id,i]); return s "]" }
75
+ n=N[id]; for(i=1;i<=n;i++) ord[i]=i
76
+ for(i=2;i<=n;i++) for(j=i;j>1 && K[id,ord[j-1]]>K[id,ord[j]];j--){ t=ord[j]; ord[j]=ord[j-1]; ord[j-1]=t }
77
+ s="{"; for(i=1;i<=n;i++) s=s (i>1 ? "," : "") K[id,ord[i]] ":" canon(C[id,ord[i]]); return s "}" }
78
+ function cat(a,b, r,i,c,seen){ r=mk("a")
79
+ for(i=1;i<=N[a];i++){ c=canon(C[a,i]); if(!(c in seen)){ seen[c]=1; add(r,"",C[a,i]) } }
80
+ for(i=1;i<=N[b];i++){ c=canon(C[b,i]); if(!(c in seen)){ seen[c]=1; add(r,"",C[b,i]) } }
81
+ return r }
82
+ function dm(a,b, r,i,x){ r=mk("o")
83
+ for(i=1;i<=N[a];i++){ x=get(b,K[a,i]); add(r,K[a,i], !x ? C[a,i] : (T[C[a,i]]=="o" && T[x]=="o") ? dm(C[a,i],x) : (T[C[a,i]]=="a" && T[x]=="a") ? cat(C[a,i],x) : x) }
84
+ for(i=1;i<=N[b];i++) if(!get(a,K[b,i])) add(r,K[b,i],C[b,i])
85
+ return r }
86
+ function has_kit(s){ return T[s]=="s" && index(V[s], ".claude/hooks/") }
87
+ function is_kit(e, hs,i,h,a,j){ hs=get(e,"\"hooks\""); if(T[e]!="o" || T[hs]!="a") return 0
88
+ for(i=1;i<=N[hs];i++){ h=C[hs,i]; if(T[h]!="o") continue
89
+ if(has_kit(get(h,"\"command\""))) return 1
90
+ a=get(h,"\"args\""); if(T[a]=="a") for(j=1;j<=N[a];j++) if(has_kit(C[a,j])) return 1 }
91
+ return 0 }
92
+ function hooks(kh,ph, r,i,e,a,x,y,j){ r=mk("o")
93
+ for(i=1;i<=N[kh];i++){ e=K[kh,i]; if(!get(r,e)) add(r,e,mk("a")) }
94
+ for(i=1;i<=N[ph];i++){ e=K[ph,i]; if(!get(r,e)) add(r,e,mk("a")) }
95
+ for(i=1;i<=N[r];i++){ e=K[r,i]; a=C[r,i]; x=get(kh,e); y=get(ph,e)
96
+ if(isnull(x)) x=0; if(isnull(y)) y=0 # null event = no entries, as the jq merge's `// []` read it
97
+ if((x && T[x]!="a") || (y && T[y]!="a")) return 0
98
+ for(j=1;j<=N[x];j++) add(a,"",C[x,j])
99
+ for(j=1;j<=N[y];j++) if(!is_kit(C[y,j])) add(a,"",C[y,j]) }
100
+ return r }
101
+ function put(id,ind, i,s,o){
102
+ if(T[id]=="s") return V[id]
103
+ o=(T[id]=="o"); if(!N[id]) return (o ? "{}" : "[]")
104
+ s=(o ? "{" : "["); for(i=1;i<=N[id];i++) s=s (i>1 ? "," : "") "\n" ind " " (o ? K[id,i] ": " : "") put(C[id,i], ind " ")
105
+ return s "\n" ind (o ? "}" : "]") }
106
+ # dls(s): parse one string (a JSONL record) the same way.
107
+ function dls(s, r,n){ split("",CH); L=split(s,CH,""); P=1
108
+ if(CTL==""){ for(r=1;r<32;r++) CTL=CTL sprintf("%c",r) }
109
+ r=pv(); sk(); return (r && P>L && T[r]=="o") ? r : 0 }
110
+ FILENAME!=cur{ f++; cur=FILENAME } { LN[f,++NL[f]]=$0 }
111
+ END{
112
+ # op=usage (hooks/context-usage.sh): FILE is a transcript tail (JSONL). Walking from the LAST line back, print the
113
+ # context total of the last record that is a real top-level `type: assistant`, not a sidechain, carrying
114
+ # message.usage.cache_read_input_tokens — the predicate the former jq program applied. Each candidate is PARSED:
115
+ # a regex over the line matched the same key names inside tool inputs and results, and could count a nested
116
+ # object instead of the record's own usage. A line that does not parse (the window's cut first line) is skipped.
117
+ if(op=="usage"){ for(i=NL[1];i>=1;i--){ l=LN[1,i]; if(!index(l,"cache_read_input_tokens")) continue
118
+ if(!(r=dls(l))) continue
119
+ x=get(r,"\"isSidechain\""); if(x && !(isnull(x) || V[x]=="false")) continue
120
+ x=get(r,"\"type\""); if(!x || V[x]!="\"assistant\"") continue
121
+ u=at(r,"message.usage"); if(T[u]!="o") continue
122
+ cr=get(u,"\"cache_read_input_tokens\""); if(!cr || isnull(cr)) continue
123
+ it=get(u,"\"input_tokens\""); cc=get(u,"\"cache_creation_input_tokens\"")
124
+ print (it && !isnull(it) ? V[it] : 0) + V[cr] + (cc && !isnull(cc) ? V[cc] : 0); exit 0 }
125
+ exit 0 }
126
+ if(op=="validate") exit !doc(1)
127
+ if(op=="get" || op=="len"){ if(!(r=doc(1))) exit 10; if(!(r=at(r,path))) exit 1
128
+ if(op=="get"){ print put(r,""); exit 0 } if(T[r]=="s") exit 1; print N[r]; exit 0 }
129
+ if(op=="strings"){ if(!(r=doc(1))) exit 10; if(!(r=at(r,path))) exit 1; if(T[r]!="a") exit 1
130
+ for(i=1;i<=N[r];i++){ c=C[r,i]; if(T[c]=="s" && substr(V[c],1,1)=="\"") print substr(V[c],2,length(V[c])-2) }
131
+ exit 0 }
132
+ if(op=="overlay"){ if(!(t=doc(1))) exit 10; if(!(q=doc(2))) exit 11; kk="\"" key "\""
133
+ qv=get(q,kk); if(T[qv]!="o") exit 11; tv=get(t,kk); if(tv && T[tv]!="o") exit 10
134
+ nv=mk("o"); for(i=1;i<=N[tv];i++){ x=get(qv,K[tv,i]); add(nv,K[tv,i], x ? x : C[tv,i]) }
135
+ for(i=1;i<=N[qv];i++) if(!get(nv,K[qv,i])) add(nv,K[qv,i],C[qv,i])
136
+ if(setk!=""){ sn=mk("s"); V[sn]=setv; x=0
137
+ for(i=1;i<=N[nv];i++) if(K[nv,i]=="\"" setk "\""){ C[nv,i]=sn; x=1 }
138
+ if(!x) add(nv,"\"" setk "\"",sn) }
139
+ x=0; for(i=1;i<=N[t];i++) if(K[t,i]==kk){ C[t,i]=nv; x=1 }
140
+ if(!x) add(t,kk,nv)
141
+ print put(t,""); exit 0 }
142
+ if(!(k=doc(1))) exit 11
143
+ if(!(p=doc(2))) exit 10
144
+ kh=get(k,"\"hooks\""); ph=get(p,"\"hooks\""); if(isnull(kh)) kh=0; if(isnull(ph)) ph=0
145
+ if((kh && T[kh]!="o") || (ph && T[ph]!="o")) exit 12
146
+ r=dm(k,p); if(!(h=hooks(kh ? kh : mk("o"), ph ? ph : mk("o")))) exit 12
147
+ for(i=1;i<=N[r];i++) if(K[r,i]=="\"hooks\""){ C[r,i]=h; h=0 }
148
+ if(h) add(r,"\"hooks\"",h)
149
+ n=split(retired,rr,"|"); for(i=1;i<=n;i++) ret["\"" rr[i] "\""]=1
150
+ pm=get(r,"\"permissions\""); ak=(T[pm]=="o" ? get(pm,"\"ask\"") : 0)
151
+ if(T[ak]=="a"){ na=mk("a"); for(i=1;i<=N[ak];i++) if(T[C[ak,i]]!="s" || !(V[C[ak,i]] in ret)) add(na,"",C[ak,i])
152
+ for(i=1;i<=N[pm];i++) if(K[pm,i]=="\"ask\"") C[pm,i]=na }
153
+ print put(r,"")
154
+ }
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # The one "leave a star" line. Single source of its URL, its text in both languages, and when it stays quiet.
3
+ #
4
+ # star.sh --once <project> ONCE PER KIT VERSION per clone. The installers (first install, and every update)
5
+ # and doctor.sh on a healthy verdict all call it this way and share one marker, so a
6
+ # version shows the line once — whichever of them reaches it first — and never again
7
+ # until the Crewforth version changes. The marker holds the version it was shown for.
8
+ # star.sh unconditional (tests, and anyone who wants to see it).
9
+ #
10
+ # Nothing else prints it: not a hook, not a session start. Cost: one line (~30 tokens) per version, and only when
11
+ # the model relays it — /crew-update and /crew-doctor tell it to pass the line through as the last line.
12
+ #
13
+ # The marker lives in the git dir (`git rev-parse --git-path`), NOT under .claude/: a project that tracks .claude/
14
+ # would otherwise commit it, and one person's "seen" would silence the line for the whole team. Outside a git
15
+ # repository it falls back to <project>/.claude/star-shown.
16
+ #
17
+ # Quiet when CREW_NO_STAR is set to anything but 0, or when CI is defined at all: an unattended run has nobody to
18
+ # read it. A silenced run writes NO marker, so the line is still owed to whoever runs it by hand later.
19
+ set -u
20
+ # The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
21
+ _crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
22
+ [ -f "$_crew_d/./crew-env.sh" ] && . "$_crew_d/./crew-env.sh"; unset _crew_d
23
+ CREW_REPO_URL="https://github.com/Crewforth/crewforth"
24
+
25
+ case "${CREW_NO_STAR:-}" in ''|0) ;; *) exit 0 ;; esac
26
+ [ -n "${CI+set}" ] && exit 0
27
+
28
+ MARK=""; VER=""
29
+ if [ "${1:-}" = --once ]; then
30
+ _dir="${2:-.}"
31
+ # Builtin reads, not `head | tr` in a $( ): on Git Bash those two pipelines were 4 of ~11 processes and ~250 ms
32
+ # of ~395 ms per call (measured on stock Windows). `read` takes the first line; the CR is stripped by expansion.
33
+ # The braces matter: a failed `<` reports before a trailing 2>/dev/null applies (measured: 450 bytes of stderr).
34
+ VER=""; { IFS= read -r VER < "$_dir/.claude/VERSION"; } 2>/dev/null; VER="${VER%$'\r'}"; VER="${VER:-unknown}"
35
+ # GIT_DIR/GIT_WORK_TREE dropped: exported by a caller (a hook), they would point the marker at another repo.
36
+ MARK="$(cd "$_dir" 2>/dev/null && env -u GIT_DIR -u GIT_WORK_TREE git rev-parse --git-path crewforth-star 2>/dev/null)" || MARK=""
37
+ case "$MARK" in
38
+ '') MARK="$_dir/.claude/star-shown" ;;
39
+ /*|[A-Za-z]:*) ;; # absolute (POSIX or a Windows drive path from git)
40
+ *) MARK="$_dir/$MARK" ;; # git answers relative to the directory it was asked in
41
+ esac
42
+ _seen=""; { IFS= read -r _seen < "$MARK"; } 2>/dev/null; [ "${_seen%$'\r'}" = "$VER" ] && exit 0
43
+ fi
44
+
45
+ # ---- CREW-I18N ------------------------------------------------------------------------------------------
46
+ # Same contract as preflight.sh: the installer exports CREW_LANG; run on its own (doctor), the locale decides.
47
+ case "${CREW_LANG:-}" in tr|en) ;; *)
48
+ _loc="${LC_ALL:-}"; [ -n "$_loc" ] || _loc="${LC_MESSAGES:-}"; [ -n "$_loc" ] || _loc="${LANG:-}"
49
+ case "$_loc" in tr*|TR*) CREW_LANG=tr ;; *) CREW_LANG=en ;; esac ;;
50
+ esac
51
+ _mt() {
52
+ [ -n "${1:-}" ] || { _M=""; return 0; }
53
+ local s="$1"; shift
54
+ if [ "$CREW_LANG" = tr ]; then
55
+ case "$s" in
56
+ "⭐ If Crewforth saves you a review round, a star helps others find it: %s") s='⭐ Crewforth bir inceleme turunu kurtardıysa, bir yıldız başkalarının da bulmasına yardım eder: %s' ;;
57
+ *) [ -n "${CREW_I18N_MISS:-}" ] && printf '%s\n' "$s" >> "$CREW_I18N_MISS" ;;
58
+ esac
59
+ fi
60
+ # shellcheck disable=SC2059
61
+ printf -v _M -- "$s" "$@"
62
+ }
63
+ # ---- /CREW-I18N -----------------------------------------------------------------------------------------
64
+
65
+ _mt '⭐ If Crewforth saves you a review round, a star helps others find it: %s' "$CREW_REPO_URL"
66
+ # The marker only after the line actually went out: a closed stdout must not record "shown".
67
+ if printf '%s\n' "$_M" && [ -n "$MARK" ]; then
68
+ { printf '%s\n' "$VER" > "$MARK"; } 2>/dev/null
69
+ fi
70
+ exit 0