crewforth 3.1.1 → 3.2.0-rc.10

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 (110) hide show
  1. package/CHANGELOG.md +323 -0
  2. package/README.md +6 -3
  3. package/VERSION +1 -1
  4. package/adopt.sh +66 -6
  5. package/bin/cli.js +3 -2
  6. package/kit/AGENT_TEMPLATE.md +2 -1
  7. package/kit/CLAUDE.md +15 -14
  8. package/kit/agents/crew-commit-agent.md +11 -4
  9. package/kit/agents/crew-docs-agent.md +71 -0
  10. package/kit/agents/crew-planner.md +2 -0
  11. package/kit/agents/crew-refuter.md +77 -0
  12. package/kit/agents/crew-review-agent.md +2 -0
  13. package/kit/agents/crew-security-expert.md +3 -1
  14. package/kit/agents/crew-session-manager.md +11 -9
  15. package/kit/eval/doctor.sh +128 -6
  16. package/kit/eval/gate-report.sh +2 -0
  17. package/kit/eval/golden-routing.txt +9 -0
  18. package/kit/eval/lib/crew-map/crew-map.sh +26 -0
  19. package/kit/eval/lib/crew-map/detect.mjs +238 -0
  20. package/kit/eval/lib/crew-map/narrative.mjs +132 -0
  21. package/kit/eval/lib/crew-map/parse.mjs +305 -0
  22. package/kit/eval/lib/crew-map/project.mjs +164 -0
  23. package/kit/eval/lib/crew-map/rules/names.json +38 -0
  24. package/kit/eval/lib/crew-map/rules/packages.json +142 -0
  25. package/kit/eval/lib/crew-map/rules/python-stdlib.json +197 -0
  26. package/kit/eval/lib/crew-map/rules/tech.json +35 -0
  27. package/kit/eval/lib/crew-map/scan.mjs +266 -0
  28. package/kit/eval/lib/crew-map/stacks.mjs +547 -0
  29. package/kit/eval/lib/crew-map/surface.mjs +535 -0
  30. package/kit/eval/lib/findings/findings.mjs +410 -0
  31. package/kit/eval/lib/findings/findings.schema.json +122 -0
  32. package/kit/eval/lib/findings/findings.sh +29 -0
  33. package/kit/eval/lib/gate-log.sh +52 -0
  34. package/kit/eval/lib/json-string.awk +27 -0
  35. package/kit/eval/lib/preset.sh +109 -0
  36. package/kit/eval/lib/presets/default.json +7 -0
  37. package/kit/eval/lib/presets/solo.json +7 -0
  38. package/kit/eval/lib/presets/strict.json +11 -0
  39. package/kit/eval/lib/preview.sh +150 -0
  40. package/kit/eval/lib/review-bench/bench.mjs +273 -0
  41. package/kit/eval/lib/review-prepass/prepass.mjs +203 -0
  42. package/kit/eval/lib/review-prepass/prepass.sh +23 -0
  43. package/kit/eval/lib/settings-default.sh +36 -0
  44. package/kit/eval/lib/settings-json.awk +19 -4
  45. package/kit/eval/lib/star.sh +23 -7
  46. package/kit/eval/lib/statusline.settings.json +6 -0
  47. package/kit/eval/lib/statusline.sh +100 -0
  48. package/kit/eval/lib/verify-cmd.sh +17 -0
  49. package/kit/eval/lib/version.sh +41 -0
  50. package/kit/eval/review-bench.sh +26 -0
  51. package/kit/eval/smoke-test.sh +878 -53
  52. package/kit/hooks/agent-outcome.sh +44 -12
  53. package/kit/hooks/board-sync.sh +2 -23
  54. package/kit/hooks/guard-agent-model.sh +177 -24
  55. package/kit/hooks/guard-bash.sh +295 -204
  56. package/kit/hooks/guard-commit-scan.sh +10 -2
  57. package/kit/hooks/guard-question.sh +73 -0
  58. package/kit/hooks/guard-write.sh +24 -7
  59. package/kit/hooks/precompact-snapshot.sh +75 -0
  60. package/kit/hooks/prompt-approval.sh +389 -103
  61. package/kit/hooks/refuter-ledger.sh +80 -0
  62. package/kit/hooks/session-rehydrate.sh +25 -2
  63. package/kit/hooks/session-update-check.sh +9 -8
  64. package/kit/hooks/trace-blocklist.txt +9 -0
  65. package/kit/owned-blobs.tsv +4 -4
  66. package/kit/settings.json +51 -0
  67. package/kit/skills/crew-approve/SKILL.md +7 -0
  68. package/kit/skills/crew-code-review/SKILL.md +16 -0
  69. package/kit/skills/crew-doctor/SKILL.md +5 -1
  70. package/kit/skills/crew-handoff/SKILL.md +3 -2
  71. package/kit/skills/crew-map/SKILL.md +55 -0
  72. package/kit/skills/crew-review/SKILL.md +4 -1
  73. package/kit/skills/crew-ship/SKILL.md +3 -1
  74. package/kit/skills/crew-update/SKILL.md +1 -1
  75. package/kit/skills/incident-runbook/SKILL.md +2 -2
  76. package/kit/skills/security-scan/references/verify.md +6 -3
  77. package/kit/studio/README.md +25 -2
  78. package/kit/studio/server/index.js +187 -21
  79. package/kit/studio/server/lib/code-stamp.js +35 -0
  80. package/kit/studio/server/lib/crew-map.js +113 -0
  81. package/kit/studio/server/lib/graph.js +9 -1
  82. package/kit/studio/server/lib/kit-telemetry.js +30 -5
  83. package/kit/studio/server/lib/lan.js +87 -0
  84. package/kit/studio/server/lib/model-outcomes.js +130 -15
  85. package/kit/studio/server/lib/owned-stream.js +56 -0
  86. package/kit/studio/server/lib/pricing.js +3 -0
  87. package/kit/studio/server/lib/projects.js +11 -4
  88. package/kit/studio/server/lib/redact.js +60 -0
  89. package/kit/studio/server/lib/remote.js +185 -0
  90. package/kit/studio/server/lib/session.js +1 -1
  91. package/kit/studio/server/lib/terminal.js +51 -13
  92. package/kit/studio/server/lib/transcript.js +32 -0
  93. package/kit/studio/server/lib/usage.js +74 -14
  94. package/kit/studio/web/app.js +287 -17
  95. package/kit/studio/web/canvas.js +58 -122
  96. package/kit/studio/web/chat.js +114 -24
  97. package/kit/studio/web/graph-plan.js +13 -6
  98. package/kit/studio/web/index.html +10 -5
  99. package/kit/studio/web/liveness.js +14 -0
  100. package/kit/studio/web/map-plan.js +410 -0
  101. package/kit/studio/web/map.js +596 -0
  102. package/kit/studio/web/models-plan.js +61 -7
  103. package/kit/studio/web/models.js +19 -3
  104. package/kit/studio/web/qr.js +200 -0
  105. package/kit/studio/web/style.css +165 -0
  106. package/kit/studio/web/tokens.css +6 -0
  107. package/kit/studio/web/usage.js +12 -0
  108. package/kit/studio/web/viewport.js +160 -0
  109. package/package.json +6 -5
  110. package/start.sh +24 -1
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # refuter-ledger.sh - the record of what crew-refuter concluded, written by the hook and not by the session.
3
+ #
4
+ # WHY. A findings document can say a finding was verified. That sentence is the session's own; the ledger is not.
5
+ # When a crew-refuter agent stops, this hook reads the TASK it was given (the first record of its own transcript)
6
+ # and the ANSWER it ended with (last_assistant_message), and adds one line when the two agree on the finding and
7
+ # the lens:
8
+ #
9
+ # ts session agent_id finding lens verdict .claude/state/crew-refuter.tsv
10
+ #
11
+ # kit/eval/lib/findings/findings.mjs `validate --ledger` holds a document to it: a medium-or-higher finding needs
12
+ # verifiers, and every verifier it names must be a line here, with the same finding, lens and verdict.
13
+ #
14
+ # Wired to SubagentStop with a matcher on the agent type, so it starts for crew-refuter only; the type is checked
15
+ # again here. It works whatever CREW_MODEL_ROUTING says: this is not model routing.
16
+ # THIS NEVER BLOCKS. It exits 0 on every path: a stop hook that failed closed would keep an agent running.
17
+ # The ledger is protected from the session the way the routing records are (guard-bash.sh, guard-write.sh): a plain
18
+ # attempt to write it is refused; a determined one (an interpreter, a name built from pieces) is not, and that is
19
+ # the stated scope of those guards.
20
+ set -uo pipefail
21
+ export LC_ALL=C
22
+ _rl_here="${BASH_SOURCE%[/\\]*}"; [ "$_rl_here" = "${BASH_SOURCE}" ] && _rl_here=.
23
+ [ -f "$_rl_here/guard-agent-model.sh" ] || exit 0
24
+ . "$_rl_here/guard-agent-model.sh"
25
+ declare -F _json_slice >/dev/null 2>&1 || exit 0
26
+
27
+ IFS= read -r -d '' INPUT || true
28
+ _json_slice "$INPUT" hook_event_name >/dev/null; [ "$_JS" = SubagentStop ] || exit 0
29
+ _json_slice "$INPUT" agent_type >/dev/null; [ "${_JS##*:}" = crew-refuter ] || exit 0
30
+ _json_slice "$INPUT" agent_id >/dev/null; AID="$_JS"
31
+ case "$AID" in ''|*[!A-Za-z0-9._-]*) exit 0 ;; esac
32
+ _json_slice "$INPUT" session_id >/dev/null; SID="$_JS"
33
+ case "$SID" in ''|*[!A-Za-z0-9._-]*) SID=- ;; esac
34
+
35
+ # One field of a text: the value after "<key>:" at the start of a line, first occurrence, trimmed.
36
+ _rl_field(){ # $1 text, $2 key (any case) -> _RLF
37
+ local line k; _RLF=""
38
+ while IFS= read -r line; do
39
+ line="${line%$'\r'}"; line="${line#"${line%%[![:space:]]*}"}"
40
+ k="${line%%:*}"; [ "$k" = "$line" ] && continue
41
+ shopt -s nocasematch
42
+ if [[ "$k" == "$2" ]]; then shopt -u nocasematch
43
+ _RLF="${line#*:}"; _RLF="${_RLF#"${_RLF%%[![:space:]]*}"}"; _RLF="${_RLF%"${_RLF##*[![:space:]]}"}"; return 0
44
+ fi
45
+ shopt -u nocasematch
46
+ done <<< "$1"
47
+ return 1
48
+ }
49
+
50
+ # The task: the first record of the agent's own transcript (a string, or a list whose first block holds the text).
51
+ _json_slice "$INPUT" agent_transcript_path >/dev/null; _json_unescape "$_JS" >/dev/null; ATP="${_JU//\\//}"
52
+ [ -f "$ATP" ] || exit 0
53
+ L1=""; IFS= read -r L1 < "$ATP" || true
54
+ _json_slice "$L1" content >/dev/null; TXT="$_JS"
55
+ [ -n "$TXT" ] || { _json_slice "$L1" text >/dev/null; TXT="$_JS"; }
56
+ _json_unescape "${TXT:0:8000}" >/dev/null; TASK="$_JU"
57
+ _rl_field "$TASK" finding || exit 0; F_TASK="$_RLF"
58
+ _rl_field "$TASK" lens || exit 0; L_TASK="$_RLF"
59
+
60
+ # The answer, and it must be about the same finding and lens.
61
+ _json_slice "$INPUT" last_assistant_message >/dev/null; _json_unescape "$_JS" >/dev/null; ANS="$_JU"
62
+ _rl_field "$ANS" finding || exit 0; F_ANS="$_RLF"
63
+ _rl_field "$ANS" lens || exit 0; L_ANS="$_RLF"
64
+ _rl_field "$ANS" verdict || exit 0; V="$_RLF"
65
+ [ "$F_TASK" = "$F_ANS" ] && [ "$L_TASK" = "$L_ANS" ] || exit 0
66
+ [[ "$F_TASK" =~ ^[a-z0-9][a-z0-9._:-]*$ ]] && [ "${#F_TASK}" -le 200 ] || exit 0
67
+ case "$L_TASK" in reachability|protection|reproduction|exclusion|blast-radius|claim) ;; *) exit 0 ;; esac
68
+ case "$V" in TRUE_POSITIVE|FALSE_POSITIVE|CANNOT_VERIFY) ;; *) exit 0 ;; esac
69
+
70
+ P="${CLAUDE_PROJECT_DIR:-$PWD}"; P="${P//\\//}"; P="${P%/}"
71
+ D="$P/.claude/state"; T="$D/crew-refuter.tsv"
72
+ mkdir -p "$D" 2>/dev/null || exit 0
73
+ # An agent that is resumed stops again: it answered once, and that answer stands.
74
+ if [ -f "$T" ]; then
75
+ while IFS=$'\t' read -r _ _ a _; do [ "$a" = "$AID" ] && exit 0; done < "$T"
76
+ fi
77
+ [ -s "$T" ] || printf 'ts\tsession\tagent_id\tfinding\tlens\tverdict\n' > "$T" 2>/dev/null
78
+ TS="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)"; case "$TS" in [0-9][0-9][0-9][0-9]-*Z) ;; *) TS=- ;; esac
79
+ printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$TS" "$SID" "$AID" "$F_TASK" "$L_TASK" "$V" >> "$T" 2>/dev/null
80
+ exit 0
@@ -68,11 +68,34 @@ if [ -f "$ROOT/.claude/hooks/pre-commit" ] && git -C "$ROOT" rev-parse --git-dir
68
68
  esac
69
69
  fi
70
70
  [ -n "$HOOKMSG" ] && MSG="${MSG:+$MSG }$HOOKMSG"
71
- [ -n "$MSG" ] || exit 0 # no handover to offer and nothing to say about language -> stay silent
71
+
72
+ # THE STATE BEFORE THE COMPACTION. precompact-snapshot.sh wrote it just before this context was compacted: the
73
+ # branch, the uncommitted files, the last gate refusal, the findings on record. Read back only after a compaction and
74
+ # only into the session that wrote it (same session id); its summary is already JSON-escaped, so it is put into the
75
+ # output as it is, and the rest of MSG stays fixed text that needs no escaping and no extra process.
76
+ SNAP=""
77
+ _rh="${BASH_SOURCE%[/\\]*}"; [ "$_rh" = "${BASH_SOURCE}" ] && _rh=.
78
+ if [ "$SRC" = compact ] && [ -f "$_rh/../eval/lib/gate-log.sh" ]; then
79
+ . "$_rh/../eval/lib/gate-log.sh"
80
+ _sf=""
81
+ if crew_git_dir "$ROOT" && [ -f "$_CREW_GD/crewforth-session-snapshot.json" ]; then _sf="$_CREW_GD/crewforth-session-snapshot.json"
82
+ elif [ -f "$ROOT/.claude/state/session-snapshot.json" ]; then _sf="$ROOT/.claude/state/session-snapshot.json"; fi
83
+ _sid(){ _SID=""; local v="${1#*\"session_id\"}"; [ "$v" = "$1" ] && return 0; v="${v#*\"}"; _SID="${v%%\"*}"; }
84
+ if [ -n "$_sf" ] && IFS= read -r _sj < "$_sf" 2>/dev/null; then
85
+ _sid "$IN"; _mine="$_SID"; _sid "$_sj"
86
+ case "$_sj" in *'"summary":"'*)
87
+ if [ -n "$_mine" ] && [ "$_mine" = "$_SID" ]; then
88
+ _sum="${_sj#*\"summary\":\"}"; _sum="${_sum%\"\}*}"
89
+ SNAP="Before this context was compacted, Crewforth wrote down the state of the work from the files and the gates: $_sum. It is a record of that moment; check the files before relying on it."
90
+ fi ;;
91
+ esac
92
+ fi
93
+ fi
94
+ [ -n "$MSG$SNAP" ] || exit 0 # nothing to offer -> stay silent
72
95
 
73
96
  # hookSpecificOutput.additionalContext is the documented channel that injects text into the model's context.
74
97
  # ONE PATH, no jq: MSG is built from fixed constants with no quote, backslash, control character or newline, so it needs no
75
98
  # escaping and every machine emits the same bytes. If you ever make MSG dynamic, escape it the way board-sync.sh
76
99
  # does (jq-identical, measured) — a raw quote or tab here makes JSON the CLI silently drops.
77
- printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$MSG"
100
+ printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s%s%s"}}\n' "$MSG" "${MSG:+${SNAP:+ }}" "$SNAP"
78
101
  exit 0
@@ -44,7 +44,9 @@
44
44
  set -uo pipefail
45
45
  # The 2.x names of the variables a user can set still work (one helper: eval/lib/crew-env.sh).
46
46
  _crew_d="${BASH_SOURCE%/*}"; [ "$_crew_d" = "${BASH_SOURCE}" ] && _crew_d=.
47
- [ -f "$_crew_d/../eval/lib/crew-env.sh" ] && . "$_crew_d/../eval/lib/crew-env.sh"; unset _crew_d
47
+ [ -f "$_crew_d/../eval/lib/crew-env.sh" ] && . "$_crew_d/../eval/lib/crew-env.sh"
48
+ # How an install's own version is read and ordered (an rc before its final): one definition, eval/lib/version.sh.
49
+ [ -f "$_crew_d/../eval/lib/version.sh" ] && . "$_crew_d/../eval/lib/version.sh"; unset _crew_d
48
50
 
49
51
  URL="${CREW_UPDATE_URL:-https://registry.npmjs.org/-/package/crewforth/dist-tags}"
50
52
  # plugin-stable, not main: the marketplace installs the plugin from that branch, and only an approved release
@@ -139,7 +141,10 @@ PJSON="$PR_ROOT/.claude-plugin/plugin.json"
139
141
  # plugin's copy steps aside whenever a project install is present, instead of asking the project's question too.
140
142
  if [ "$IS_PLUGIN" = 1 ] && [ -f "$CL/VERSION" ]; then exit 0; fi
141
143
  if [ -f "$CL/VERSION" ]; then
142
- read -r _cv < "$CL/VERSION" 2>/dev/null || _cv=""; CUR="$(sane_version "$_cv")" || exit 0
144
+ # The install's own file, so an rc (X.Y.Z-rc.N) is a version here; what npm answers is still held to X.Y.Z.
145
+ read -r _cv < "$CL/VERSION" 2>/dev/null || _cv=""
146
+ declare -F crew_local_version >/dev/null || exit 0
147
+ CUR="$(crew_local_version "$_cv")" || exit 0
143
148
  STATE="$CL/.state" # repo-local: the install lives in the repo
144
149
  FEED="${CREW_UPDATE_URL:-$URL}"; KEY=latest; EDITION=project
145
150
  ANSWER='bash .claude/hooks/session-update-check.sh --answer'
@@ -186,12 +191,8 @@ fi
186
191
  [ -n "$LATEST" ] || exit 0
187
192
  LATEST="$(sane_version "$LATEST")" || exit 0
188
193
 
189
- # Numeric field compare, one awk. Pre-release suffixes degrade to their numeric prefix (`1-rc2` -> 1), which is
190
- # the harmless direction: it can suppress a question, never invent one.
191
- awk -v a="$LATEST" -v b="$CUR" 'BEGIN{
192
- split(a,x,"."); split(b,y,".");
193
- for(i=1;i<=3;i++){ if(x[i]+0 > y[i]+0) exit 0; if(x[i]+0 < y[i]+0) exit 1 }
194
- exit 1 }' || exit 0
194
+ # One awk, eval/lib/version.sh's order: 3.2.0 is newer than an installed 3.2.0-rc.1, so the rc's user is asked.
195
+ awk -v a="$LATEST" -v b="$CUR" "${CREW_VCMP_AWK:-}"' BEGIN { exit !(vcmp(a, b) > 0) }' 2>/dev/null || exit 0
195
196
 
196
197
  # Skipped for good? Asked (or answered "later") within the last day?
197
198
  if [ -f "$STATE/update-skip" ]; then read -r _sk < "$STATE/update-skip" 2>/dev/null || _sk=""; [ "${_sk//[!0-9.]/}" = "$LATEST" ] && exit 0; fi
@@ -12,6 +12,15 @@
12
12
 
13
13
  Co-Authored-By
14
14
  #test: Co-Authored-By: An Assistant <noreply@example.com>
15
+ # The session-link trailer Claude Code asks the model to end a commit message with: it reached six pushed commits of a
16
+ # project with Crewforth installed (2026-10-18), because nothing here named it. The trailer and the address are both
17
+ # caught, so a message that keeps only one of them is still stopped. One line, not two: each line is one grep on every
18
+ # commit (a process, 62-135 ms on Git Bash).
19
+ Claude-Session:|claude\.ai/code/session_
20
+ #test: Claude-Session: https://claude.ai/code/session_0123456789abcdef
21
+ #test: see https://claude.ai/code/session_0123456789abcdef for the run
22
+ #test-clean: the claude session limit is reached
23
+ #test-clean: https://claude.ai/code is the web app
15
24
  # "Generated by/with" alone matched every machine-written header in existence — a pubspec.lock, an EF Core
16
25
  # scaffold, protoc, openapi-generator. A live --dotnet install could not make its FIRST commit because the
17
26
  # cloned base carries a Dart lockfile saying "Generated by pub". §4.1 is about AI AUTHORSHIP, so the pattern
@@ -1,10 +1,10 @@
1
- # Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.1.0 (below VERSION 3.1.1) — do not edit by hand.
1
+ # Generated by packaging/gen-legacy-blobs.sh from the final release tags v1.0.0..v3.1.1 (below VERSION 3.2.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 3fdc551d221ee3665b21ef8cc3563de861a9abe6 v1.4.0 v1.6.3
6
6
  .claude/AGENT_TEMPLATE.md 686fed306c873b45c245c77a95a8e6de6ee383d5 v1.7.0 v2.11.0
7
- .claude/AGENT_TEMPLATE.md 7ab19417b6ff3ac732e2c894be17ffb3ec091f7d v3.1.0 v3.1.0
7
+ .claude/AGENT_TEMPLATE.md 7ab19417b6ff3ac732e2c894be17ffb3ec091f7d v3.1.0 v3.1.1
8
8
  .claude/AGENT_TEMPLATE.md 981df9ea27b6f1c5ab5cb509d40875a07603858d v2.12.0 v2.13.0
9
9
  .claude/AGENT_TEMPLATE.md b38b9c9b799156e07280ca886230524563acc75c v1.0.5 v1.3.0
10
10
  .claude/AGENT_TEMPLATE.md faec09e45543b75763c43bb24114a7b6355649ec v1.0.0 v1.0.4
@@ -26,7 +26,7 @@
26
26
  .claude/DISCIPLINE.md 76d73a4e25c559ea361618862cde8480b1a6d8c2 v3.0.0 v3.0.0
27
27
  .claude/DISCIPLINE.md b1ab7549b571dd2fc80d5777e0ae3185d29bd7b6 v2.13.0 v2.13.0
28
28
  .claude/DISCIPLINE.md b7e1066451a8590e0a2607ad0a6fe80b21599c53 v1.1.3 v1.1.8
29
- .claude/DISCIPLINE.md b90a067ee323a08146862b889293f8feb49f8e7e v3.1.0 v3.1.0
29
+ .claude/DISCIPLINE.md b90a067ee323a08146862b889293f8feb49f8e7e v3.1.0 v3.1.1
30
30
  .claude/DISCIPLINE.md bf062c2b51a9cf3cc4d0caac389c148cf6fdc977 v2.12.0 v2.12.0
31
31
  .claude/DISCIPLINE.md c693f1bd5832ce570058c6eedcacb5dff13d89e8 v1.10.0 v1.10.0
32
32
  .claude/DISCIPLINE.md d63a01a183c9a80d739c840a511d338dc2765820 v1.4.0 v1.4.4
@@ -43,4 +43,4 @@
43
43
  .claude/README.md 97d4dd1f7f1695052cc1d06cc43f5bcdc603c989 v1.0.0 v1.0.4
44
44
  .claude/README.md a3419891cfda7385564008e4befc1007bc5364cb v1.1.1 v1.7.0
45
45
  .claude/README.md a3b76e0fa31267610d28b742c122a962f6db59c4 v3.0.1 v3.0.4
46
- .claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.1.0
46
+ .claude/README.md b6c9b57a67d25b7e4781e4eb61ff9637e6abe5ef v3.1.0 v3.1.1
package/kit/settings.json CHANGED
@@ -108,6 +108,17 @@
108
108
  "timeout": 600
109
109
  }
110
110
  ]
111
+ },
112
+ {
113
+ "matcher": "AskUserQuestion",
114
+ "hooks": [
115
+ {
116
+ "type": "command",
117
+ "shell": "bash",
118
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/guard-question.sh",
119
+ "timeout": 600
120
+ }
121
+ ]
111
122
  }
112
123
  ],
113
124
  "UserPromptSubmit": [
@@ -142,6 +153,12 @@
142
153
  "shell": "bash",
143
154
  "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/session-guard.sh",
144
155
  "timeout": 60
156
+ },
157
+ {
158
+ "type": "command",
159
+ "shell": "bash",
160
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/prompt-approval.sh",
161
+ "timeout": 60
145
162
  }
146
163
  ]
147
164
  }
@@ -168,6 +185,17 @@
168
185
  "timeout": 600
169
186
  }
170
187
  ]
188
+ },
189
+ {
190
+ "matcher": "(.*:)?crew-refuter",
191
+ "hooks": [
192
+ {
193
+ "type": "command",
194
+ "shell": "bash",
195
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/refuter-ledger.sh",
196
+ "timeout": 30
197
+ }
198
+ ]
171
199
  }
172
200
  ],
173
201
  "PostToolUse": [
@@ -181,6 +209,29 @@
181
209
  "timeout": 60
182
210
  }
183
211
  ]
212
+ },
213
+ {
214
+ "matcher": "AskUserQuestion",
215
+ "hooks": [
216
+ {
217
+ "type": "command",
218
+ "shell": "bash",
219
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/prompt-approval.sh",
220
+ "timeout": 60
221
+ }
222
+ ]
223
+ }
224
+ ],
225
+ "PreCompact": [
226
+ {
227
+ "hooks": [
228
+ {
229
+ "type": "command",
230
+ "shell": "bash",
231
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" 2>/dev/null; bash .claude/hooks/precompact-snapshot.sh",
232
+ "timeout": 30
233
+ }
234
+ ]
184
235
  }
185
236
  ],
186
237
  "SessionStart": [
@@ -26,4 +26,11 @@ Read the line Crewforth added to this turn and do what it says:
26
26
  the argument was not `commit`, `push` or `commit+push`. Say which, and in the first case run the command and let
27
27
  the gate's own prompt ask.
28
28
 
29
+ In `auto` the same approval can be given with one click: the gate's refusal prints a fixed AskUserQuestion, and
30
+ the user's answer is recorded exactly like this command. Where a `PermissionRequest` hook may be wired (in the
31
+ user's, the project's or a plugin's settings, or in a skill's or an agent's front matter), that question is closed,
32
+ and so is the gate's own prompt in `default` / `acceptEdits`, because such a hook answers a prompt before the user
33
+ does. This command is then the one approval left, in those modes too. Settings passed with `--settings` and
34
+ managed settings delivered by a server cannot be read, so a hook there is past the check.
35
+
29
36
  Force-push and the rest of §4.5 are not opened by this.
@@ -17,6 +17,21 @@ Trigger phrases: "code-review", "review the code", "review the PR", "review my c
17
17
  A review runs in four steps — **group, plan, review, fact-check** — and reports few findings that are right rather
18
18
  than many that might be. Every step below says what it produces.
19
19
 
20
+ ## 0. The pre-pass first, and the findings in one shape
21
+ Before reading the diff, run the deterministic pre-pass (`eval/lib/review-prepass/` in either edition):
22
+ `bash <dir>/prepass.sh` for the staged change, `--base <ref>` for a branch. It writes
23
+ `.crewforth/review-prepass.json`: secret patterns, a debug call or TODO left in code, shellcheck on shell files, a
24
+ lock file that moved without its manifest, a very large change. **Carry those over, do not re-derive them**; your
25
+ time goes to what a pattern cannot see. rc 3 (no Node) or a `notMeasured` line means that part was NOT checked:
26
+ say so in the review, never read it as clean.
27
+
28
+ Write your own findings in the same shape (`crewforth.findings/1`: `rule` `review-<category>`, `severity`,
29
+ `confidence`, `evidence` with `file`, `line` and a `text` quote, `detail` = what to do instead) and store them with
30
+ `bash <dir>/../findings/findings.sh write review <file>`. A `critical` or `high` finding first goes to
31
+ **crew-refuter** (claim + `file:line`, one call per lens, in parallel) and the document is written with
32
+ `--verified`, which then refuses any `medium`-or-higher finding without its verifiers. This is model discipline:
33
+ no hook runs the pre-pass or reads `review.json`.
34
+
20
35
  ## 1. Scope
21
36
  - **Review the lines this change adds or edits.** Deleted code is context for understanding them, not a target.
22
37
  - Code that is correct, or that the change did not touch, gets no comment.
@@ -142,6 +157,7 @@ five questions that decide *answer in the thread* vs *edit the code*: **`referen
142
157
  item leaves with a disposition from the triage table above — a reasoned refusal is an answer, silence is not.
143
158
 
144
159
  ## DoD (this skill's contribution)
160
+ - The pre-pass ran (or its rc 3 / `notMeasured` is reported as unchecked), and the findings are in `.crewforth/review.json`.
145
161
  - The plan (what changed, the risks) came before the findings; every file in each group was read.
146
162
  - Every finding has a severity, a category and a reason, in the finding format above.
147
163
  - Scope creep and hidden complexity are flagged.
@@ -24,7 +24,7 @@ Verify Crewforth is actually *active* in this project (not just present on disk)
24
24
  5. Summarise: **healthy**, or the precise fixes applied/needed, plus the readiness score. If it's not a git repo,
25
25
  note that the commit-time gates need `git init` + `git config core.hooksPath .claude/hooks`.
26
26
  If the output contains a line starting with ⭐, pass it to the user verbatim as the LAST line of your summary —
27
- it prints once per Crewforth version, and it is meant for the user, not for you.
27
+ it prints once per Crewforth version here (the installer has its own once), and it is meant for the user, not for you.
28
28
 
29
29
  If `.claude/eval/doctor.sh` doesn't exist, this is not a full (start.sh / adopt.sh) install — Crewforth is likely
30
30
  running as a **plugin**, whose hooks are managed by Claude Code itself; there's nothing for the doctor to check,
@@ -52,3 +52,7 @@ found no Git Bash: tell the user the gates do not run and that the fix is Git fo
52
52
  scripts are developer instruments: they inspect an installation from outside it, and the plugin edition has no
53
53
  installation to inspect. Say this plainly when someone asks why `/crew-doctor` reports only the listing (and, on Windows, Git Bash) on a plugin install,
54
54
  rather than treating it as a defect. The Studio panel ships in both editions (`/crew-studio`).
55
+
56
+ **On a plugin install, end with the star line** the full install's doctor prints: `bash "${CLAUDE_PLUGIN_ROOT}/eval/lib/star.sh" --once . doctor`
57
+ (Bash tool, not PowerShell). It prints once per plugin version, or nothing; if it prints a line starting with ⭐, pass it to the
58
+ user verbatim as the LAST line of your summary.
@@ -6,8 +6,9 @@ metadata:
6
6
  ---
7
7
  # /crew-handoff
8
8
  applies the `handoff` skill:
9
- 1. Read the measured fill: the `🔋 Session` line, or `bash .claude/hooks/context-usage.sh --verbose` (Bash tool, not PowerShell).
10
- 2. Write an actionable handover to `docs/SESSION_STATE.md`: Done · In progress (exactly where) · Next step · Open decisions · File pointers · Blockers.
9
+ 1. **crew-session-manager** (@agent-crew-session-manager) measures the fill (`context-usage.sh`, never a guess), gives the
10
+ recommendation and drafts the handover. The steps below work from its draft.
11
+ 2. Write the handover to `docs/SESSION_STATE.md`: Done · In progress (exactly where) · Next step · Open decisions · File pointers · Blockers.
11
12
  3. Preserve decision rationale; the next session should not start from scratch.
12
13
  4. **Cold-reader pass** (`handoff/references/cold-reader.md`): the questions are written from the work BEFORE the
13
14
  file, then answered from the file alone. Anything unanswerable goes back into the handover and is re-checked.
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: crew-map
3
+ description: Map the project — modules, what they touch, the gaps between them — and describe each module from the lines the scan cites.
4
+ disable-model-invocation: true
5
+ metadata:
6
+ kind: command
7
+ ---
8
+ # /crew-map
9
+
10
+ Write `.crewforth/crew-map.json`, then describe it. The scanner draws the map from the files; you add only what
11
+ it cannot: a sentence or two on what each module is for, and a short overview. Every description points at lines
12
+ the scan itself read, and a gate holds it to that. **Never commit.** Show the diff and stop.
13
+
14
+ 1. **Resolve the scanner.** Both editions carry it, in different places:
15
+
16
+ ```
17
+ PLUGIN_MAP=${CLAUDE_PLUGIN_ROOT}/eval/lib/crew-map/crew-map.sh
18
+ ```
19
+
20
+ Claude Code replaces `${CLAUDE_PLUGIN_ROOT}` in this text before you read it, and only in the plugin. If the
21
+ line above names a real path, that is the scanner; if it still shows `${CLAUDE_PLUGIN_ROOT}`, this is a full
22
+ install and the scanner is `.claude/eval/lib/crew-map/crew-map.sh`. Below, `MAP` is whichever it is.
23
+
24
+ 2. **Scan:** `bash MAP` at the project root (Bash tool, not PowerShell). It writes the map and keeps the
25
+ descriptions of an earlier run that still cite only lines this scan cites; it says how many it kept and dropped.
26
+ rc 3 is no usable Node: say so and stop, it is not a pass.
27
+
28
+ 3. **Describe.** Read `.crewforth/crew-map.json`, and as much of the cited source as you need. Then edit that
29
+ file, changing ONLY these fields:
30
+ - each `module` node's `narrative`: `{ "text": "…", "cites": ["file:line", …], "layer": "…", "domain": "…" }`.
31
+ What the module is for, in at most two sentences. Every cite is a `file:line` the scan read for THAT node: its
32
+ `evidence`, its `entryPoint`, or its `about` (the first prose line of its README); cite at least one. `layer` is where it sits, one of `ui` (what a person uses: an app, a site),
33
+ `api` (serves requests to others), `service` (runs work: a worker, a job), `data` (holds or shapes data),
34
+ `infra` (deploys and runs the rest), `tool` (a script or CLI for the team), `library` (code other modules
35
+ import); `domain` is the part of the business it serves, a short phrase ("payments", "mail import"). Both
36
+ are optional and rest on the same cites: leave one out rather than guess. Leave a node `null` when its
37
+ evidence does not tell you what it is for: an honest gap beats a guess.
38
+ - the document's `overview`: the same shape, at most 2000 characters, for someone new to the project: the entry
39
+ points, where data goes, the risky places. Its cites may be any node's evidence.
40
+ - the document's `tour`: `[{ "node": "<id>", "text": "…", "cites": ["file:line"] }, …]`, one to eight stops a
41
+ newcomer reads in order, usually the entry point first and the riskiest place last. Each stop names a node of
42
+ the map, says in at most two sentences why to look there, and cites lines from that node's own evidence.
43
+ Describe what the cited lines show. A connection, a purpose or a risk the scan did not find is not added here:
44
+ say it to the user instead.
45
+
46
+ 4. **Validate:** `bash MAP --validate` (Bash tool, not PowerShell). It refuses a description that cites a line
47
+ the scan did not read, cites nothing, or runs past two sentences; a layer not on the list or a domain written
48
+ as a sentence; a tour stop at no node, citing another node's lines, or at a node twice; and a map whose
49
+ scanner fields were changed.
50
+ Fix what it names, in the descriptions only, until it holds.
51
+
52
+ 5. **Hand over:** the scan's summary line, how many modules you described (and gave a layer) and how many you
53
+ left `null` and why, the tour's stops,
54
+ then `git diff --stat .crewforth/crew-map.json`. Studio's **Map** view (`/crew-studio`) draws the result.
55
+ Committing it is the user's call.
@@ -9,12 +9,15 @@ Run the change set through the review trio (read-only). **The audits go out in p
9
9
  1. **At once, in ONE message** — several `Agent` calls, because none of these writes code and so there is
10
10
  nothing to serialise:
11
11
  - @agent-crew-security-expert (security-scan) — auth/IDOR/injection/secret; findings with severity.
12
+ Each `medium`-or-higher one then goes to **crew-refuter** (@agent-crew-refuter; claim + `file:line`, one call per lens, in
13
+ parallel) before it is reported; `findings.sh write --verified` refuses one its ledger does not back.
12
14
  - @agent-crew-performance-expert (`performance`) — hot path, query/loop, render, payload. Reports
13
15
  **candidates** (reasoned) and **findings** (measured) separately; an unmeasured claim is never a verdict.
14
16
  - @agent-crew-privacy-agent (`privacy-compliance`) — **when the diff touches personal data**: legal basis,
15
17
  minimisation, retention, transfer.
16
18
  2. (if SonarQube is in use) **sonarqube-check** — 0/0/0/0 gate (language-agnostic).
17
- 3. **Last, once 1-2 leave no blocker:** @agent-crew-review-agent (crew-code-review) — "does it improve overall
19
+ 3. **Last, once 1-2 leave no blocker:** @agent-crew-review-agent (crew-code-review) — it starts from the
20
+ deterministic pre-pass (`.crewforth/review-prepass.json`) and writes `.crewforth/review.json`; "does it improve overall
18
21
  code health": the plan (what changed, the risks), then findings with a severity and a category. "Clean" means
19
22
  what crew-code-review means by it: **no critical or high finding open**. A medium or low audit finding does not
20
23
  hold the reviewer back; hand it over in the reviewer's prompt and keep it in the report. A critical or high one
@@ -9,7 +9,9 @@ Closure flow — in order:
9
9
  1. **DoD gate:** was `/simplify` applied · are tests green (@agent-crew-test-expert) · (if SonarQube is used) `sonarqube-check` 0/0/0/0.
10
10
  If any one is red, **STOP**, report what is missing; no deferral.
11
11
  2. If clean, @agent-crew-commit-agent (commit-message) **proposes** an atomic Conventional Commit from the staged diff.
12
- 3. Commit runs ONLY if the user says "commit" (§4.4). "Done" is not approval.
12
+ 3. Commit runs ONLY if the user says "commit" (§4.4). "Done" is not approval. In `auto`, Crewforth hands the session the
13
+ one-click approval question when crew-commit-agent is called: ask it with the message; the typed `/crew-approve` is
14
+ for when no question is offered.
13
15
  4. **Close the board item too** (`teamboard`), when one is held. Closing work is one movement: ask what shipped,
14
16
  run `board.sh done <id> "<note>"`, name the items it unblocked, and offer the next one as options. Do not
15
17
  leave this to the user to remember — a claim nobody closes reads to the whole team as still in progress, and
@@ -32,7 +32,7 @@ Bring the Crewforth install up to the latest published version:
32
32
  (no flags) in **their own terminal**, where both the npx and the interactive prompts work.
33
33
  7. **After:** run `bash .claude/eval/update-guard.sh post` (Bash tool, not PowerShell) and show the user its lists — added, changed, moved,
34
34
  removed.
35
- 8. **Verify:** run `/crew-doctor` (or `bash .claude/eval/doctor.sh` — Bash tool, not PowerShell) so a bad/partial update surfaces immediately.
35
+ 8. **Verify:** run `CREW_NO_STAR=1 bash .claude/eval/doctor.sh` (Bash tool, not PowerShell) so a bad/partial update surfaces immediately; the updater has already shown the star line, if it was due.
36
36
  If the updater's or doctor's output contains a line starting with ⭐, pass it to the user verbatim as the LAST
37
37
  line of your summary — it prints once per Crewforth version, and it is meant for the user, not for you.
38
38
  9. **Report** old → new + the headline changes. Take them from `.claude/.state/whats-new.md` — the updater writes
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: incident-runbook
3
3
  description: |
4
- Production incident response: diagnose → mitigate → resolve, then a blameless postmortem and a repeatable
4
+ Incident response: diagnose → mitigate → resolve, then a blameless postmortem and a repeatable
5
5
  runbook. Stop the impact first, root cause second.
6
- Use when production is degraded or broken, and again for the postmortem afterwards.
6
+ Use when production is degraded or broken, an outage keeps recurring, or for the postmortem.
7
7
  ---
8
8
 
9
9
  # Incident Response & Runbook
@@ -19,9 +19,12 @@ audit). Each verifier:
19
19
  Three passes inside one context window are not three verifiers. The second pass has already read the first one's
20
20
  reasoning; the blindness above is then a wish, not a property, and the agreement it produces is one argument
21
21
  counted three times. So:
22
- - **Each verifier is its own subagent** (`Agent`, one call per verifier, run in parallel). A subagent has its own
23
- context window: it cannot see what the others concluded, which is the whole point. Read-only work, so no
24
- `worktree` is needed — nothing is being written.
22
+ - **Each verifier is its own `crew-refuter` call** (one call per verifier and lens, run in parallel). A subagent
23
+ has its own context window: it cannot see what the others concluded, which is the whole point. Read-only work,
24
+ so no `worktree` is needed — nothing is being written. Its task is the card, then `finding:`, `lens:`, `cite:`
25
+ and `claim:` (its agent file has the exact form); when it stops, `hooks/refuter-ledger.sh` records the verdict
26
+ in the refuter ledger, and `findings.sh write --verified` refuses a `medium`-or-higher finding whose verifiers
27
+ are not there. The ledger is the gate; the prompt that carries no reasoning is still discipline.
25
28
  - **Give it the finding and the file:line, never your reasoning.** Not "I think X is exploitable because Y" —
26
29
  just "claim: X at file:line. Try to refute it."
27
30
  - **If you did not isolate, say so.** Write `verifiers: 1 (single pass, not isolated)` in the verdict block. A
@@ -198,6 +198,27 @@ reported as unreachable, never as empty.
198
198
  Nothing listens on a network interface in this arrangement: both Studios stay on
199
199
  loopback and the tunnel does the crossing.
200
200
 
201
+ ## Remote viewers
202
+
203
+ A viewer is a second token that reads and changes nothing, for watching the
204
+ panel from a phone on the same Wi-Fi without handing it the panel. Add one in
205
+ the Machines tab; its link is shown once, as a QR code (`web/qr.js`, made in the
206
+ page). The server refuses a viewer's every request that is not a `GET`, and its
207
+ reads of `/api/remote`, with 403 before any handler runs (`server/lib/remote.js`).
208
+ A viewer lasts eight hours, then its link answers 401 and the page says Session
209
+ expired. Revoke ends the token at once and closes a stream it holds open.
210
+ Viewers live in memory and end with the server.
211
+
212
+ Transcripts are hidden unless the viewer was added with them shown: the routes
213
+ that are only transcript are refused, and every other answer, the event stream
214
+ included, passes through `server/lib/redact.js`, which drops every string whose
215
+ key is not on its allow-list.
216
+
217
+ While a viewer is live the server also listens on the machine's private Wi-Fi
218
+ address (`server/lib/lan.js`), and there it takes a viewer's token only, never
219
+ the panel's. With none, it is on loopback alone. Every viewer connection and
220
+ refusal is logged (`GET /api/remote`, and the server's output).
221
+
201
222
  ## What Crewforth already measured
202
223
 
203
224
  The panel does not recompute any of it. Selecting the session node opens what
@@ -249,8 +270,10 @@ channel that does not exist.
249
270
 
250
271
  ## Security
251
272
 
252
- The server binds to `127.0.0.1` only, and the host is deliberately not
253
- configurable — it reads a developer's live sessions and can start new ones.
273
+ The server binds to `127.0.0.1`, and the host is deliberately not configurable —
274
+ it reads a developer's live sessions and can start new ones. The one exception is
275
+ the Wi-Fi listener above: open only while a viewer is live, for viewer tokens
276
+ only, read-only.
254
277
 
255
278
  A token is always required for `/api/`. Supply one, or let the server generate
256
279
  one and print it with the URL: