crewforth 3.4.0 → 3.5.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +273 -0
  2. package/README.md +3 -2
  3. package/VERSION +1 -1
  4. package/adopt.sh +40 -5
  5. package/bin/cli.js +138 -2
  6. package/kit/AGENT_TEMPLATE.md +15 -0
  7. package/kit/CLAUDE.md +50 -18
  8. package/kit/agents/crew-backend-expert.md +4 -3
  9. package/kit/agents/crew-commit-agent.md +2 -0
  10. package/kit/agents/crew-database-expert.md +2 -1
  11. package/kit/agents/crew-devops-expert.md +1 -0
  12. package/kit/agents/crew-docs-agent.md +3 -1
  13. package/kit/agents/crew-frontend-expert.md +2 -0
  14. package/kit/agents/crew-performance-expert.md +7 -2
  15. package/kit/agents/crew-planner.md +15 -3
  16. package/kit/agents/crew-privacy-agent.md +2 -0
  17. package/kit/agents/crew-review-agent.md +8 -1
  18. package/kit/agents/crew-security-expert.md +4 -3
  19. package/kit/agents/crew-test-expert.md +6 -2
  20. package/kit/eval/doctor.sh +201 -1
  21. package/kit/eval/gate-report.sh +28 -0
  22. package/kit/eval/golden-holdout.txt +2 -0
  23. package/kit/eval/golden-routing.txt +17 -0
  24. package/kit/eval/lib/crew-map/contracts.mjs +77 -0
  25. package/kit/eval/lib/crew-map/crew-map.sh +2 -1
  26. package/kit/eval/lib/crew-map/detect.mjs +2 -1
  27. package/kit/eval/lib/crew-map/narrative.mjs +14 -3
  28. package/kit/eval/lib/crew-map/project.mjs +45 -6
  29. package/kit/eval/lib/crew-map/scan.mjs +153 -14
  30. package/kit/eval/lib/findings/findings.schema.json +11 -1
  31. package/kit/eval/lib/findings/findings.sh +24 -1
  32. package/kit/eval/lib/hook-inject.sh +159 -0
  33. package/kit/eval/lib/lockfiles.sh +71 -0
  34. package/kit/eval/lib/payload-check.sh +29 -0
  35. package/kit/eval/lib/preview.sh +3 -3
  36. package/kit/eval/lib/review-bench/bench.mjs +14 -2
  37. package/kit/eval/lib/review-prepass/prepass.mjs +32 -3
  38. package/kit/eval/lib/settings-json.awk +18 -0
  39. package/kit/eval/lib/statusline.sh +22 -5
  40. package/kit/eval/lib/todo.awk +167 -0
  41. package/kit/eval/lib/todo.sh +120 -0
  42. package/kit/eval/lib/untrusted-text.awk +38 -0
  43. package/kit/eval/scan-skill.sh +67 -20
  44. package/kit/eval/smoke-test.sh +1759 -41
  45. package/kit/eval/utilization.sh +77 -3
  46. package/kit/hooks/agent-outcome.sh +9 -1
  47. package/kit/hooks/board-sync.sh +49 -3
  48. package/kit/hooks/board.sh +8 -8
  49. package/kit/hooks/branch-fresh.sh +101 -0
  50. package/kit/hooks/floor-blocklist.txt +19 -1
  51. package/kit/hooks/guard-bash.sh +92 -8
  52. package/kit/hooks/guard-question.sh +2 -1
  53. package/kit/hooks/loop-guard.sh +445 -0
  54. package/kit/hooks/map-stale.sh +437 -0
  55. package/kit/hooks/pre-commit +48 -7
  56. package/kit/hooks/precompact-snapshot.sh +102 -6
  57. package/kit/hooks/prompt-approval.sh +50 -14
  58. package/kit/hooks/route-hint.sh +36 -7
  59. package/kit/hooks/secret-blocklist.txt +55 -1
  60. package/kit/hooks/session-guard.sh +45 -15
  61. package/kit/hooks/session-rehydrate.sh +34 -11
  62. package/kit/hooks/session-stats.sh +169 -8
  63. package/kit/hooks/session-update-check.sh +5 -1
  64. package/kit/hooks/skill-trust.sh +23 -14
  65. package/kit/hooks/turn-check.sh +190 -0
  66. package/kit/owned-blobs.tsv +4 -4
  67. package/kit/settings.json +64 -0
  68. package/kit/skills/a11y/SKILL.md +50 -3
  69. package/kit/skills/a11y/references/recipes.md +41 -0
  70. package/kit/skills/adr/SKILL.md +14 -2
  71. package/kit/skills/api-design/SKILL.md +6 -1
  72. package/kit/skills/api-design/references/idempotency.md +39 -0
  73. package/kit/skills/brainstorm/SKILL.md +4 -2
  74. package/kit/skills/brainstorm/references/asking.md +47 -0
  75. package/kit/skills/ci-pipeline/SKILL.md +11 -1
  76. package/kit/skills/ci-pipeline/references/agent-job.md +33 -0
  77. package/kit/skills/commit-message/SKILL.md +9 -3
  78. package/kit/skills/confidence-check/SKILL.md +9 -7
  79. package/kit/skills/confidence-check/references/source-check.md +37 -0
  80. package/kit/skills/crew-approve/SKILL.md +3 -0
  81. package/kit/skills/crew-audit/SKILL.md +63 -0
  82. package/kit/skills/crew-board/SKILL.md +2 -2
  83. package/kit/skills/crew-code-review/SKILL.md +27 -4
  84. package/kit/skills/crew-code-review/references/panel-mode.md +9 -9
  85. package/kit/skills/crew-code-review/references/simplify.md +28 -0
  86. package/kit/skills/crew-doctor/SKILL.md +3 -2
  87. package/kit/skills/crew-gates/SKILL.md +3 -0
  88. package/kit/skills/crew-gates/references/enforcers.md +30 -0
  89. package/kit/skills/crew-interview/SKILL.md +42 -0
  90. package/kit/skills/crew-map/SKILL.md +26 -6
  91. package/kit/skills/crew-plan/SKILL.md +10 -2
  92. package/kit/skills/crew-pr/SKILL.md +57 -0
  93. package/kit/skills/crew-pr/references/checks.md +35 -0
  94. package/kit/skills/crew-pr/references/threads.md +23 -0
  95. package/kit/skills/crew-review/SKILL.md +34 -1
  96. package/kit/skills/crew-ship/SKILL.md +5 -2
  97. package/kit/skills/crew-skill/SKILL.md +26 -1
  98. package/kit/skills/crew-todo/SKILL.md +31 -0
  99. package/kit/skills/crew-update/SKILL.md +4 -1
  100. package/kit/skills/db-migration/SKILL.md +15 -0
  101. package/kit/skills/db-migration/references/expand-contract.md +52 -0
  102. package/kit/skills/dependency-audit/SKILL.md +34 -1
  103. package/kit/skills/dependency-audit/scripts/record.sh +37 -0
  104. package/kit/skills/dependency-upgrade/SKILL.md +29 -1
  105. package/kit/skills/deploy/SKILL.md +18 -0
  106. package/kit/skills/deploy/references/topology-a.md +7 -0
  107. package/kit/skills/deploy/references/topology-b.md +2 -2
  108. package/kit/skills/docs-writer/SKILL.md +37 -2
  109. package/kit/skills/eval-grader/SKILL.md +9 -0
  110. package/kit/skills/eval-grader/references/method.md +26 -0
  111. package/kit/skills/frontend/SKILL.md +5 -2
  112. package/kit/skills/frontend/references/from-design.md +29 -0
  113. package/kit/skills/frontend/references/page-metadata.md +17 -0
  114. package/kit/skills/frontend/references/verify-contract.md +18 -1
  115. package/kit/skills/frontend/references/worst-data.md +35 -0
  116. package/kit/skills/frontend-design/SKILL.md +20 -4
  117. package/kit/skills/frontend-design/references/design-review.md +21 -0
  118. package/kit/skills/frontend-design/references/detection.md +44 -0
  119. package/kit/skills/frontend-design/references/motion.md +54 -0
  120. package/kit/skills/frontend-design/references/ui-copy.md +35 -0
  121. package/kit/skills/frontend-rn-expo/SKILL.md +3 -0
  122. package/kit/skills/handoff/SKILL.md +28 -3
  123. package/kit/skills/handoff/references/cold-reader.md +13 -6
  124. package/kit/skills/i18n-integrity/SKILL.md +3 -0
  125. package/kit/skills/iterate/SKILL.md +7 -2
  126. package/kit/skills/mcp-builder/SKILL.md +21 -4
  127. package/kit/skills/mcp-builder/references/building.md +74 -13
  128. package/kit/skills/mcp-builder/references/cli-for-agents.md +26 -0
  129. package/kit/skills/observability/SKILL.md +13 -1
  130. package/kit/skills/performance/SKILL.md +25 -1
  131. package/kit/skills/performance/references/bottlenecks.md +11 -0
  132. package/kit/skills/performance/references/web-vitals.md +31 -0
  133. package/kit/skills/red-team/SKILL.md +25 -3
  134. package/kit/skills/reflect/SKILL.md +7 -2
  135. package/kit/skills/release/SKILL.md +13 -0
  136. package/kit/skills/security-scan/SKILL.md +6 -2
  137. package/kit/skills/security-scan/references/ai-agents.md +16 -0
  138. package/kit/skills/security-scan/references/fronts.md +59 -2
  139. package/kit/skills/security-scan/references/reporting.md +13 -0
  140. package/kit/skills/security-scan/references/verify.md +9 -0
  141. package/kit/skills/sonarqube-check/SKILL.md +32 -2
  142. package/kit/skills/sonarqube-check/references/ratchet.md +32 -0
  143. package/kit/skills/spec-planning/SKILL.md +32 -8
  144. package/kit/skills/systematic-debugging/SKILL.md +15 -7
  145. package/kit/skills/systematic-debugging/references/techniques.md +5 -0
  146. package/kit/skills/teamboard/SKILL.md +3 -3
  147. package/kit/skills/testing/SKILL.md +18 -3
  148. package/kit/skills/testing/references/browser-headless.md +21 -0
  149. package/kit/skills/token-budget/SKILL.md +26 -5
  150. package/kit/skills/worktree/SKILL.md +13 -5
  151. package/kit/studio/server/lib/session.js +23 -2
  152. package/kit/studio/server/lib/worktree-changes.js +64 -0
  153. package/kit/studio/web/agent-spend.js +32 -0
  154. package/kit/studio/web/app.js +245 -236
  155. package/kit/studio/web/approvals.js +14 -13
  156. package/kit/studio/web/ask.js +10 -9
  157. package/kit/studio/web/canvas.js +56 -54
  158. package/kit/studio/web/chat.js +41 -41
  159. package/kit/studio/web/convo.js +33 -16
  160. package/kit/studio/web/dock.js +20 -19
  161. package/kit/studio/web/graph-plan.js +62 -22
  162. package/kit/studio/web/i18n-tr.js +760 -0
  163. package/kit/studio/web/i18n.js +79 -0
  164. package/kit/studio/web/index.html +1 -0
  165. package/kit/studio/web/inspect.js +27 -24
  166. package/kit/studio/web/list-plan.js +16 -15
  167. package/kit/studio/web/list.js +6 -5
  168. package/kit/studio/web/liveness.js +11 -9
  169. package/kit/studio/web/map-plan.js +86 -36
  170. package/kit/studio/web/map.js +85 -72
  171. package/kit/studio/web/models-plan.js +47 -42
  172. package/kit/studio/web/models.js +21 -20
  173. package/kit/studio/web/names.js +13 -10
  174. package/kit/studio/web/nav.js +34 -33
  175. package/kit/studio/web/newsession.js +25 -24
  176. package/kit/studio/web/style.css +2 -0
  177. package/kit/studio/web/theme.js +5 -2
  178. package/kit/studio/web/timeline-plan.js +19 -18
  179. package/kit/studio/web/timeline.js +23 -22
  180. package/kit/studio/web/usage.js +36 -32
  181. package/package.json +5 -4
  182. package/start.sh +11 -2
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env bash
2
+ # turn-check.sh — UserPromptSubmit + Stop: the files the main thread changed in a turn get the project's own fast
3
+ # check once, before the turn ends.
4
+ #
5
+ # WHY. A crew agent's work is checked when it stops (agent-outcome.sh runs its card's `verify`). The main thread's
6
+ # own edits, which the discipline allows for small changes, had nothing: the turn ended, the user read "done", and
7
+ # a lint error waited for the next commit or the next person. This closes that gap with the command the project
8
+ # already declared for it, and only that one.
9
+ #
10
+ # WHAT RUNS. The `Fast check (files):` line of ./CLAUDE.md (## Verify). `<file>` in it becomes the turn's changed files,
11
+ # each in single quotes; a command with no `<file>` runs as written. No line, the template's placeholder, or `none`:
12
+ # nothing runs, nothing is written, no process starts. The command is judged exactly as agent-outcome.sh judges a
13
+ # verify command before it runs it (_am_verify_ok in guard-agent-model.sh: permission rules, the shell gate, the
14
+ # mode); one it may not run is said to the user once per session and not run.
15
+ #
16
+ # WHICH FILES ARE THE TURN'S. At UserPromptSubmit a snapshot goes into the git directory (never committed): HEAD, the
17
+ # time, and the content id of every file that was already dirty. At Stop the turn's set is
18
+ # * a dirty file whose content differs from the snapshot (one dirty before the turn and untouched is not the
19
+ # turn's, it is earlier work);
20
+ # * the files of commits the turn made (commit, amend, cherry-pick, revert), as `git diff <snapshot HEAD> HEAD`;
21
+ # an amend therefore counts only what it changed. When the turn moved HEAD any other way (pull, merge, reset,
22
+ # checkout, rebase), the commits are not counted at all: someone else's code is not this turn's to check.
23
+ # Deleted files are left out. An empty set is silence.
24
+ #
25
+ # RED. Exit 2 with the end of the check's output, which Claude Code hands back to the model so the turn goes on to
26
+ # fix it. Once per turn: Claude Code marks the next Stop of the same turn `stop_hook_active`, and that Stop passes.
27
+ #
28
+ # MODES: CREW_TURN_CHECK=off nothing runs. CREW_TURN_CHECK_TIMEOUT seconds the check may take (default 120, at
29
+ # most 170, under the hook's own 180). NOT A GATE ON ITS OWN FAILURE: an error of this script is silence (exit 0);
30
+ # the only exit 2 is a check that ran and failed.
31
+ set -uo pipefail
32
+ case "${CREW_TURN_CHECK:-on}" in off|OFF|0|false) exit 0 ;; esac
33
+ trap 'exit 0' EXIT
34
+ export LC_ALL=C
35
+ IN=""; IFS= read -r -d '' -t "${CREW_STDIN_TIMEOUT:-2}" IN 2>/dev/null; [ "$?" -gt 128 ] && exit 0
36
+
37
+ _TC_HERE="${BASH_SOURCE%/*}"; [ "$_TC_HERE" = "${BASH_SOURCE}" ] && _TC_HERE=.
38
+ P="${CLAUDE_PROJECT_DIR:-$PWD}"; cd "$P" 2>/dev/null || exit 0
39
+
40
+ # The declared command, read with the builtin `read`: a project that declared none pays no process at all.
41
+ FC=""
42
+ if [ -f CLAUDE.md ]; then
43
+ while IFS= read -r _l || [ -n "$_l" ]; do
44
+ case "$_l" in "Fast check (files):"*) FC="${_l#Fast check (files):}"; break ;; esac
45
+ done < CLAUDE.md
46
+ fi
47
+ FC="${FC%$'\r'}"; FC="${FC#"${FC%%[![:space:]]*}"}"; FC="${FC%"${FC##*[![:space:]]}"}"
48
+ case "$FC" in '`'*'`') FC="${FC#\`}"; FC="${FC%\`}" ;; esac
49
+ case "$FC" in ''|'<'*|none|None|NONE) exit 0 ;; esac
50
+
51
+ _field(){ # $1 = key -> _F, the payload's string value (letters, digits, - and _ only), "" when absent
52
+ local r="${IN#*\"$1\"}"; _F=""
53
+ [ "$r" = "$IN" ] && return 0
54
+ r="${r#"${r%%[![:space:]:]*}"}"; r="${r#\"}"; _F="${r%%\"*}"
55
+ case "$_F" in *[!A-Za-z0-9_-]*) _F="" ;; esac
56
+ }
57
+ _field hook_event_name; EV="$_F"
58
+ _field session_id; SID="$_F"; [ -n "$SID" ] || exit 0
59
+
60
+ GD="$(git rev-parse --absolute-git-dir 2>/dev/null)" || exit 0
61
+ SNAPD="$GD/crewforth-turn"; SNAP="$SNAPD/$SID"
62
+ _now(){ if [ "${BASH_VERSINFO[0]}" -ge 5 ] || { [ "${BASH_VERSINFO[0]}" -eq 4 ] && [ "${BASH_VERSINFO[1]}" -ge 2 ]; }; then printf -v NOW '%(%s)T' -1; else NOW="$(date +%s)"; fi; }
63
+
64
+ # Dirty paths, NUL-separated, from `status -z` (a rename's old name is dropped; a deleted file is dropped).
65
+ _dirty(){ # -> DIRTY array
66
+ DIRTY=()
67
+ local rec st path skip=0
68
+ while IFS= read -r -d '' rec; do
69
+ if [ "$skip" = 1 ]; then skip=0; continue; fi
70
+ st="${rec:0:2}"; path="${rec:3}"
71
+ case "$st" in R*|C*) skip=1 ;; esac
72
+ case "$st" in *D*) continue ;; esac
73
+ [ -f "$path" ] && DIRTY+=("$path")
74
+ done < <(git -c core.fsmonitor= status --porcelain=v1 -z --untracked-files=all 2>/dev/null)
75
+ }
76
+ _hashes(){ # paths in "$@" -> HASH array, one content id per path (git hash-object, one process)
77
+ HASH=()
78
+ [ "$#" -gt 0 ] || return 0
79
+ local h
80
+ while IFS= read -r h; do HASH+=("$h"); done < <(printf '%s\n' "$@" | git hash-object --stdin-paths 2>/dev/null)
81
+ }
82
+
83
+ if [ "$EV" = UserPromptSubmit ]; then
84
+ HEAD="$(git rev-parse -q --verify HEAD 2>/dev/null)"
85
+ _dirty; _hashes ${DIRTY[@]+"${DIRTY[@]}"}; _now
86
+ mkdir -p "$SNAPD" 2>/dev/null || exit 0
87
+ {
88
+ printf 'head\t%s\ntime\t%s\n' "${HEAD:--}" "$NOW"
89
+ i=0; while [ "$i" -lt "${#DIRTY[@]}" ]; do printf 'f\t%s\t%s\n' "${HASH[$i]:--}" "${DIRTY[$i]}"; i=$((i+1)); done
90
+ } > "$SNAP.tmp" 2>/dev/null && mv -f "$SNAP.tmp" "$SNAP" 2>/dev/null
91
+ exit 0
92
+ fi
93
+
94
+ [ "$EV" = Stop ] || exit 0
95
+ case "$IN" in *'"stop_hook_active":true'*|*'"stop_hook_active": true'*) exit 0 ;; esac
96
+ [ -f "$SNAP" ] || exit 0
97
+
98
+ # ---- the turn's files -----------------------------------------------------------------------------------------
99
+ # No associative arrays: macOS ships bash 3.2. The snapshot's dirty files are one newline-framed string of
100
+ # "<content id> <path>" lines, and a lookup is one `case`.
101
+ SH="-"; ST=0; BEFORE=$'\n'
102
+ while IFS=$'\t' read -r k a b; do
103
+ case "$k" in head) SH="$a" ;; time) ST="$a" ;; f) BEFORE="$BEFORE$a $b"$'\n' ;; esac
104
+ done < "$SNAP"
105
+ rm -f "$SNAP" 2>/dev/null
106
+ case "$ST" in ''|*[!0-9]*) ST=0 ;; esac
107
+
108
+ SEEN=$'\n'; FILES=()
109
+ _add(){
110
+ [ -n "$1" ] && [ -f "$1" ] || return 0
111
+ case "$SEEN" in *$'\n'"$1"$'\n'*) return 0 ;; esac
112
+ SEEN="$SEEN$1"$'\n'; FILES+=("$1")
113
+ }
114
+
115
+ _dirty; _hashes ${DIRTY[@]+"${DIRTY[@]}"}
116
+ i=0; while [ "$i" -lt "${#DIRTY[@]}" ]; do
117
+ p="${DIRTY[$i]}"
118
+ case "$BEFORE" in *$'\n'"${HASH[$i]:-x} $p"$'\n'*) ;; *) _add "$p" ;; esac
119
+ i=$((i+1))
120
+ done
121
+
122
+ HEAD="$(git rev-parse -q --verify HEAD 2>/dev/null)"
123
+ if [ -n "$HEAD" ] && [ "$SH" != - ] && [ "$HEAD" != "$SH" ]; then
124
+ # Only a turn whose every move of HEAD was a commit of its own counts its commits.
125
+ OURS=1; N=0
126
+ while IFS=$'\t' read -r sel subj; do
127
+ t="${sel#*@\{}"; t="${t%\}}"; case "$t" in *[!0-9]*|'') continue ;; esac
128
+ [ "$t" -ge "$ST" ] || break
129
+ N=$((N+1))
130
+ case "$subj" in "commit:"*|"commit (amend):"*|"commit (initial):"*|"cherry-pick:"*|"revert:"*) ;; *) OURS=0 ;; esac
131
+ done < <(git log -g --date=unix --format='%gd%x09%gs' -n 50 HEAD 2>/dev/null)
132
+ if [ "$OURS" = 1 ] && [ "$N" -gt 0 ]; then
133
+ while IFS= read -r -d '' p; do _add "$p"; done < <(git -c core.fsmonitor= diff --name-only -z --no-renames --diff-filter=d --no-ext-diff "$SH" "$HEAD" -- 2>/dev/null)
134
+ fi
135
+ fi
136
+ [ "${#FILES[@]}" -gt 0 ] || exit 0
137
+
138
+ # ---- the command ---------------------------------------------------------------------------------------------
139
+ CMD="$FC"
140
+ case "$FC" in *'<file>'*)
141
+ Q=""; n=0
142
+ for p in "${FILES[@]}"; do
143
+ case "$p" in *"'"*|*[\;\&\|\<\>\`\$]*|-*) continue ;; esac
144
+ Q="${Q:+$Q }'$p'"; n=$((n+1)); [ "$n" -ge 50 ] && break
145
+ done
146
+ [ -n "$Q" ] || exit 0
147
+ CMD="${FC//<file>/$Q}" ;;
148
+ esac
149
+
150
+ [ -f "$_TC_HERE/guard-agent-model.sh" ] || exit 0
151
+ . "$_TC_HERE/guard-agent-model.sh"
152
+ declare -F _am_verify_ok >/dev/null 2>&1 || exit 0
153
+ _am_state
154
+ _field permission_mode; PM="${_F:-default}"
155
+ if ! _am_verify_ok "$CMD" "$PM"; then
156
+ # Said once per session: a check that never runs must not look like one that passes.
157
+ if mkdir -p "$SNAPD" 2>/dev/null && [ ! -e "$SNAPD/$SID.said" ] && : > "$SNAPD/$SID.said" 2>/dev/null; then
158
+ W="${_AMVW//\\/\\\\}"; W="${W//\"/\\\"}"
159
+ printf '{"systemMessage":"crew: the fast check of this turn was not run: %s. CLAUDE.md declares it under Fast check (files)."}\n' "$W"
160
+ fi
161
+ exit 0
162
+ fi
163
+
164
+ LIM="${CREW_TURN_CHECK_TIMEOUT:-120}"; case "$LIM" in ''|*[!0-9]*) LIM=120 ;; esac; [ "$LIM" -le 170 ] || LIM=170
165
+ OUTF="$SNAPD/$SID.out"; mkdir -p "$SNAPD" 2>/dev/null || exit 0
166
+ if command -v setsid >/dev/null 2>&1; then ( exec setsid bash -c "$CMD" ) > "$OUTF" 2>&1 </dev/null &
167
+ else set -m; ( exec bash -c "$CMD" ) > "$OUTF" 2>&1 </dev/null & set +m; fi
168
+ VPID=$!; W=0; RC=""
169
+ while kill -0 "$VPID" 2>/dev/null; do
170
+ if [ "$W" -ge "$LIM" ]; then
171
+ kill -TERM -- "-$VPID" 2>/dev/null; kill -TERM "$VPID" 2>/dev/null; sleep 1
172
+ kill -KILL -- "-$VPID" 2>/dev/null; kill -KILL "$VPID" 2>/dev/null; RC=timeout; break
173
+ fi
174
+ sleep 1; W=$((W+1))
175
+ done
176
+ if [ "$RC" = timeout ]; then
177
+ wait "$VPID" 2>/dev/null; rm -f "$OUTF" 2>/dev/null
178
+ printf '{"systemMessage":"crew: the fast check of this turn did not finish in %ss and was stopped; nothing is concluded from it."}\n' "$LIM"
179
+ exit 0
180
+ fi
181
+ wait "$VPID" 2>/dev/null; RC=$?
182
+ TAIL="$(tail -n 20 "$OUTF" 2>/dev/null | cut -c1-300)"; rm -f "$OUTF" 2>/dev/null
183
+ [ "$RC" = 0 ] && exit 0
184
+ {
185
+ printf 'The fast check this project declares failed on the files this turn changed (exit %s): %s\n' "$RC" "$CMD"
186
+ printf 'The end of its output:\n%s\n' "$TAIL"
187
+ printf 'Fix what it reports in those files, or say to the user why it stays red. This is said once per turn.\n'
188
+ } >&2
189
+ trap - EXIT
190
+ exit 2
@@ -1,10 +1,10 @@
1
- # Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.3.0 (below VERSION 3.4.0) — do not edit by hand.
1
+ # Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.4.0 (below VERSION 3.5.0) — do not edit by hand.
2
2
  # The flat files Crewforth owns: a copy whose bytes (CR aside) are one of these ids was never edited.
3
3
  # install_path blob_sha1 first_tag last_tag
4
4
  .claude/AGENT_TEMPLATE.md 075e2c26706c06f56af01652cd24c36918513bee v3.0.0 v3.0.4
5
5
  .claude/AGENT_TEMPLATE.md 2c8223d811245e3627a3011bb64fb9e4b0bf3b94 v3.2.0 v3.2.1
6
6
  .claude/AGENT_TEMPLATE.md 3fdc551d221ee3665b21ef8cc3563de861a9abe6 v1.4.0 v1.6.3
7
- .claude/AGENT_TEMPLATE.md 59d3af63b0480696dec38136e124bd582066fb16 v3.3.0 v3.3.0
7
+ .claude/AGENT_TEMPLATE.md 59d3af63b0480696dec38136e124bd582066fb16 v3.3.0 v3.4.0
8
8
  .claude/AGENT_TEMPLATE.md 686fed306c873b45c245c77a95a8e6de6ee383d5 v1.7.0 v2.11.0
9
9
  .claude/AGENT_TEMPLATE.md 7ab19417b6ff3ac732e2c894be17ffb3ec091f7d v3.1.0 v3.1.1
10
10
  .claude/AGENT_TEMPLATE.md 981df9ea27b6f1c5ab5cb509d40875a07603858d v2.12.0 v2.13.0
@@ -30,7 +30,7 @@
30
30
  .claude/DISCIPLINE.md b7e1066451a8590e0a2607ad0a6fe80b21599c53 v1.1.3 v1.1.8
31
31
  .claude/DISCIPLINE.md b90a067ee323a08146862b889293f8feb49f8e7e v3.1.0 v3.1.1
32
32
  .claude/DISCIPLINE.md bf062c2b51a9cf3cc4d0caac389c148cf6fdc977 v2.12.0 v2.12.0
33
- .claude/DISCIPLINE.md c04f53866b84c54aa0e60089cb54cdc7c9aa8df1 v3.3.0 v3.3.0
33
+ .claude/DISCIPLINE.md c04f53866b84c54aa0e60089cb54cdc7c9aa8df1 v3.3.0 v3.4.0
34
34
  .claude/DISCIPLINE.md c693f1bd5832ce570058c6eedcacb5dff13d89e8 v1.10.0 v1.10.0
35
35
  .claude/DISCIPLINE.md cf881a5229d85850194a98b8d0d0438a8eaaa9d6 v3.2.0 v3.2.1
36
36
  .claude/DISCIPLINE.md d63a01a183c9a80d739c840a511d338dc2765820 v1.4.0 v1.4.4
@@ -47,4 +47,4 @@
47
47
  .claude/README.md 97d4dd1f7f1695052cc1d06cc43f5bcdc603c989 v1.0.0 v1.0.4
48
48
  .claude/README.md a3419891cfda7385564008e4befc1007bc5364cb v1.1.1 v1.7.0
49
49
  .claude/README.md a3b76e0fa31267610d28b742c122a962f6db59c4 v3.0.1 v3.0.4
50
- .claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.3.0
50
+ .claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.4.0
package/kit/settings.json CHANGED
@@ -152,6 +152,12 @@
152
152
  "shell": "bash",
153
153
  "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/route-hint.sh",
154
154
  "timeout": 60
155
+ },
156
+ {
157
+ "type": "command",
158
+ "shell": "bash",
159
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/turn-check.sh",
160
+ "timeout": 30
155
161
  }
156
162
  ]
157
163
  }
@@ -170,6 +176,18 @@
170
176
  "shell": "bash",
171
177
  "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/prompt-approval.sh",
172
178
  "timeout": 60
179
+ },
180
+ {
181
+ "type": "command",
182
+ "shell": "bash",
183
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/map-stale.sh",
184
+ "timeout": 30
185
+ },
186
+ {
187
+ "type": "command",
188
+ "shell": "bash",
189
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/turn-check.sh",
190
+ "timeout": 180
173
191
  }
174
192
  ]
175
193
  }
@@ -242,6 +260,41 @@
242
260
  "timeout": 60
243
261
  }
244
262
  ]
263
+ },
264
+ {
265
+ "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
266
+ "hooks": [
267
+ {
268
+ "type": "command",
269
+ "shell": "bash",
270
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/loop-guard.sh",
271
+ "timeout": 30
272
+ }
273
+ ]
274
+ },
275
+ {
276
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
277
+ "hooks": [
278
+ {
279
+ "type": "command",
280
+ "shell": "bash",
281
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/map-stale.sh",
282
+ "timeout": 30
283
+ }
284
+ ]
285
+ }
286
+ ],
287
+ "PostToolUseFailure": [
288
+ {
289
+ "matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
290
+ "hooks": [
291
+ {
292
+ "type": "command",
293
+ "shell": "bash",
294
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/loop-guard.sh",
295
+ "timeout": 30
296
+ }
297
+ ]
245
298
  }
246
299
  ],
247
300
  "PreCompact": [
@@ -291,6 +344,17 @@
291
344
  }
292
345
  ]
293
346
  },
347
+ {
348
+ "matcher": "startup|resume",
349
+ "hooks": [
350
+ {
351
+ "type": "command",
352
+ "shell": "bash",
353
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/branch-fresh.sh",
354
+ "timeout": 30
355
+ }
356
+ ]
357
+ },
294
358
  {
295
359
  "matcher": "startup",
296
360
  "hooks": [
@@ -8,9 +8,9 @@ description: |
8
8
  # Accessibility (a11y)
9
9
 
10
10
  <!-- routing-eval reads the next line; why it sits in the body: AGENT_TEMPLATE.md -->
11
- Trigger phrases: "a11y", "accessibility", "WCAG", "screen reader", "keyboard navigation", "contrast", "ARIA", "keyboard only", "with a keyboard", "keyboard focus", "without a mouse", "focus ring", "tab order", "alt text", "color blind", "colour blind", "voiceover", "talkback", "reduced motion"
11
+ Trigger phrases: "a11y", "accessibility", "WCAG", "screen reader", "keyboard navigation", "contrast", "ARIA", "keyboard only", "with a keyboard", "keyboard focus", "without a mouse", "focus ring", "tab order", "alt text", "color blind", "colour blind", "voiceover", "talkback", "reduced motion", "target size", "focus not obscured", "focus is hidden", "WCAG 2.2"
12
12
 
13
- Goal: make the interface usable by **everyone**, including keyboard, screen reader, and low vision. Baseline target: **WCAG 2.1 AA**.
13
+ Goal: make the interface usable by **everyone**, including keyboard, screen reader, and low vision. Baseline target: **WCAG 2.2 AA**.
14
14
  Stack-agnostic (web/React/RN); do a web search when needed for framework-specific APIs.
15
15
 
16
16
  ## Checklist
@@ -21,9 +21,56 @@ Stack-agnostic (web/React/RN); do a web search when needed for framework-specifi
21
21
  - [ ] **Alt text**: `alt` on meaningful images; `alt=""` on decorative images
22
22
  - [ ] **Forms**: every input has a `label`; error message programmatically linked (`aria-describedby`)
23
23
  - [ ] **ARIA**: only when needed; wrong ARIA is worse than no ARIA; role/name/state correct
24
- - [ ] **Motion/animation**: respect `prefers-reduced-motion`
24
+ - [ ] **Motion/animation**: respect `prefers-reduced-motion`; anything that moves for more than 5 s can be paused (2.2.2)
25
25
  - [ ] **Language**: `<html lang>` correct (coordinate with i18n)
26
26
 
27
+ WCAG 2.2 added these; a 2.1 checklist misses all of them:
28
+ - [ ] **Focus not obscured** (2.4.11): a focused element is never fully hidden under a sticky header, footer or banner
29
+ - [ ] **Dragging** (2.5.7): every drag has a single-pointer alternative (buttons, a menu)
30
+ - [ ] **Target size** (2.5.8): pointer targets ≥ 24×24 CSS px, or spaced so a 24 px circle on each does not overlap another
31
+ - [ ] **Redundant entry** (3.3.7): information already given in the same flow is filled in or selectable, not asked again
32
+ - [ ] **Accessible authentication** (3.3.8): no memory or puzzle test to sign in without an alternative; paste and
33
+ password managers work
34
+
35
+ Commonly missed, all checkable in the markup:
36
+ - [ ] **Autocomplete** (1.3.5): personal-data inputs carry the right `autocomplete` token
37
+ - [ ] **No paste blocking** on any input, passwords and confirmations included
38
+ - [ ] **Submit stays enabled**: a disabled-until-valid button hides why; validate on submit and say what is wrong
39
+ - [ ] **Zoom and reflow** (1.4.4, 1.4.10): usable at 200% text and at 320 CSS px wide; no `maximum-scale=1` or
40
+ `user-scalable=no` in the viewport meta
41
+ - [ ] **Skip link** (2.4.1): the first focusable element jumps past repeated navigation to `main`
42
+ - [ ] **No flashing** (2.3.1): nothing flashes more than three times a second
43
+ - [ ] **Error summary**: a form that fails lists its errors in one focusable block that links to each field
44
+ - [ ] **No positive `tabindex`**: `0` and `-1` only; order comes from the DOM
45
+
46
+ **Criteria, not conventions.** A failed A or AA criterion, or a task a keyboard or screen-reader user cannot finish,
47
+ is a finding. AAA, an ARIA Authoring Practices pattern, or a 44 px target where 24 px passes is a recommendation:
48
+ say so, and never rank it with a finding.
49
+
50
+ ## Before you finish: grep for these
51
+ | Pattern in the diff | Why it fails |
52
+ |---|---|
53
+ | `outline: none` / `outline: 0` with no `:focus-visible` rule | focus becomes invisible (2.4.7) |
54
+ | `<div onClick` / `<span onClick` with no role and key handler | not reachable by keyboard (2.1.1) |
55
+ | `tabindex="1"` or higher | breaks the reading order (2.4.3) |
56
+ | `disabled={!isValid}` on a submit | hides why the form cannot be sent |
57
+ | a live region rendered together with its text | many readers announce nothing: render it empty first |
58
+ | `maximum-scale=1`, `user-scalable=no` | blocks zoom (1.4.4) |
59
+ | `aria-hidden="true"` on an element that holds something focusable | focus lands on nothing the reader can name |
60
+ | `<img` with no `alt` | no text alternative (1.1.1) |
61
+ | `onDrag`/`draggable` with no button or menu path | no single-pointer alternative (2.5.7) |
62
+
63
+ ## Symptom → cause → fix
64
+ | Symptom | Usual cause | Fix |
65
+ |---|---|---|
66
+ | Reader says "button" and nothing else | icon-only button with no name | `aria-label`, or visually hidden text |
67
+ | Tab skips an element | a `div` handling clicks | a real `button` / `a` |
68
+ | Focus lost after a modal closes | focus not returned | return it to the element that opened the modal |
69
+ | An error appears and nothing is announced | message not tied to the field or a live region | `aria-describedby` on the field; a status region for the summary |
70
+ | Layout breaks at 200% zoom | fixed heights or widths in px | let text boxes grow; `min-width: 0` on flex and grid children |
71
+
72
+ Recipes for focus, forms and announcements (which channel says what, in order): `references/recipes.md`.
73
+
27
74
  ## How
28
75
  1. **Start with semantics** — the right element solves 80%. Use `button` instead of `role="button"`+`div`.
29
76
  2. **Navigate with the keyboard** — drop the mouse, run the whole flow with Tab/Shift-Tab/Enter/Escape; is focus visible and its order sensible.
@@ -0,0 +1,41 @@
1
+ # Accessibility recipes: focus, forms, announcements
2
+
3
+ Loaded from the `a11y` skill when a change moves focus, validates a form or tells the user something happened.
4
+ Stack-neutral; the React Native and Flutter equivalents are named in the skill.
5
+
6
+ ## Focus
7
+ - **Where focus goes after an action.** Opening a dialog: to its first meaningful control, or to the dialog itself
8
+ when it starts with text. Closing it: back to the control that opened it. Deleting the focused row: to the next
9
+ row, or the previous one when it was the last. A route change: to the new page's `h1` (`tabindex="-1"`), or to a
10
+ skip target, so the reader starts at the content and not at the top of the navigation.
11
+ - **Visible focus.** Style `:focus-visible`, never remove `outline` without a replacement of at least 2 px with 3:1
12
+ contrast against what surrounds it. A sticky header must not cover the focused element: set `scroll-padding-top`
13
+ to its height.
14
+ - **Traps.** Only a modal traps focus, and Escape always leaves it. `inert` on the background is the simplest trap
15
+ where it is supported.
16
+
17
+ ## Forms
18
+ - Every field has a visible `label` tied by `for`/`id`; a placeholder is not a label.
19
+ - Validate on submit (and on blur for a field the user has left), not on every keystroke.
20
+ - On a failed submit: put a summary at the top that names how many errors there are and links to each field, move
21
+ focus to it, and tie each field's message to the field with `aria-describedby` and `aria-invalid="true"`.
22
+ - Keep the submit button enabled; a disabled button cannot be focused and does not say what is missing.
23
+ - Required fields say so in text, not only with a colour or an asterisk the reader skips.
24
+ - Known personal data carries its `autocomplete` token (`name`, `email`, `tel`, `street-address`, `one-time-code`,
25
+ `current-password`, `new-password`).
26
+
27
+ ## Announcements: the cheapest channel that works
28
+ Use the first rung that fits; each one further down interrupts more.
29
+ 1. **Move focus** when the user must act next (a dialog opened, an error summary).
30
+ 2. **`aria-describedby`** when the message belongs to one control (a field hint, a field error).
31
+ 3. **`role="status"`** (polite) for a result the user should hear without being interrupted: "Saved", "3 results".
32
+ 4. **`role="alert"`** (assertive) only for something that cannot wait: a session about to expire, a failed payment.
33
+
34
+ Rules that make the live regions work:
35
+ - Render the region **empty** when the page loads and change its text later. A region that arrives together with its
36
+ text is often not announced.
37
+ - One polite region per page is usually enough; reuse it instead of adding one per component.
38
+ - Do not announce what focus already says. Moving focus to an error summary and also putting it in an alert reads it
39
+ twice.
40
+ - Toasts that disappear on their own need a status region and enough time to read (at least 5 s, longer for longer
41
+ text), or a way to bring them back.
@@ -34,7 +34,14 @@ every teammate's next session opening, and it travels even when the board lives
34
34
  file stays the long form; the board entry is the part that has to reach people, so it must be readable on its
35
35
  own — a title nobody can act on ("auth decided") is not a record.
36
36
 
37
- ## Format (docs/adr/NNNN-short-title.md, ~1 page)
37
+ ## Where it goes: the project's scheme first
38
+ Before writing, look for records that already exist: a decisions folder (`docs/adr/`, `doc/adr/`,
39
+ `docs/decisions/`, `Documentation/Decisions/`, `architecture/decisions/`), an `.adr-dir` file (adr-tools), or files
40
+ named like `ADR-004-…` or `0004-…`. Found: continue it, same folder, extension (.md, .rst, .adoc), numbering and
41
+ heading style, next number. Not found: `docs/adr/NNNN-short-title.md` below. Never start a second scheme beside one
42
+ that exists; two numbering lines make every "see ADR 12" ambiguous.
43
+
44
+ ## Format (fallback: docs/adr/NNNN-short-title.md, ~1 page)
38
45
  ```
39
46
  # NNNN. <Decision title>
40
47
  Status: proposed | accepted | rejected | superseded (by NNNN)
@@ -46,11 +53,15 @@ What was decided (clear, one sentence + rationale)?
46
53
  Pros / cons / accepted trade-offs.
47
54
  ## Alternatives considered
48
55
  Why were they not chosen?
56
+ Verified against: <package>@<version> · <docs or source> · <date> — or "none" (no external fact relied on)
49
57
  ```
50
58
 
51
59
  ## Principles
52
60
  - **Invariant:** a new decision marks the old ADR as `superseded`; an ADR is **never deleted** (decision history is preserved).
53
61
  - Numbered and dated; keep it short.
62
+ - **A number a test enforces moves with its ADR.** When a test or gate pins a figure an ADR set (a cap, a count, a
63
+ budget), the change that moves the figure amends that ADR in the same commit; a figure that drifts from its record
64
+ leaves the next reader two answers.
54
65
 
55
66
  ## Deliberately bypassing a rule is a decision too
56
67
  A gate the user knowingly overrode — a deferred DoD item, an accepted known gap, a trimmed scope — belongs here
@@ -67,7 +78,8 @@ Record it in the same file as any other decision, with the fields that make it r
67
78
  ```
68
79
 
69
80
  `revisit` is the load-bearing field. A bypass with no condition attached is permanent by default, and nobody
70
- chose permanence. See [[confidence-check]], which is where most bypasses surface.
81
+ chose permanence. See [[confidence-check]], which is where most bypasses surface. A recorded bypass is the user's decision, not a
82
+ deferral (`CLAUDE.md`, "No deferral").
71
83
 
72
84
  ## DoD
73
85
  - Decision + rationale + rejected alternatives recorded; status current.
@@ -20,7 +20,8 @@ public API is a commitment; a breaking change is expensive. Stack-agnostic (REST
20
20
  - [ ] A clear **versioning** strategy (URL `/v1` or header); a breaking change means a new version
21
21
  - [ ] **Pagination/filtering/sorting** defined and consistent on large collections
22
22
  - [ ] **Backward compatibility**: adding a field is additive; removing a field or changing its meaning is breaking → version
23
- - [ ] **Idempotency** (for POST/payment-like cases) supported via a key when needed
23
+ - [ ] **Idempotency** (for POST/payment-like cases) supported via a key when needed: the key, its atomic claim and
24
+ the answers to a replay are in **`references/idempotency.md`**
24
25
  - [ ] The contract is documented in **OpenAPI**; example request/response present (coordinate with `docs-writer`)
25
26
 
26
27
  ## How
@@ -42,6 +43,10 @@ public API is a commitment; a breaking change is expensive. Stack-agnostic (REST
42
43
  | A new optional parameter | Add a required parameter |
43
44
  | A new enum value (if the consumer is tolerant) | Change a type/meaning, change a status code |
44
45
 
46
+ **Observable behaviour is the contract,** not only the documented fields: someone depends on the error text, the
47
+ order of a list, the default page size and how long a call takes. Changing one of those is breaking for them; say
48
+ it in the changelog like a field change.
49
+
45
50
  ## Invariant rules
46
51
  1. **A public API is a commitment** — a breaking change is not made silently; version + announcement.
47
52
  2. **Consistency > local cleverness** — a single naming/error/pagination pattern across the whole API.
@@ -0,0 +1,39 @@
1
+ # Idempotency keys
2
+
3
+ A retried POST that charges twice, ships twice or creates two rows is the bug this prevents. The key makes a retry
4
+ safe; the store behind it decides whether it really is.
5
+
6
+ ## The key
7
+ - **From the intent, not the request.** The client sends `Idempotency-Key` (a UUID made once per user action, kept
8
+ across its retries), or the server derives one from what the action means (order id + operation). A key made per
9
+ HTTP attempt protects nothing: every retry is new.
10
+ - **Scoped to the caller.** Store it per account or API client, so two callers cannot collide or read each other's
11
+ result.
12
+
13
+ ## Claiming it
14
+ - **Atomically.** Insert the key under a unique constraint (or an equivalent conditional write) before the side
15
+ effect runs. "Look it up, then insert" leaves a window where two retries both find nothing and both charge.
16
+ - **With a fingerprint of the payload.** Store a hash of the normalised body with the key.
17
+
18
+ ## Answers
19
+ | The request | The answer |
20
+ |---|---|
21
+ | New key | Run it; store the outcome with the key |
22
+ | Same key, same payload, finished | The stored response, status code included, without running it again |
23
+ | Same key, different payload | `422` (or `409`): a client bug, never a silent replay of the first result |
24
+ | Same key, first attempt still running | `409` with `Retry-After`, or wait briefly, or `202` with a status URL; never a second run |
25
+
26
+ Three outcomes are stored: **succeeded** (replay it), **failed in a way the client can fix** (replay the 4xx), and
27
+ **failed before or during the side effect** (5xx, timeout): release the key, or mark it retryable, so the next
28
+ retry can run. Storing a 5xx as final turns a passing outage into a permanent failure for that action.
29
+
30
+ ## Retention
31
+ Keep a key longer than the longest retry chain any client runs (its backoff schedule and the queue's redelivery
32
+ window), and say how long in the API docs. A key dropped after one hour is useless to a client that retries for a
33
+ day.
34
+
35
+ ## Review checks
36
+ - The side effect and the key claim are in one transaction, or the key is claimed first and the effect is
37
+ conditional on it.
38
+ - A downstream call (payment provider, email) gets its own idempotency key, derived from ours.
39
+ - A test sends the same key twice in parallel and finds one effect.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: brainstorm
3
3
  description: |
4
- Divergent discovery BEFORE planning: turn a fuzzy ask into 2–4 scoped options + named unknowns, pick a
5
- direction, hand to spec-planning. Bounded; converges to explicit choices, never guesses.
4
+ Use when the ask is still fuzzy and no direction is chosen yet: "not sure what we want", several ways to go,
5
+ or a scope to settle before any plan is written.
6
6
  ---
7
7
 
8
8
  # Brainstorm — diverge before you converge
@@ -27,6 +27,8 @@ option into tasks). If scope is already clear, skip this and go straight to [[sp
27
27
  4. **Converge — ask, don't guess.** Present the directions as **explicit numbered options** and let the user
28
28
  choose (CLAUDE.md discipline: a decision point = multiple choice, never a prose either/or). Record the
29
29
  chosen direction + the rejected ones with the one-line reason they lost.
30
+ How to put a question, what is worth asking and what to do when no answer comes:
31
+ `references/asking.md`; `/crew-interview` runs the whole round of questions as a command.
30
32
  5. **Hand off.** The chosen direction + resolved constraints flow into `spec-planning`; a lasting
31
33
  architectural choice made here is recorded with [[adr]].
32
34
 
@@ -0,0 +1,47 @@
1
+ # Asking the user — what is worth a question, and how to put it
2
+
3
+ The discipline says to ask at every decision point with `AskUserQuestion`. This file says what a decision point
4
+ is, and how to put one so the answer settles something.
5
+
6
+ ## Facts are yours, decisions are theirs
7
+ - **Never ask for a fact the workspace, the docs or the web can answer**: the ORM, the test runner, the Node
8
+ version, where a file lives. Read it, then state it.
9
+ - If the options differ only in how the code does it, and a user would see the same behaviour either way, it is
10
+ not their decision. Pick, and say what you picked.
11
+ - A decision is theirs when it changes what someone sees or pays: scope, behaviour, data kept or shown, cost, risk,
12
+ a trade-off between two things they want.
13
+
14
+ ## Is it worth asking?
15
+ A question earns its place when all three hold:
16
+ 1. **The answer changes the outcome.** If every answer leads to the same next step, do not ask.
17
+ 2. **It is about this work**, not a principle ("do you value maintainability?").
18
+ 3. **It is not asked already.** Never ask again what was answered in this session or recorded in the project.
19
+
20
+ Where to look for the questions that matter: the empty state, the error state, two people at once, existing data
21
+ when the shape changes, and the default when nobody chooses. When two things the user said pull against each
22
+ other, quote both and ask which wins.
23
+
24
+ ## Putting the question
25
+ - **One `AskUserQuestion` call, at most 4 questions, and only questions that do not depend on each other.** A
26
+ question whose options depend on another answer waits for the next call.
27
+ - **Options are behaviour, not mechanics.** "A saved draft survives closing the tab" beats "use localStorage".
28
+ - **Recommended option first**, marked as recommended, with a one-line reason. The user should be able to agree
29
+ in one click.
30
+ - A concrete case beats an abstract choice: "a refund on an order shipped in two parts" is answerable;
31
+ "refund granularity" is not.
32
+
33
+ ## Confirming an inference
34
+ When you can infer the answer, do not ask an open question: state the inference and let the user confirm it.
35
+ The statement is the question: "Looks right" / "Off base" (and what to change). Read the answer four ways: a
36
+ yes, a no, a correction (take the correction, not your inference), or a new constraint (add it, then re-check
37
+ what it changes).
38
+
39
+ ## When the answer does not come
40
+ - **A skipped question becomes a recorded assumption.** Write it down where the plan or brief keeps its
41
+ assumptions, so it can be revisited, and go on.
42
+ - **A whole round skipped means stop asking.** Proceed on the recorded assumptions and say so.
43
+ - **"I don't know"** means the question was framed wrong. Re-ask it from what the user would see, not from how it
44
+ is built.
45
+ - **No one can answer** (`auto` mode with no person attending, `/loop`, a scheduled or headless run): do not
46
+ interview. Take the recommended option for each open decision, record it as an assumption, and list the
47
+ assumptions at the top of what you hand over.
@@ -19,9 +19,19 @@ Trigger phrases: "ci", "pipeline", "github actions", "jenkins", "build pipeline"
19
19
 
20
20
  ## Principles
21
21
  - **Deterministic:** dependencies pinned, cache keyed correctly; no "it worked on my machine".
22
- - **Secret management:** CI secret store; NO plaintext secrets in the repo/logs (overlaps with trace scan).
22
+ - **Actions and the token:** pin every third-party action to a full commit SHA with its version in a comment (a tag
23
+ moves when its owner moves it); `permissions: contents: read` at the top, raised per job only where a step writes;
24
+ `persist-credentials: false` on checkout unless a later step pushes; never put `${{ github.event.* }}` or a matrix
25
+ value inside `run:` — pass it through `env:` and quote it.
26
+ - **Secret management:** CI secret store; NO plaintext secrets in the repo/logs (overlaps with trace scan). Log
27
+ masking is a filter on exact strings: an encoded, split or transformed value prints in clear, so a secret never
28
+ reaches a step that echoes its environment. Prefer short-lived OIDC credentials over stored keys and ephemeral
29
+ runners over reused ones; a cache restored into a job is input, so key it on what produced it and never on a
30
+ value a fork controls.
23
31
  - **PR gate:** quality gate + tests must pass; a red build is not merged.
24
32
  - **Branch protection:** direct push to main is disabled; PR + review required.
33
+ - **An agent job in CI** (one that fixes, updates or triages with nobody watching): pinned, few tools, an isolated
34
+ copy, an allowlist for what comes back: **`references/agent-job.md`**.
25
35
 
26
36
  ## When a stage fails
27
37
  1. **Fetch the failing job's log first** — `gh run view --log-failed`, `glab ci trace`, `jenkins-cli console <job>`,