crewforth 3.5.0-rc.1 โ†’ 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 (41) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/VERSION +1 -1
  4. package/kit/CLAUDE.md +1 -1
  5. package/kit/eval/lib/statusline.sh +22 -5
  6. package/kit/eval/lib/todo.awk +167 -0
  7. package/kit/eval/lib/todo.sh +120 -0
  8. package/kit/eval/smoke-test.sh +60 -2
  9. package/kit/hooks/prompt-approval.sh +5 -5
  10. package/kit/skills/crew-plan/SKILL.md +2 -1
  11. package/kit/skills/crew-todo/SKILL.md +31 -0
  12. package/kit/skills/spec-planning/SKILL.md +9 -2
  13. package/kit/studio/web/agent-spend.js +32 -0
  14. package/kit/studio/web/app.js +245 -236
  15. package/kit/studio/web/approvals.js +14 -13
  16. package/kit/studio/web/ask.js +10 -9
  17. package/kit/studio/web/canvas.js +56 -54
  18. package/kit/studio/web/chat.js +41 -41
  19. package/kit/studio/web/convo.js +21 -18
  20. package/kit/studio/web/dock.js +20 -19
  21. package/kit/studio/web/graph-plan.js +62 -22
  22. package/kit/studio/web/i18n-tr.js +760 -0
  23. package/kit/studio/web/i18n.js +79 -0
  24. package/kit/studio/web/index.html +1 -0
  25. package/kit/studio/web/inspect.js +27 -24
  26. package/kit/studio/web/list-plan.js +16 -15
  27. package/kit/studio/web/list.js +6 -5
  28. package/kit/studio/web/liveness.js +11 -9
  29. package/kit/studio/web/map-plan.js +33 -29
  30. package/kit/studio/web/map.js +74 -73
  31. package/kit/studio/web/models-plan.js +47 -42
  32. package/kit/studio/web/models.js +21 -20
  33. package/kit/studio/web/names.js +13 -10
  34. package/kit/studio/web/nav.js +34 -33
  35. package/kit/studio/web/newsession.js +25 -24
  36. package/kit/studio/web/style.css +2 -0
  37. package/kit/studio/web/theme.js +5 -2
  38. package/kit/studio/web/timeline-plan.js +19 -18
  39. package/kit/studio/web/timeline.js +23 -22
  40. package/kit/studio/web/usage.js +36 -32
  41. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -34,6 +34,13 @@ versioning follows [SemVer](https://semver.org/).
34
34
 
35
35
  ### Added
36
36
 
37
+ - **`/crew-todo` shows the plan as a to-do list under the status line.** The status line ends with the plan's count
38
+ (`๐Ÿ“‹ 7/12`); the command opens a panel under it and closes it again: the plan's phases and tasks, done ones ticked,
39
+ open ones boxed, the phase and the task in work marked, cut to a window around the task in work (`CREW_TODO_LINES`).
40
+ It reads the plan's own boxes (`- [ ]`, `- [x]`, `- [~]` in work) under its headings and never writes the plan;
41
+ `spec-planning` now gives every task a box and tells whoever works the plan to tick it when its check passes.
42
+ `/crew-todo <file>` reads another plan, `/crew-todo show` prints the panel in the chat. The drawing is cached
43
+ until the plan changes, so a status line refresh starts no process for it.
37
44
  - **A compaction no longer loses what the user asked for.** The PreCompact snapshot keeps the user's first and last
38
45
  three prompts and the skills used in the session, and gives them back after the compaction, in the same session
39
46
  only; secrets and `<private>` text are left out. The `handoff` template carries the user's words, the dead ends
@@ -127,6 +134,11 @@ versioning follows [SemVer](https://semver.org/).
127
134
  - **Studio shows what a session it started has changed.** The strip above the conversation says how many files
128
135
  changed since the session started (lines added and removed) and how many new files git does not track yet,
129
136
  counted against the commit recorded at the start. It is read without git rewriting the index and kept in memory.
137
+ - **Studio speaks Turkish.** The **TR** button in the top bar, beside the theme button, puts the panel in Turkish
138
+ and **EN** puts it back; English stays the default, and the choice is kept per browser. What Studio shows from
139
+ elsewhere (the conversation, agent and tool names, a reason the server gave) stays as it was written. The Studio
140
+ gate fails when a sentence on screen has no Turkish, when a translation is left over, or when the two disagree
141
+ on a `{slot}`.
130
142
  - **`/crew-doctor` names a linked worktree with no `.claude/settings.json`**, where a resumed session runs none of
131
143
  Crewforth's hooks. One Claude Code makes under `.claude/worktrees/` is not named: it still runs this checkout's.
132
144
  - **`npx crewforth remove` takes Crewforth out of a project.** By default it lists what it would change and changes
@@ -140,6 +152,11 @@ versioning follows [SemVer](https://semver.org/).
140
152
  A `CREW_` variable set in the environment that no Crewforth file reads (a typo) is named, and so is a switch that
141
153
  takes a whole number set to something else.
142
154
 
155
+ - **Studio's summary box says what each kind of agent spent.** Open **Agents** and, as **Cost** does for models, it
156
+ lists one line per agent type (`Backend`, `Test ร—2`, โ€ฆ) with its new tokens and estimated cost, the most spent
157
+ first; a type that ran more than once shows how many times. Without an agent to break down, the row still opens on
158
+ the token kinds.
159
+
143
160
  ### Changed
144
161
 
145
162
  - **`dependency-audit` asks which registry answers an internal name** (scope mappings, `--index-url` against
@@ -161,6 +178,11 @@ versioning follows [SemVer](https://semver.org/).
161
178
  - **Studio's Map spreads lines that share a side of a card**, so a module with three dependents shows three lines
162
179
  instead of one. Measured first on the crew-map test fixtures and Crewforth's own map: 48 line ends shared a pixel
163
180
  across 46 edges; now none do. Sideways lines between ends at two heights turn square instead of running slanted.
181
+ - **Studio's graph uses both sides of a wide window.** The session stands in the middle: the agents that build
182
+ (backend, frontend, database, test, devops, performance) grow to its left and every other agent to its right, so
183
+ one tall column beside an empty half of the pane becomes two short ones. The side follows the agent's type, never
184
+ the count, so an agent that arrives moves nothing already drawn. A narrow window keeps the left-to-right tree, and
185
+ **Order** keeps its waves.
164
186
  - **Frontend work checks more of what users hit.** `a11y` separates criteria from conventions (a failed A/AA
165
187
  criterion is a finding, AAA or a 44 px target a recommendation), adds a skip link, flashing, an error summary and
166
188
  positive `tabindex`, a table of patterns to grep before finishing, and recipes for focus, forms and which
@@ -230,6 +252,16 @@ versioning follows [SemVer](https://semver.org/).
230
252
 
231
253
  ### Fixed
232
254
 
255
+ - **A session no longer tells the user that a command is not installed when it only cannot see it.** The commands
256
+ marked user-only (`/crew-interview`, `/crew-approve`, `/crew-pr`โ€ฆ) are left out of the model's skill list, so a
257
+ session asked about `/crew-interview` answered that no such command existed, while the files were in
258
+ `.claude/skills`. The discipline now says so in one sentence and points at `ls .claude/skills`.
259
+ - **An approval refused in a background session now says so.** Claude Code reports a session started with
260
+ `claude --bg`, or from the agent view, as unattended (`CLAUDE_CODE_SESSION_ATTENDED=0`) even when the user attaches
261
+ and types into it, so `/crew-approve` was refused with "started with -p, or by a program" and no way on. Measured on
262
+ 2.1.319: a background session and `claude -p` both report 0, and a prompt started by a program in the background is
263
+ recorded as a human one, so the gate cannot tell them apart and stays closed. The refusal now names the background
264
+ case and the two ways on: a regular session, or the commit in the user's own terminal.
233
265
  - **`adopt` refuses a `settings.json` whose hook entries Claude Code would reject.** Claude Code ignores the whole
234
266
  file when one hook entry has the wrong shape, so merging Crewforth's hooks into it installed gates that never ran.
235
267
  adopt now names the entries and stops the merge, and the merged result is checked the same way before it is
package/README.md CHANGED
@@ -32,7 +32,7 @@ Then open Claude Code and run `/crew-doctor` to confirm the setup. Requires bash
32
32
 
33
33
  - **14 specialist agents**, from `crew-planner` to `crew-commit-agent`, each owning one domain.
34
34
  - **40 skills** holding the method: testing, migrations, API contracts, observability, accessibility, deployment.
35
- - **17 commands**, each started with its `/crew-โ€ฆ` name: `/crew-plan`, `/crew-review`, `/crew-ship`, `/crew-handoff`, `/crew-brainstorm`, `/crew-update`, `/crew-doctor`, `/crew-gates`, `/crew-skill`, `/crew-studio`, `/crew-approve`, `/crew-loosen`, `/crew-allow-verify`, `/crew-map`, `/crew-audit`, `/crew-interview`, `/crew-pr`.
35
+ - **18 commands**, each started with its `/crew-โ€ฆ` name: `/crew-plan`, `/crew-review`, `/crew-ship`, `/crew-handoff`, `/crew-brainstorm`, `/crew-update`, `/crew-doctor`, `/crew-gates`, `/crew-skill`, `/crew-studio`, `/crew-approve`, `/crew-loosen`, `/crew-allow-verify`, `/crew-map`, `/crew-audit`, `/crew-interview`, `/crew-pr`, `/crew-todo`.
36
36
  - In `auto` mode a commit or a push waits for you: type `/crew-approve` and what was just refused is approved, tied to what is staged.
37
37
  - A stopped destructive command says what it would have touched; a review starts from a deterministic pre-pass, and `crew-refuter` tries to refute each finding that blocks.
38
38
  - **Organisation presets.** `npx crewforth init --preset strict` (or `solo`, or your own JSON file) carries critical paths, model floors and verify commands to every project.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.5.0-rc.1
1
+ 3.5.0-rc.2
package/kit/CLAUDE.md CHANGED
@@ -46,7 +46,7 @@ What the user sees โ†’ **crew-frontend-expert** ยท server behaviour โ†’ **crew-b
46
46
  **crew-privacy-agent** (personal data) ยท **crew-test-expert** ยท **crew-performance-expert** ยท docs/legal/store
47
47
  text โ†’ **crew-docs-agent** ยท what to build โ†’
48
48
  **crew-planner** ยท unknown cause โ†’ **general-purpose** + `systematic-debugging`. Two domains โ†’ delegate in
49
- sequence. All are installed: "no owner" is an `ls`.
49
+ sequence. All are installed: "no owner" is an `ls`. The user-only commands (`/crew-interview`, `/crew-approve`โ€ฆ) are missing from your skill list by design, not uninstalled: `ls .claude/skills`.
50
50
 
51
51
  1. **Diagnose, then plan** โ€” unknown cause โ†’ `systematic-debugging` first; unclear scope โ†’ **crew-planner** (`/crew-plan`).
52
52
  2. **Produce** โ€” the domain owner above.
@@ -12,11 +12,14 @@
12
12
  # โš™ crew agents started in this session that have not stopped yet (model routing's records)
13
13
  # ๐Ÿ”‹ the context fill Claude Code measured (context_window.used_percentage), never an estimate
14
14
  # ~$ what Claude Code puts the session's cost at (cost.total_cost_usd), at API list price
15
+ # ๐Ÿ“‹ the plan's count of done tasks (docs/PLAN.md, todo.sh); with the panel open (/crew-todo), the panel itself is
16
+ # drawn under the line instead: phases and tasks, โœ“ done, โ˜ open, โ–ธ the phase and the task in work
15
17
  #
16
18
  # Wired by the installers only where no status line is set already (theirs is never replaced), from
17
19
  # statusline.settings.json beside this file.
18
20
  # COST. Claude Code runs this after every change, debounced at 300 ms. Measured: awk (the stdin JSON) and tail (the
19
- # gate log), and on bash older than 4.2 one date; the rest is builtins. No git, no jq, no python.
21
+ # gate log), and on bash older than 4.2 one date; the rest is builtins. No git, no jq, no python. The plan panel
22
+ # adds one awk only when the plan (or its drawing code) changed since the last line; otherwise it reads its cache.
20
23
  set -u
21
24
  _here="${BASH_SOURCE%[/\\]*}"; [ "$_here" = "${BASH_SOURCE}" ] && _here=.
22
25
 
@@ -93,8 +96,22 @@ case "${COST:-}" in ''|null|*[!0-9.]*) ;; *)
93
96
  printf -v c '%.2f' "$COST" 2>/dev/null && OUT+=("~\$$c") ;;
94
97
  esac
95
98
 
96
- [ "${#OUT[@]}" -gt 0 ] || exit 0
97
- line="${OUT[0]}"
98
- for p in "${OUT[@]:1}"; do line="$line ยท $p"; done
99
- printf '%s\n' "$line"
99
+ # ๐Ÿ“‹ The plan: its count on the line, or with the panel open (/crew-todo), the panel under it.
100
+ PANEL=""
101
+ if [ -d .claude ] && [ -f "$_here/todo.sh" ]; then
102
+ . "$_here/todo.sh"
103
+ if [ -e .claude/state/todo/open ]; then
104
+ col=1; [ -n "${NO_COLOR:-}" ] && col=0
105
+ crew_todo_read "$col"; PANEL="$_CREW_TODO_PANEL"
106
+ elif [ -f docs/PLAN.md ] || [ -s .claude/state/todo/path ]; then
107
+ crew_todo_read 1; [ -n "$_CREW_TODO_HINT" ] && OUT+=("$_CREW_TODO_HINT")
108
+ fi
109
+ fi
110
+
111
+ if [ "${#OUT[@]}" -gt 0 ]; then
112
+ line="${OUT[0]}"
113
+ for p in "${OUT[@]:1}"; do line="$line ยท $p"; done
114
+ printf '%s\n' "$line"
115
+ fi
116
+ [ -n "$PANEL" ] && printf '%s\n' "$PANEL"
100
117
  exit 0
@@ -0,0 +1,167 @@
1
+ # todo.awk โ€” reads a plan (docs/PLAN.md by default) and draws its to-do panel: the phases and the tasks, a tick on
2
+ # what is done, an empty box on what is not, and the phase and the task in work marked.
3
+ #
4
+ # LC_ALL=C awk -v color=1 -v maxl=14 -v name=docs/PLAN.md -f todo.awk docs/PLAN.md
5
+ #
6
+ # Run it under LC_ALL=C: lengths are counted in bytes, so a cut never depends on the locale (and is mended so it never
7
+ # splits a UTF-8 character).
8
+ #
9
+ # What it reads, and nothing else:
10
+ # a task a list item whose text starts with a box: `- [ ] โ€ฆ` `- [x] โ€ฆ` `1. [ ] โ€ฆ` (at the margin)
11
+ # [ ] open ยท [x] done ยท [~] or [>] in work. A box indented under a task is a sub-step, not a task.
12
+ # a phase a heading (## or deeper) with at least one task under it.
13
+ # the scope when the plan has a `## Tasks` heading (or Gรถrevler, ฤฐลŸler, To-do), only what is under it; else the
14
+ # whole file. An `Acceptance criteria` section is never a phase: its boxes are counted as AC n/m.
15
+ # code blocks are skipped (a template quoted in the plan is not the plan).
16
+ # The task in work is the first one marked [~]/[>]; with none marked, the first open one. Its phase is the active one.
17
+ #
18
+ # Output: line 1 the short count for the status line ("๐Ÿ“‹ 7/12"), or nothing when the plan has no task; then the panel.
19
+ # It decides nothing and changes nothing.
20
+ BEGIN {
21
+ if (maxl + 0 < 6) maxl = 14
22
+ wmax = 64
23
+ E = sprintf("%c", 27)
24
+ if (color + 0) { DIM = E "[2m"; BOLD = E "[1m"; ACT = E "[1;36m"; RST = E "[0m" } else { DIM = BOLD = ACT = RST = "" }
25
+ for (i = 128; i < 192; i++) CONT = CONT sprintf("%c", i)
26
+ for (i = 192; i < 256; i++) LEAD = LEAD sprintf("%c", i)
27
+ TICK = "โœ“"; BOX = "โ˜"; HERE = "โ–ธ"
28
+ nev = 0; fence = ""
29
+ }
30
+
31
+ # A fenced code block: skipped whole.
32
+ /^[ ]*(```|~~~)/ {
33
+ f = $0; sub(/^[ ]*/, "", f); f = substr(f, 1, 3)
34
+ if (fence == "") fence = f; else if (f == fence) fence = ""
35
+ next
36
+ }
37
+ fence != "" { next }
38
+
39
+ /^#+[ \t]/ {
40
+ t = $0; match(t, /^#+/); lv = RLENGTH
41
+ t = substr(t, lv + 1); sub(/^[ \t]+/, "", t); sub(/[ \t]+#*[ \t]*$/, "", t); sub(/\r$/, "", t)
42
+ nev++; EV[nev] = "H"; LV[nev] = lv; TX[nev] = t
43
+ next
44
+ }
45
+
46
+ {
47
+ line = $0; sub(/\r$/, "", line)
48
+ match(line, /^ */); ind = RLENGTH
49
+ if (ind > 1) next
50
+ rest = substr(line, ind + 1)
51
+ if (match(rest, /^[-*+][ \t]+\[[ xX~>]\][ \t]+/)) num = ""
52
+ else if (match(rest, /^[0-9]+[.)][ \t]+\[[ xX~>]\][ \t]+/)) { num = rest; sub(/[.)].*/, "", num) }
53
+ else next
54
+ head = substr(rest, 1, RLENGTH); txt = substr(rest, RLENGTH + 1)
55
+ b = head; sub(/^[^[]*\[/, "", b); b = substr(b, 1, 1)
56
+ st = (b == "x" || b == "X") ? "d" : (b == "~" || b == ">") ? "w" : "o"
57
+ # The task's name is what comes before its first " โ€” " (the plan's fields: satisfies, size, riskโ€ฆ).
58
+ k = index(txt, " โ€” "); if (k > 0) txt = substr(txt, 1, k - 1)
59
+ sub(/[ \t]+$/, "", txt)
60
+ if (num != "") txt = num ". " txt
61
+ nev++; EV[nev] = "I"; ST[nev] = st; TX[nev] = txt
62
+ }
63
+
64
+ function isac(t) { return t ~ /^([Aa]cceptance [Cc]riteria|[Kk]abul [Kk]riter)/ }
65
+ function istasks(t) { return t ~ /^([Tt]asks|TASKS|[Gg]รถrevler|Gร–REVLER|[ฤฐi]ลŸler|ฤฐลžLER|[Tt]o-?[Dd]o|TODO)([^A-Za-z]|$)/ }
66
+ # A cut in bytes that never leaves half a UTF-8 character: drop trailing continuation bytes, then their lead byte.
67
+ function cut(s, n, r) {
68
+ if (length(s) <= n) return s
69
+ r = substr(s, 1, n - 1)
70
+ while (length(r) && index(CONT, substr(r, length(r), 1))) r = substr(r, 1, length(r) - 1)
71
+ if (length(r) && index(LEAD, substr(r, length(r), 1))) r = substr(r, 1, length(r) - 1)
72
+ return r "โ€ฆ"
73
+ }
74
+ function mark(st) { return st == "d" ? TICK : st == "w" ? HERE : BOX }
75
+ function out(s) { L[++nl] = s }
76
+
77
+ END {
78
+ # The scope: under a Tasks heading when the plan has one.
79
+ s0 = 1; s1 = nev; base = 1; title = ""
80
+ for (i = 1; i <= nev; i++) if (EV[i] == "H" && LV[i] == 1 && title == "") title = TX[i]
81
+ for (i = 1; i <= nev; i++) if (EV[i] == "H" && LV[i] >= 2 && istasks(TX[i])) {
82
+ s0 = i + 1; s1 = nev; base = LV[i]
83
+ for (j = i + 1; j <= nev; j++) if (EV[j] == "H" && LV[j] <= LV[i]) { s1 = j - 1; break }
84
+ break
85
+ }
86
+ # Acceptance criteria, wherever they are: counted, never shown as a phase.
87
+ ac = 0; acd = 0; inac = 0
88
+ for (i = 1; i <= nev; i++) {
89
+ if (EV[i] == "H") { if (inac && LV[i] <= aclv) inac = 0; if (isac(TX[i])) { inac = 1; aclv = LV[i] } }
90
+ else if (inac) { ac++; if (ST[i] == "d") acd++ }
91
+ }
92
+ # Phases and tasks inside the scope. A heading opens a phase; tasks before any heading sit in an unnamed one.
93
+ np = 0; cur = 0; skip = 0; skiplv = 0; nt = 0; done = 0
94
+ for (i = s0; i <= s1; i++) {
95
+ if (EV[i] == "H") {
96
+ if (skip && LV[i] <= skiplv) skip = 0
97
+ if (LV[i] <= base && base > 1) continue
98
+ if (isac(TX[i]) || (LV[i] == 1 && base == 1)) { if (isac(TX[i])) { skip = 1; skiplv = LV[i] }; cur = 0; continue }
99
+ if (skip) continue
100
+ np++; PN[np] = TX[i]; PT[np] = 0; PD[np] = 0; cur = np
101
+ continue
102
+ }
103
+ if (skip) continue
104
+ if (cur == 0) { np++; PN[np] = ""; PT[np] = 0; PD[np] = 0; cur = np }
105
+ PT[cur]++; nt++; T[cur, PT[cur]] = i
106
+ if (ST[i] == "d") { PD[cur]++; done++ }
107
+ }
108
+ if (nt == 0) { print ""; print "๐Ÿ“‹ " cut(name != "" ? name : FILENAME, wmax) ": no task with a box yet ( - [ ] task ยท - [x] done ยท - [~] in work )"; exit }
109
+
110
+ # The task in work: the first marked [~]/[>], else the first open one.
111
+ ap = 0; at = 0
112
+ for (p = 1; p <= np && !ap; p++) for (t = 1; t <= PT[p]; t++) if (ST[T[p, t]] == "w") { ap = p; at = t; break }
113
+ for (p = 1; p <= np && !ap; p++) for (t = 1; t <= PT[p]; t++) if (ST[T[p, t]] == "o") { ap = p; at = t; break }
114
+
115
+ # Phases with no task are not phases.
116
+ m = 0
117
+ for (p = 1; p <= np; p++) if (PT[p] > 0) { m++; PN[m] = PN[p]; PT[m] = PT[p]; PD[m] = PD[p]; for (t = 1; t <= PT[p]; t++) T[m, t] = T[p, t]; if (p == ap) ap = m }
118
+ np = m
119
+ phased = (np > 1 || PN[1] != "")
120
+
121
+ print "๐Ÿ“‹ " done "/" nt
122
+ hd = BOLD "๐Ÿ“‹ " (title != "" ? cut(title, wmax) : "Plan") RST " ยท " done "/" nt " done"
123
+ if (phased && ap) hd = hd " ยท phase " ap "/" np
124
+ if (ac) hd = hd " ยท AC " acd "/" ac
125
+ if (!ap) hd = hd " ยท all done"
126
+ out(hd)
127
+
128
+ # Which phases get a line: all of them when they fit, else the active one with its neighbours and a count of the rest.
129
+ budget = maxl - 1
130
+ pa = 1; pb = np
131
+ if (phased && np > budget - 3) {
132
+ c = (ap ? ap : np); pa = c - 1; if (pa < 1) pa = 1; pb = pa + (budget - 6); if (pb > np) pb = np
133
+ if (pb - pa < budget - 6) { pa = pb - (budget - 6); if (pa < 1) pa = 1 }
134
+ }
135
+ # The tasks of the active phase (or of the plan, when it has no phases) get what is left.
136
+ room = budget - (phased ? (pb - pa + 1) + (pa > 1) + (pb < np) : 0)
137
+ if (room < 3) room = 3
138
+
139
+ if (phased && pa > 1) { k = 0; for (p = 1; p < pa; p++) k += (PD[p] == PT[p]); out(DIM "โ€ฆ " (pa - 1) " phase" (pa > 2 ? "s" : "") " before (" k " done)" RST) }
140
+ for (p = (phased ? pa : 1); p <= (phased ? pb : 1); p++) {
141
+ if (phased) {
142
+ nm = (PN[p] != "" ? cut(PN[p], wmax) : "(no phase)") " " PD[p] "/" PT[p]
143
+ if (p == ap) out(ACT HERE " " nm RST)
144
+ else if (PD[p] == PT[p]) out(DIM TICK " " nm RST)
145
+ else out(BOX " " nm)
146
+ }
147
+ if (p != ap && !(phased == 0 && !ap)) continue
148
+ # A window over the tasks that keeps the one in work in sight: one before it, the rest after.
149
+ n = PT[p]; lo = 1; hi = n
150
+ if (n > room) {
151
+ lo = (at > 1 ? at - 1 : 1)
152
+ hi = (lo == 1) ? room - 1 : lo + room - 3
153
+ if (hi >= n) { hi = n; lo = n - room + 2; if (lo < 1) lo = 1 }
154
+ }
155
+ ind = (phased ? " " : "")
156
+ if (lo > 1) out(ind DIM "โ€ฆ " (lo - 1) " above" RST)
157
+ for (t = lo; t <= hi; t++) {
158
+ e = T[p, t]; s = mark(ST[e]) " " cut(TX[e], wmax)
159
+ if (t == at && p == ap) out(ind ACT HERE " " cut(TX[e], wmax) RST)
160
+ else if (ST[e] == "d") out(ind DIM s RST)
161
+ else out(ind s)
162
+ }
163
+ if (hi < n) out(ind DIM "โ€ฆ " (n - hi) " more" RST)
164
+ }
165
+ if (phased && pb < np) { k = 0; for (p = pb + 1; p <= np; p++) k += PT[p] - PD[p]; out(BOX " โ€ฆ " (np - pb) " more phase" (np - pb > 1 ? "s" : "") " (" k " open)") }
166
+ for (i = 1; i <= nl; i++) print L[i]
167
+ }
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bash
2
+ # todo.sh โ€” the plan's to-do panel: what is done (โœ“), what is not (โ˜), and the phase and the task in work (โ–ธ), read
3
+ # from the plan file (docs/PLAN.md unless /crew-todo named another). The drawing is todo.awk; this file keeps the
4
+ # panel's two bits of state and serves both its readers.
5
+ #
6
+ # bash .claude/eval/lib/todo.sh open the panel if it is closed, close it if it is open
7
+ # bash .claude/eval/lib/todo.sh on [FILE] open it (and read FILE from now on) ยท off: close it
8
+ # bash .claude/eval/lib/todo.sh FILE the same as `on FILE`
9
+ # bash .claude/eval/lib/todo.sh show [FILE] print the panel here, once (no status line needed)
10
+ # bash .claude/eval/lib/todo.sh status one line: open or closed, which file, how far
11
+ #
12
+ # The status line sources this file and calls crew_todo_read: open, the panel is drawn under the line; closed, the
13
+ # line carries only the count ("๐Ÿ“‹ 7/12"). Nothing here decides or blocks anything, and the plan is never written:
14
+ # the plan's own boxes are the record ([ ] open ยท [x] done ยท [~] in work), ticked by whoever does the work.
15
+ #
16
+ # State, per project, under .claude/state/todo/: `open` (present = the panel is open) and `path` (the plan file, when
17
+ # it is not docs/PLAN.md); `view` is a cache of the drawing, made again only when the plan, todo.awk or the settings
18
+ # changed, so a status line refresh costs no process at all while the plan stands still.
19
+ # Settings: CREW_TODO_LINES (the panel's height, default 14) ยท NO_COLOR (set: no colours).
20
+
21
+ _CREW_TODO_LIB="${BASH_SOURCE%[/\\]*}"; [ "$_CREW_TODO_LIB" = "${BASH_SOURCE}" ] && _CREW_TODO_LIB=.
22
+ _CREW_TODO_DIR=.claude/state/todo
23
+
24
+ # The plan file this project's panel reads: _CREW_TODO_PLAN. Run from the project root.
25
+ crew_todo_plan(){
26
+ _CREW_TODO_PLAN=""
27
+ [ -s "$_CREW_TODO_DIR/path" ] && { IFS= read -r _CREW_TODO_PLAN < "$_CREW_TODO_DIR/path"; _CREW_TODO_PLAN="${_CREW_TODO_PLAN%$'\r'}"; }
28
+ [ -n "$_CREW_TODO_PLAN" ] || _CREW_TODO_PLAN=docs/PLAN.md
29
+ }
30
+
31
+ # The drawing: _CREW_TODO_HINT (the count, "" when the plan has no task) and _CREW_TODO_PANEL (its lines, joined with
32
+ # newlines). $1 = color (1/0). From the cache when the cache still matches; one awk when it does not.
33
+ crew_todo_read(){
34
+ local col="${1:-1}" maxl="${CREW_TODO_LINES:-14}" v="$_CREW_TODO_DIR/view" key l first=1
35
+ _CREW_TODO_HINT=""; _CREW_TODO_PANEL=""
36
+ case "$maxl" in ''|*[!0-9]*) maxl=14 ;; esac
37
+ crew_todo_plan
38
+ if [ ! -f "$_CREW_TODO_PLAN" ]; then
39
+ _CREW_TODO_PANEL="๐Ÿ“‹ no plan at $_CREW_TODO_PLAN yet: /crew-plan writes one, /crew-todo <file> reads another"
40
+ return 0
41
+ fi
42
+ key="k=$col/$maxl/$_CREW_TODO_PLAN"
43
+ # The cache holds when it is newer than the plan and than the drawing code, and was drawn with these settings.
44
+ if [ -f "$v" ] && [ "$v" -nt "$_CREW_TODO_PLAN" ] && ! [ "$_CREW_TODO_LIB/todo.awk" -nt "$v" ]; then
45
+ IFS= read -r l < "$v" 2>/dev/null || l=""
46
+ [ "$l" = "$key" ] || l=""
47
+ else l=""; fi
48
+ if [ -z "$l" ]; then
49
+ [ -d "$_CREW_TODO_DIR" ] || mkdir -p "$_CREW_TODO_DIR" 2>/dev/null
50
+ { printf '%s\n' "$key"; LC_ALL=C awk -v color="$col" -v maxl="$maxl" -v name="$_CREW_TODO_PLAN" -f "$_CREW_TODO_LIB/todo.awk" "$_CREW_TODO_PLAN" 2>/dev/null; } > "$v" 2>/dev/null || return 0
51
+ fi
52
+ {
53
+ IFS= read -r l # the key
54
+ IFS= read -r _CREW_TODO_HINT
55
+ while IFS= read -r l; do
56
+ if [ "$first" = 1 ]; then _CREW_TODO_PANEL="$l"; first=0; else _CREW_TODO_PANEL="$_CREW_TODO_PANEL"$'\n'"$l"; fi
57
+ done
58
+ } < "$v"
59
+ return 0
60
+ }
61
+
62
+ # Sourced: the functions only.
63
+ [ "${BASH_SOURCE[0]}" = "$0" ] || return 0
64
+
65
+ set -u
66
+ [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "$CLAUDE_PROJECT_DIR/.claude" ] && cd "$CLAUDE_PROJECT_DIR" 2>/dev/null
67
+ if [ ! -d .claude ]; then echo "todo: no .claude/ here: run it from the project root of a Crewforth install" >&2; exit 1; fi
68
+
69
+ cmd="${1:-toggle}"; file="${2:-}"
70
+ case "$cmd" in on|off|toggle|show|status) ;; -h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
71
+ -*) echo "todo: unknown option $cmd (on ยท off ยท show ยท status ยท a plan file)" >&2; exit 2 ;;
72
+ *) file="$cmd"; cmd=on ;;
73
+ esac
74
+ case "$file" in *$'\n'*|*$'\r'*) echo "todo: a file name with a line break is not read" >&2; exit 2 ;; esac
75
+
76
+ if [ "$cmd" = show ]; then
77
+ # Drawn here, not from the cache, and coloured only on a terminal: this may go to a chat that does not take colour.
78
+ crew_todo_plan; [ -n "$file" ] && _CREW_TODO_PLAN="$file"
79
+ if [ ! -f "$_CREW_TODO_PLAN" ]; then echo "๐Ÿ“‹ no plan at $_CREW_TODO_PLAN yet: /crew-plan writes one"; exit 0; fi
80
+ c=0; [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && c=1
81
+ LC_ALL=C awk -v color="$c" -v maxl=999 -v name="$_CREW_TODO_PLAN" -f "$_CREW_TODO_LIB/todo.awk" "$_CREW_TODO_PLAN" | sed 1d
82
+ exit 0
83
+ fi
84
+
85
+ mkdir -p "$_CREW_TODO_DIR" 2>/dev/null || { echo "todo: cannot write $_CREW_TODO_DIR" >&2; exit 1; }
86
+ if [ -n "$file" ]; then
87
+ [ -f "$file" ] || echo "todo: $file does not exist yet; the panel will read it once it does"
88
+ if [ "$file" = docs/PLAN.md ]; then rm -f "$_CREW_TODO_DIR/path"; else printf '%s\n' "$file" > "$_CREW_TODO_DIR/path"; fi
89
+ fi
90
+ [ "$cmd" = toggle ] && { if [ -e "$_CREW_TODO_DIR/open" ]; then cmd=off; else cmd=on; fi; }
91
+ case "$cmd" in
92
+ on) : > "$_CREW_TODO_DIR/open" ;;
93
+ off) rm -f "$_CREW_TODO_DIR/open" ;;
94
+ esac
95
+
96
+ crew_todo_read 0
97
+ st=closed; [ -e "$_CREW_TODO_DIR/open" ] && st=open
98
+ how=""
99
+ if [ -n "$_CREW_TODO_HINT" ]; then
100
+ how="${_CREW_TODO_HINT#๐Ÿ“‹ } done"
101
+ inwork="$(printf '%s\n' "$_CREW_TODO_PANEL" | grep 'โ–ธ ' | tail -n 1 | sed 's/^ *โ–ธ //')"
102
+ [ -n "$inwork" ] && how="$how, in work: $inwork"
103
+ elif [ -f "$_CREW_TODO_PLAN" ]; then how="no task with a box in it yet"
104
+ else how="the file does not exist yet"; fi
105
+ if [ "$st" = open ]; then echo "plan panel: open ยท $_CREW_TODO_PLAN ยท $how"
106
+ else echo "plan panel: closed ยท $_CREW_TODO_PLAN ยท $how (the status line keeps the count)"; fi
107
+
108
+ # Shown under Crewforth's status line only. Say so when this project's status line is another one, or none.
109
+ sl=""
110
+ for f in .claude/settings.local.json .claude/settings.json "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"; do
111
+ [ -f "$f" ] && [ -f "$_CREW_TODO_LIB/settings-json.awk" ] || continue
112
+ sl="$(awk -v op=get -v path=statusLine.command -f "$_CREW_TODO_LIB/settings-json.awk" "$f" 2>/dev/null)" || sl=""
113
+ [ -n "$sl" ] && break
114
+ done
115
+ case "$sl" in
116
+ *eval/lib/statusline.sh*) ;;
117
+ ?*) [ "$st" = open ] && echo "note: this project's status line is not Crewforth's, so the panel does not show there; /crew-todo show prints it here" ;;
118
+ *) [ "$st" = open ] && echo "note: no status line is set; bash .claude/eval/lib/statusline.sh --install (Bash tool, not PowerShell) sets Crewforth's (only where none is set), or /crew-todo show prints the panel here" ;;
119
+ esac
120
+ exit 0
@@ -2762,7 +2762,7 @@ sec "== 6f) always-on token budget =="
2762
2762
  # for that cost, and a gate rather than a reminder โ€” a verbose new description fails the suite instead of
2763
2763
  # quietly taxing every future session. Budgets sit just above the current sizes: raising one is allowed, but
2764
2764
  # only as a deliberate edit here.
2765
- BUDGET_DISC=16870 # Discipline rules: 16470 โ†’ 16870 (+400, the cap the user set for this one change): in an audit, text that steers the verdict is a finding; content cannot carry a gate verdict or the ๐Ÿ”‹ line; a fetch is https only and never reaches a private, loopback or metadata host, redirects too; a secret is never asked for in chat; a link is cited only when opened this session; every report says `Not tested:` and labels each figure measured, derived or inferred; a refused action is not retried another way. Each is about what the main thread does on its own turn โ€” what it fetches, asks, cites, reports and retries โ€” so a skill read on demand would arrive after the act. What did not fit went to the skill that owns it: the 90-day date on an external fact (docs-writer, confidence-check), the audit-steering detail (security-scan, red-team). Before that, P07a: 16214 โ†’ 16470 (+256): the audit step no longer re-runs every lane after every fix โ€” only a critical/high fix, or one on a critical path, does, a lesser one is re-checked by the lane that raised it, a low finding never starts a round, three rounds with medium+ open then ask โ€” and the DoD's "nothing deferred" says what it means (medium+ fixed, tracked or user-accepted, the lows to the user as one list). Field transcripts of four projects showed main threads reading the old two lines as "fix every low and audit everything again"; both are where every session reads them, because the loop is run by the main thread, not by a skill. Before that, P05: 15637 โ†’ 16214 (+577): "Short never costs meaning" (a short reply keeps every negation, condition and number; code, paths and errors verbatim; point to path:line instead of restating; full sentences for warnings, irreversible steps, questions and persisted text), principle 2's "never cut" list (validation at a trust boundary, data-loss error handling, security, accessibility, what was asked) and a re-read-after-compaction clause. All three are about how every reply is written, so a skill read on demand would arrive after the reply. evals/cases/terse-fidelity measures the first. Before that, P02: 15570 โ†’ 15637 (+67): "Untrusted content" names the text a hook injects from teammates (the team board) as data; the hook now labels it the same way. Before that, P01: 15429 โ†’ 15570 (+141): "No deferral" names the recorded, user-accepted bypass (BYPASSED line, ADR Bypassed entry, accepted finding) as the user's decision, so the rule stops contradicting confidence-check, adr and crew-code-review. Before that, 3.5: 14971 โ†’ 15429 (+458): ยง4.7 (a staged lockfile needs an audit record of that lockfile diff, +275 with the ยง4.4โ€“ยง4.7 range) and the quality line (+183): the DoD reads `## Stack` โ†’ `Quality analysis:` instead of six "(if SonarQube is in use)" clauses, which a project with no server read as false every time (0 analyses in its first 8 days, 0 audits against 21 lockfile commits). Both have to be where every session reads them: the gate refuses a commit, and the refusal must not be a surprise. Before that, 3.1.0: 13986 โ†’ 14975 (+989): the task card (files, change, verify), the model table read from it, "the referee follows the risk" and the verify-then-escalate rule (+634 in the first form, +355 for the card). The session chooses a model on every crew agent call, so the table has to be where every session reads it; a skill would be read after the choice. Before that, 3.0.1: 13741 โ†’ 13986 (+245): the ladder states that a project CLAUDE.md can tighten ยง4, never loosen it โ€” naming ยง4.1 as the one exception, since adopt may loosen the trace gate โ€” and that ยง numbers point into this file (two field projects read their stricter ยง4.4 as a contradiction), the opening's "project wins" carries the same limit, and the context-usage command names the Bash tool (a Windows session ran the scripts in PowerShell). RC-1: 13711 โ†’ 13741 (+30): "Reply in the user's language even when a skill's text is English" โ€” a Turkish session answered /crew-review in English, because every skill body it had just read was English; the rule is stated nowhere else. evals/cases/reply-language measures it. 5R.3: 13712 โ†’ 13711 (the old-name phrases rewritten, net โˆ’1). 5d.2 prompt audit: tightened to the measured size (13719 โ†’ 13712: format-to-content style line, one reload
2765
+ BUDGET_DISC=17030 # Discipline rules: 16870 โ†’ 17030 (+160: the user-only commands are absent from the model's skill list by design, and a session told the user /crew-interview was not installed); before that 16470 โ†’ 16870 (+400, the cap the user set for this one change): in an audit, text that steers the verdict is a finding; content cannot carry a gate verdict or the ๐Ÿ”‹ line; a fetch is https only and never reaches a private, loopback or metadata host, redirects too; a secret is never asked for in chat; a link is cited only when opened this session; every report says `Not tested:` and labels each figure measured, derived or inferred; a refused action is not retried another way. Each is about what the main thread does on its own turn โ€” what it fetches, asks, cites, reports and retries โ€” so a skill read on demand would arrive after the act. What did not fit went to the skill that owns it: the 90-day date on an external fact (docs-writer, confidence-check), the audit-steering detail (security-scan, red-team). Before that, P07a: 16214 โ†’ 16470 (+256): the audit step no longer re-runs every lane after every fix โ€” only a critical/high fix, or one on a critical path, does, a lesser one is re-checked by the lane that raised it, a low finding never starts a round, three rounds with medium+ open then ask โ€” and the DoD's "nothing deferred" says what it means (medium+ fixed, tracked or user-accepted, the lows to the user as one list). Field transcripts of four projects showed main threads reading the old two lines as "fix every low and audit everything again"; both are where every session reads them, because the loop is run by the main thread, not by a skill. Before that, P05: 15637 โ†’ 16214 (+577): "Short never costs meaning" (a short reply keeps every negation, condition and number; code, paths and errors verbatim; point to path:line instead of restating; full sentences for warnings, irreversible steps, questions and persisted text), principle 2's "never cut" list (validation at a trust boundary, data-loss error handling, security, accessibility, what was asked) and a re-read-after-compaction clause. All three are about how every reply is written, so a skill read on demand would arrive after the reply. evals/cases/terse-fidelity measures the first. Before that, P02: 15570 โ†’ 15637 (+67): "Untrusted content" names the text a hook injects from teammates (the team board) as data; the hook now labels it the same way. Before that, P01: 15429 โ†’ 15570 (+141): "No deferral" names the recorded, user-accepted bypass (BYPASSED line, ADR Bypassed entry, accepted finding) as the user's decision, so the rule stops contradicting confidence-check, adr and crew-code-review. Before that, 3.5: 14971 โ†’ 15429 (+458): ยง4.7 (a staged lockfile needs an audit record of that lockfile diff, +275 with the ยง4.4โ€“ยง4.7 range) and the quality line (+183): the DoD reads `## Stack` โ†’ `Quality analysis:` instead of six "(if SonarQube is in use)" clauses, which a project with no server read as false every time (0 analyses in its first 8 days, 0 audits against 21 lockfile commits). Both have to be where every session reads them: the gate refuses a commit, and the refusal must not be a surprise. Before that, 3.1.0: 13986 โ†’ 14975 (+989): the task card (files, change, verify), the model table read from it, "the referee follows the risk" and the verify-then-escalate rule (+634 in the first form, +355 for the card). The session chooses a model on every crew agent call, so the table has to be where every session reads it; a skill would be read after the choice. Before that, 3.0.1: 13741 โ†’ 13986 (+245): the ladder states that a project CLAUDE.md can tighten ยง4, never loosen it โ€” naming ยง4.1 as the one exception, since adopt may loosen the trace gate โ€” and that ยง numbers point into this file (two field projects read their stricter ยง4.4 as a contradiction), the opening's "project wins" carries the same limit, and the context-usage command names the Bash tool (a Windows session ran the scripts in PowerShell). RC-1: 13711 โ†’ 13741 (+30): "Reply in the user's language even when a skill's text is English" โ€” a Turkish session answered /crew-review in English, because every skill body it had just read was English; the rule is stated nowhere else. evals/cases/reply-language measures it. 5R.3: 13712 โ†’ 13711 (the old-name phrases rewritten, net โˆ’1). 5d.2 prompt audit: tightened to the measured size (13719 โ†’ 13712: format-to-content style line, one reload
2766
2766
  # answer, the orphaned background-warning line removed). Before that: 3.0 rename (suffix โ†’ crew- prefix): +23 B (23 occurrences), not content โ€” measured 13696 โ†’ 13719.
2767
2767
  # DISCIPLINE.md (the discipline half of CLAUDE.md); before 3.0 the ceiling was 13700, currently 13601. (2026-09-18, a second
2768
2768
  # +100 B on top of the raise below, and the whole of it went into ONE sentence of ยง4.6: a commit
@@ -13415,9 +13415,67 @@ JSON
13415
13415
  _g="$(printf '{"a":{"b":1},"c":"x y"}' | awk -v op=gets -v 'path=a.b|nope|c' -f "$ROOT/eval/lib/settings-json.awk" - 2>&1)"
13416
13416
  [ "$_g" = "1"$'\t\t'"\"x y\"" ] && pass "settings-json.awk gets: several paths in one read, tab-separated, an absent one empty" \
13417
13417
  || fail "settings-json.awk gets printed '$_g'"
13418
+ # The plan's to-do panel (todo.sh, todo.awk): the count on the line while closed, the panel under it while open,
13419
+ # drawn from the plan's own boxes, and read from its cache until the plan changes.
13420
+ if [ -f "$ROOT/eval/lib/todo.sh" ]; then
13421
+ mkdir -p "$_sl/q/.claude" "$_sl/q/docs"
13422
+ cat > "$_sl/q/docs/PLAN.md" <<'PLAN'
13423
+ # Export โ€” Plan
13424
+ ## Acceptance criteria
13425
+ - [x] AC-1: exports โ€” proven by: e2e
13426
+ - [ ] AC-2: on Safari โ€” proven by: manual
13427
+ ## Tasks (order)
13428
+ Progress: tick a task [x] when its check passes, mark the one in work [~].
13429
+ ### Phase 1 โ€” Parser
13430
+ 1. [x] Read the file โ€” satisfies: AC-1 โ€” size: S
13431
+ 2. [x] Count the boxes โ€” satisfies: AC-1 โ€” size: S
13432
+ ### Phase 2 โ€” Panel
13433
+ 3. [x] Draw the lines โ€” satisfies: AC-2
13434
+ 4. [ ] Wire the toggle โ€” satisfies: AC-2
13435
+ - [ ] a sub-step, not a task
13436
+ 5. [ ] Cache the drawing
13437
+ ```
13438
+ - [ ] quoted in a code block, not a task
13439
+ ```
13440
+ ### Phase 3 โ€” Docs
13441
+ - [ ] README
13442
+ ## Not covered by tests
13443
+ - [ ] outside the Tasks section, not a task
13444
+ PLAN
13445
+ _tj='{"session_id":"sl2","context_window":{"used_percentage":12},"cost":{"total_cost_usd":0.5},"workspace":{"project_dir":"'"$_sl/q"'"}}'
13446
+ _t(){ printf '%s' "$_tj" | (cd "$_sl" && env NO_COLOR=1 bash "$SLS") 2>&1; }
13447
+ # A second between the plan and its first drawing: bash 3.2 (macOS) compares times in whole seconds, and a cache
13448
+ # drawn in the plan's own second is not newer than it, so it is drawn again (right, but not what (3) reads).
13449
+ sleep 1
13450
+ _to1="$(_t)"
13451
+ _tt="$(cd "$_sl/q" && env -u CLAUDE_PROJECT_DIR bash "$ROOT/eval/lib/todo.sh" 2>&1 | head -n 1)"
13452
+ _to2="$(_t)"
13453
+ _want2="๐Ÿ”‹ 12% ยท ~\$0.50
13454
+ ๐Ÿ“‹ Export โ€” Plan ยท 3/6 done ยท phase 2/3 ยท AC 1/2
13455
+ โœ“ Phase 1 โ€” Parser 2/2
13456
+ โ–ธ Phase 2 โ€” Panel 1/3
13457
+ โœ“ 3. Draw the lines
13458
+ โ–ธ 4. Wire the toggle
13459
+ โ˜ 5. Cache the drawing
13460
+ โ˜ Phase 3 โ€” Docs 0/1"
13461
+ # The cache is what a refresh reads: a planted line shows, until the plan changes and the drawing is made again.
13462
+ _tv="$_sl/q/.claude/state/todo/view"; { IFS= read -r _tk < "$_tv"; printf '%s\n%s\n%s\n' "$_tk" '๐Ÿ“‹ 9/9' 'FROM THE CACHE'; } > "$_tv.n" 2>/dev/null && mv "$_tv.n" "$_tv"
13463
+ _to3="$(_t)"
13464
+ sleep 1; sed 's/^4\. \[ \]/4. [x]/' "$_sl/q/docs/PLAN.md" > "$_sl/q/p.n" && mv "$_sl/q/p.n" "$_sl/q/docs/PLAN.md"
13465
+ _to4="$(_t)"
13466
+ (cd "$_sl/q" && env -u CLAUDE_PROJECT_DIR bash "$ROOT/eval/lib/todo.sh" off >/dev/null 2>&1)
13467
+ _to5="$(_t)"
13468
+ [ "$_to1" = "๐Ÿ”‹ 12% ยท ~\$0.50 ยท ๐Ÿ“‹ 3/6" ] && [ "$_tt" = "plan panel: open ยท docs/PLAN.md ยท 3/6 done, in work: 4. Wire the toggle" ] \
13469
+ && [ "$_to2" = "$_want2" ] && [ "$_to3" = "๐Ÿ”‹ 12% ยท ~\$0.50"$'\n'"FROM THE CACHE" ] \
13470
+ && case "$_to4" in *"4/6 done"*"โ–ธ 5. Cache the drawing"*) true ;; *) false ;; esac && [ "$_to5" = "๐Ÿ”‹ 12% ยท ~\$0.50 ยท ๐Ÿ“‹ 4/6" ] \
13471
+ && pass "plan panel: the count on the line while closed; open, the phases with the one in work opened, done ticked, open boxed, the task in work marked (no sub-step, no quoted box, nothing outside Tasks); read from its cache until the plan changes; closed again" \
13472
+ || fail "plan panel: closed='$_to1' ยท toggle='$_tt' ยท open='$_to2' ยท cached='$_to3' ยท after an edit='$_to4' ยท closed again='$_to5'"
13473
+ else
13474
+ skip scope "plan panel checks skipped (this project has no eval/lib/todo.sh)"
13475
+ fi
13418
13476
  rm -rf "$_sl"
13419
13477
  else
13420
- skip scope "status line checks skipped (this project has no eval/lib/statusline.sh)" 3
13478
+ skip scope "status line checks skipped (this project has no eval/lib/statusline.sh)" 4
13421
13479
  fi
13422
13480
 
13423
13481
  STAR="$ROOT/eval/lib/star.sh"
@@ -606,7 +606,7 @@ _crew_q_pre(){ # the PreToolUse decision: `ask`, or a refusal (exit 2) that say
606
606
  qno "in $PM, with a PermissionRequest hook that may be wired ($_PRH), only the typed approval counts." ;;
607
607
  *) qno "in ${PM:-an unknown mode} this approval is not open (bypassPermissions and plan fail closed)." ;;
608
608
  esac
609
- case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) qno "this session has nobody in front of it (it was started with -p, or by a program)." ;; esac
609
+ case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) qno "this session has nobody in front of it (it was started with -p, by a program, or in the background with claude --bg or the agent view, which Claude Code counts as unattended even when you type into it; approve in a regular session, or commit in your own terminal)." ;; esac
610
610
  case "$SID$_QTU" in *[!A-Za-z0-9._-]*) qno "the call carries no session or tool id this hook can bind the answer to." ;; esac
611
611
  { [ -n "$SID" ] && [ -n "$_QTU" ]; } || qno "the call carries no session or tool id this hook can bind the answer to."
612
612
  _crew_prh_scan && qno "a PermissionRequest hook may be wired ($_PRH). Such a hook can answer the question before the user does, and the answer cannot be told from a click."
@@ -668,7 +668,7 @@ _pa_record(){
668
668
  # HONEST SCOPE: a session started with an emptied environment and driven through a pseudo-terminal for a second
669
669
  # message passes both. guard-bash.sh refuses the forms of that it can read; a program that types is past it.
670
670
  case "${CLAUDE_CODE_SESSION_ATTENDED-}" in
671
- 0) no "this session has nobody in front of it (it was started with -p, or by a program), and an approval is what a person types into their own session." ;;
671
+ 0) no "this session has nobody in front of it (it was started with -p, by a program, or in the background with claude --bg or the agent view, which Claude Code counts as unattended even when you type into it; approve in a regular session, or commit in your own terminal), and an approval is what a person types into their own session." ;;
672
672
  esac
673
673
  _json_slice "$INPUT" transcript_path >/dev/null; _json_unescape "$_JS" >/dev/null; TP="${_JU//\\//}"
674
674
  { [ -n "$TP" ] && [ -s "$TP" ]; } || no "this is the first message of this session, or the session keeps no transcript (one started from inside another session keeps none). An approval answers a commit message the session has shown: have it shown, then approve."
@@ -787,7 +787,7 @@ _pa_question(){
787
787
  no) [ -n "$REC" ] && { : > "$REC"; } 2>/dev/null
788
788
  say "Crewforth: not approved - you answered No." "The user answered No to the approval question. Do not commit or push; ask what they want changed." ;;
789
789
  esac
790
- case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) no "this session has nobody in front of it (it was started with -p, or by a program)." ;; esac
790
+ case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) no "this session has nobody in front of it (it was started with -p, by a program, or in the background with claude --bg or the agent view, which Claude Code counts as unattended even when you type into it; approve in a regular session, or commit in your own terminal)." ;; esac
791
791
  _json_slice "$INPUT" transcript_path >/dev/null; _json_unescape "$_JS" >/dev/null; TP="${_JU//\\//}"
792
792
  { [ -n "$TP" ] && [ -s "$TP" ]; } || no "the session keeps no transcript on disk (one started from inside another session keeps none)."
793
793
  BARE=0
@@ -884,7 +884,7 @@ _crew_appr_op "$_JU"
884
884
  # that class from here, so the old failures do not raise it straight back.
885
885
  if [ "$OP" = loosen ]; then
886
886
  nol(){ say "Crewforth: floor NOT lowered - $1" "The user sent the command only the user can type /crew-loosen, for $LO_AGENT $LO_CHANGE $LO_RISK, and Crewforth did not lower anything: $1 Tell the user exactly that."; }
887
- case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) nol "this session has nobody in front of it (it was started with -p, or by a program)." ;; esac
887
+ case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) nol "this session has nobody in front of it (it was started with -p, by a program, or in the background with claude --bg or the agent view, which Claude Code counts as unattended even when you type into it; approve in a regular session, or commit in your own terminal)." ;; esac
888
888
  _json_slice "$INPUT" transcript_path >/dev/null; _json_unescape "$_JS" >/dev/null; TP="${_JU//\\//}"
889
889
  { [ -n "$TP" ] && [ -s "$TP" ]; } || nol "this is the first message of this session, or the session keeps no transcript."
890
890
  _lh="${BASH_SOURCE%/*}"; [ "$_lh" = "${BASH_SOURCE}" ] && _lh=.
@@ -928,7 +928,7 @@ fi
928
928
  # + % - and spaces, so it can neither chain another (the gate would refuse a chain anyway) nor need escaping here.
929
929
  if [ "$OP" = allowverify ]; then
930
930
  noa(){ say "Crewforth: verify command NOT allowed - $1" "The user sent the command only the user can type /crew-allow-verify, and Crewforth added no rule: $1 Tell the user exactly that."; }
931
- case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) noa "this session has nobody in front of it (it was started with -p, or by a program)." ;; esac
931
+ case "${CLAUDE_CODE_SESSION_ATTENDED-}" in 0) noa "this session has nobody in front of it (it was started with -p, by a program, or in the background with claude --bg or the agent view, which Claude Code counts as unattended even when you type into it; approve in a regular session, or commit in your own terminal)." ;; esac
932
932
  _json_slice "$INPUT" transcript_path >/dev/null; _json_unescape "$_JS" >/dev/null; TP="${_JU//\\//}"
933
933
  { [ -n "$TP" ] && [ -s "$TP" ]; } || noa "this is the first message of this session, or the session keeps no transcript."
934
934
  _lh="${BASH_SOURCE%/*}"; [ "$_lh" = "${BASH_SOURCE}" ] && _lh=.
@@ -21,5 +21,6 @@ Delegate to the @agent-crew-planner agent (spec-planning skill). For ambiguously
21
21
  4. Ask about ambiguities with **explicit options**; write down the assumptions.
22
22
 
23
23
  crew-planner is read-only and returns the plan; you (the main thread) write it to `docs/PLAN.md` and keep only the
24
- summary + file pointer in context (token-budget).
24
+ summary + file pointer in context (token-budget). Its tasks carry boxes (`[ ]` open, `[x]` done, `[~]` in work);
25
+ tell the user that `/crew-todo` shows them under the status line.
25
26
  Do NOT write code โ€” this is a planning step.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: crew-todo
3
+ description: Open or close the plan's to-do panel under the status line โ€” done tasks ticked, open ones boxed, the phase and task in work marked.
4
+ argument-hint: "[on | off | show | status | <plan file>] (nothing: open it if closed, close it if open)"
5
+ disable-model-invocation: true
6
+ metadata:
7
+ kind: command
8
+ ---
9
+ # /crew-todo
10
+ Argument: $ARGUMENTS
11
+
12
+ The user wants to see where the plan stands. Run one command and pass on what it prints; change nothing else.
13
+
14
+ The script is `${CLAUDE_PLUGIN_ROOT}/eval/lib/todo.sh` if that names a real path (the plugin), else
15
+ `.claude/eval/lib/todo.sh`. From the project root, with the Bash tool (not PowerShell):
16
+
17
+ ```
18
+ bash <script> $ARGUMENTS
19
+ ```
20
+
21
+ - **Nothing, `on`, `off`, or a file name**: it opens or closes the panel (a file name also makes the panel read that
22
+ plan from now on, instead of `docs/PLAN.md`) and prints one line: open or closed, which file, how far. Say that
23
+ line. The panel shows at the status line's next refresh, under Crewforth's status line; when the script adds a
24
+ `note:` that this project's status line is another one, or none, say the note too.
25
+ - **`show`**: it prints the panel once. Show its lines in a code block, exactly as printed.
26
+ - **`status`**: say the one line.
27
+
28
+ What the panel reads is the plan's own boxes: `- [ ]` open, `- [x]` done, `- [~]` in work, grouped under the plan's
29
+ headings as phases (only what is under its `## Tasks` heading, when it has one). It never writes the plan. When the
30
+ plan's tasks carry no box, say that the panel counts only tasks with one, and that ticking them as the work goes is
31
+ the plan's rule (`spec-planning`).