@aragon-agent/cli 0.6.35 → 0.6.36

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 (146) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +3173 -3071
  3. package/dist/agent/agent-mode.d.ts +20 -4
  4. package/dist/agent/agent-mode.js +20 -4
  5. package/dist/agent/controller.d.ts +38 -3
  6. package/dist/agent/controller.js +64 -4
  7. package/dist/agent/headless.js +31 -0
  8. package/dist/agent/reducer.d.ts +13 -2
  9. package/dist/agent/reducer.js +1 -0
  10. package/dist/cli.js +12 -0
  11. package/dist/commands/agents-command.d.ts +15 -0
  12. package/dist/commands/agents-command.js +27 -0
  13. package/dist/commands/builtins.js +131 -4
  14. package/dist/commands/perf.d.ts +11 -0
  15. package/dist/commands/perf.js +2 -1
  16. package/dist/commands/registry.d.ts +14 -0
  17. package/dist/config/app-paths.d.ts +15 -0
  18. package/dist/config/app-paths.js +33 -2
  19. package/dist/config/env.d.ts +4 -1
  20. package/dist/config/env.js +28 -2
  21. package/dist/config/load.d.ts +10 -0
  22. package/dist/config/load.js +8 -1
  23. package/dist/config/schema.d.ts +68 -0
  24. package/dist/config/schema.js +39 -0
  25. package/dist/config/store.js +12 -1
  26. package/dist/config/ui-state.d.ts +18 -0
  27. package/dist/config/ui-state.js +9 -0
  28. package/dist/exec/events.d.ts +25 -0
  29. package/dist/exec/events.js +4 -0
  30. package/dist/exec/index.js +16 -1
  31. package/dist/exec/runner.js +10 -0
  32. package/dist/proc/limits.d.ts +16 -0
  33. package/dist/proc/limits.js +16 -0
  34. package/dist/proc/supervisor.d.ts +20 -1
  35. package/dist/proc/supervisor.js +42 -14
  36. package/dist/prompts/command.d.ts +34 -0
  37. package/dist/prompts/command.js +237 -0
  38. package/dist/prompts/copy.d.ts +89 -0
  39. package/dist/prompts/copy.js +131 -0
  40. package/dist/prompts/fill.d.ts +51 -0
  41. package/dist/prompts/fill.js +46 -0
  42. package/dist/prompts/limits.d.ts +53 -0
  43. package/dist/prompts/limits.js +53 -0
  44. package/dist/prompts/normalize.d.ts +53 -0
  45. package/dist/prompts/normalize.js +190 -0
  46. package/dist/prompts/resolve.d.ts +31 -0
  47. package/dist/prompts/resolve.js +60 -0
  48. package/dist/prompts/search.d.ts +58 -0
  49. package/dist/prompts/search.js +146 -0
  50. package/dist/prompts/store.d.ts +86 -0
  51. package/dist/prompts/store.js +453 -0
  52. package/dist/prompts/types.d.ts +204 -0
  53. package/dist/prompts/types.js +13 -0
  54. package/dist/session/checkpoint.d.ts +8 -1
  55. package/dist/session/checkpoint.js +6 -0
  56. package/dist/session/persist.d.ts +19 -1
  57. package/dist/session/persist.js +13 -1
  58. package/dist/session/startup-session.d.ts +7 -2
  59. package/dist/session/startup-session.js +5 -2
  60. package/dist/session/store.js +3 -0
  61. package/dist/session/validate-session.js +64 -1
  62. package/dist/team/limits.d.ts +25 -0
  63. package/dist/team/limits.js +26 -0
  64. package/dist/team/runtime.js +27 -2
  65. package/dist/team/settled.d.ts +57 -0
  66. package/dist/team/settled.js +87 -0
  67. package/dist/team/subagent.js +48 -2
  68. package/dist/team/timeline.d.ts +74 -0
  69. package/dist/team/timeline.js +101 -0
  70. package/dist/team/types.d.ts +24 -1
  71. package/dist/todo/acceptance.d.ts +120 -0
  72. package/dist/todo/acceptance.js +399 -0
  73. package/dist/todo/follow-through.d.ts +48 -23
  74. package/dist/todo/follow-through.js +110 -44
  75. package/dist/todo/limits.d.ts +62 -1
  76. package/dist/todo/limits.js +62 -1
  77. package/dist/todo/panel-rows.d.ts +29 -39
  78. package/dist/todo/panel-rows.js +37 -83
  79. package/dist/todo/plan-sections.d.ts +60 -0
  80. package/dist/todo/plan-sections.js +108 -0
  81. package/dist/todo/prompt.d.ts +17 -12
  82. package/dist/todo/prompt.js +59 -40
  83. package/dist/todo/row-window.d.ts +51 -0
  84. package/dist/todo/row-window.js +107 -0
  85. package/dist/todo/store.d.ts +139 -31
  86. package/dist/todo/store.js +271 -73
  87. package/dist/todo/todo-tool.d.ts +33 -8
  88. package/dist/todo/todo-tool.js +186 -30
  89. package/dist/todo/types.d.ts +58 -0
  90. package/dist/ui/App.js +310 -46
  91. package/dist/ui/Composer.d.ts +6 -2
  92. package/dist/ui/Composer.js +2 -2
  93. package/dist/ui/Header.d.ts +9 -1
  94. package/dist/ui/Header.js +2 -2
  95. package/dist/ui/PromptInput.d.ts +21 -1
  96. package/dist/ui/PromptInput.js +21 -1
  97. package/dist/ui/ScrollbackShell.d.ts +19 -19
  98. package/dist/ui/ScrollbackShell.js +8 -1
  99. package/dist/ui/StatusBar.d.ts +5 -0
  100. package/dist/ui/StatusBar.js +1 -0
  101. package/dist/ui/TeamPanel.d.ts +26 -44
  102. package/dist/ui/TeamPanel.js +46 -135
  103. package/dist/ui/TodoPanel.d.ts +17 -14
  104. package/dist/ui/TodoPanel.js +146 -38
  105. package/dist/ui/Transcript.js +1 -1
  106. package/dist/ui/composer-input.d.ts +10 -0
  107. package/dist/ui/composer-input.js +57 -24
  108. package/dist/ui/entries/TodoCard.d.ts +26 -11
  109. package/dist/ui/entries/TodoCard.js +56 -15
  110. package/dist/ui/glyphs.d.ts +19 -0
  111. package/dist/ui/glyphs.js +4 -0
  112. package/dist/ui/layout/agents-board.d.ts +102 -0
  113. package/dist/ui/layout/agents-board.js +389 -0
  114. package/dist/ui/layout/cells.d.ts +61 -0
  115. package/dist/ui/layout/cells.js +156 -0
  116. package/dist/ui/layout/header-hints.d.ts +13 -3
  117. package/dist/ui/layout/header-hints.js +48 -14
  118. package/dist/ui/layout/status-detail-layout.js +9 -0
  119. package/dist/ui/layout/status-layout.d.ts +13 -0
  120. package/dist/ui/layout/team-row.d.ts +139 -0
  121. package/dist/ui/layout/team-row.js +246 -0
  122. package/dist/ui/layout/todo-layout.d.ts +38 -0
  123. package/dist/ui/layout/todo-layout.js +75 -0
  124. package/dist/ui/layout/virtual-window.js +23 -3
  125. package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
  126. package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
  127. package/dist/ui/overlays/HelpOverlay.js +17 -2
  128. package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
  129. package/dist/ui/overlays/PromptEditorPage.js +227 -0
  130. package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
  131. package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
  132. package/dist/ui/overlays/PromptPicker.d.ts +36 -0
  133. package/dist/ui/overlays/PromptPicker.js +129 -0
  134. package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
  135. package/dist/ui/overlays/PromptTextArea.js +141 -0
  136. package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
  137. package/dist/ui/overlays/prompt-library-state.js +335 -0
  138. package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
  139. package/dist/ui/overlays/prompt-picker-state.js +98 -0
  140. package/dist/ui/scrollback/band.js +14 -1
  141. package/dist/ui/team-status.d.ts +25 -0
  142. package/dist/ui/team-status.js +31 -3
  143. package/dist/ui/use-startup-notices.d.ts +35 -1
  144. package/dist/ui/use-startup-notices.js +29 -2
  145. package/dist/ui/use-wheel-routing.js +9 -1
  146. package/package.json +2 -2
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The rail's two-section row allocation (todo-acceptance-criteria §6.9.2).
3
+ *
4
+ * ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope.
5
+ *
6
+ * ONE ROW IDENTITY, DEFINED BEFORE IT IS IMPLEMENTED, and it is the only
7
+ * bookkeeping this feature does:
8
+ *
9
+ * showGauge 1 row when the gauge rule fires (width >= 20, rows >= 8)
10
+ * + 1 the TODO header row
11
+ * + (compact ? 0 : 1) ONE blank row: the full rail has it, the compact rail does not
12
+ * + todoItemRows
13
+ * + Number(todoFooter)
14
+ * + (acc ? acc.headerRows + acc.itemRows : 0)
15
+ * <= rows
16
+ *
17
+ * `overhead = 3` appears only in the full rung with a `visible` second section:
18
+ * 1 TODO header + 1 blank row + 1 ACCEPT header. THAT BLANK ROW IS THE SAME ROW
19
+ * TWICE OVER - it is the breathing space under the TODO header AND the separator
20
+ * between the two sections - so counting it twice overflows the box by one row,
21
+ * and Ink's `overflow="hidden"` clips the overflow silently. The symptom a user
22
+ * reports is "the panel only shows 2 criteria", which is why this comment is at
23
+ * the top of the file rather than next to the arithmetic.
24
+ *
25
+ * PURE: no I/O, no timers, no imports from `ui/**`. The rail renders what this
26
+ * returns; `/perf` reports the `acceptance.reason` it returns; the status-bar chip
27
+ * asks it whether the rail is already reporting the count. One number, one owner.
28
+ */
29
+ import type { PlanPhase } from './types.js';
30
+ export interface PlanSectionsInput {
31
+ /** The rail's whole row budget (`TodoPanel`'s `rows` prop). */
32
+ rows: number;
33
+ /** The rail's whole column budget, chrome included. */
34
+ width: number;
35
+ phase: PlanPhase;
36
+ todoCount: number;
37
+ /** 0 means "no acceptance section at all". */
38
+ acceptanceCount: number;
39
+ /** Decided by `TodoPanel` under the existing gauge rule (width >= 20, rows >= 8). */
40
+ showGauge: boolean;
41
+ }
42
+ /**
43
+ * `visible` - header row plus item rows.
44
+ * `summary` - one row only (`AC 2/5`), no header, no items.
45
+ * `absent` - nothing at all; the count has to reach the status-bar chip (I-7).
46
+ */
47
+ export type AcceptanceSectionReason = 'visible' | 'summary' | 'absent';
48
+ export interface PlanSections {
49
+ todoItemRows: number;
50
+ /** Whether the `-N +M` footer is drawn (the existing `compactFooter` semantics). */
51
+ todoFooter: boolean;
52
+ acceptance: {
53
+ reason: AcceptanceSectionReason;
54
+ /** 1 = header row drawn; 0 = summary row only. */
55
+ headerRows: number;
56
+ /** > 0 to draw item rows. */
57
+ itemRows: number;
58
+ } | null;
59
+ }
60
+ export declare function buildPlanSections(input: PlanSectionsInput): PlanSections;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The rail's two-section row allocation (todo-acceptance-criteria §6.9.2).
3
+ *
4
+ * ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope.
5
+ *
6
+ * ONE ROW IDENTITY, DEFINED BEFORE IT IS IMPLEMENTED, and it is the only
7
+ * bookkeeping this feature does:
8
+ *
9
+ * showGauge 1 row when the gauge rule fires (width >= 20, rows >= 8)
10
+ * + 1 the TODO header row
11
+ * + (compact ? 0 : 1) ONE blank row: the full rail has it, the compact rail does not
12
+ * + todoItemRows
13
+ * + Number(todoFooter)
14
+ * + (acc ? acc.headerRows + acc.itemRows : 0)
15
+ * <= rows
16
+ *
17
+ * `overhead = 3` appears only in the full rung with a `visible` second section:
18
+ * 1 TODO header + 1 blank row + 1 ACCEPT header. THAT BLANK ROW IS THE SAME ROW
19
+ * TWICE OVER - it is the breathing space under the TODO header AND the separator
20
+ * between the two sections - so counting it twice overflows the box by one row,
21
+ * and Ink's `overflow="hidden"` clips the overflow silently. The symptom a user
22
+ * reports is "the panel only shows 2 criteria", which is why this comment is at
23
+ * the top of the file rather than next to the arithmetic.
24
+ *
25
+ * PURE: no I/O, no timers, no imports from `ui/**`. The rail renders what this
26
+ * returns; `/perf` reports the `acceptance.reason` it returns; the status-bar chip
27
+ * asks it whether the rail is already reporting the count. One number, one owner.
28
+ */
29
+ import { TODO_LIMITS } from './limits.js';
30
+ /**
31
+ * Clamp, and NEVER silently reverse: `clampRows` requires `lo <= hi` at the call
32
+ * site, which every branch below establishes before it calls (§6.9.2, P2-5).
33
+ * `clampRows(x, 1, 0)` would return 0 - a panel that draws no acceptance rows and
34
+ * says nothing about why.
35
+ */
36
+ function clampRows(value, lo, hi) {
37
+ return Math.min(Math.max(value, lo), hi);
38
+ }
39
+ /**
40
+ * The share of the item budget a `work`/`done` rail gives the criteria: 0.3, so the
41
+ * two sections trade rows rather than one starving the other (§6.9.2 step 6). The
42
+ * `verify` phase does not use it - there the criteria take everything above one row.
43
+ */
44
+ const ACCEPTANCE_SHARE = 0.3;
45
+ export function buildPlanSections(input) {
46
+ const rows = Math.max(0, Math.floor(input.rows));
47
+ const acceptanceCount = Math.max(0, Math.floor(input.acceptanceCount));
48
+ const todoCount = Math.max(0, Math.floor(input.todoCount));
49
+ // 1. The existing definition of "compact", unchanged.
50
+ const compact = rows < TODO_LIMITS.panelFullRows;
51
+ // 2. What is left after the gauge, unchanged.
52
+ const available = rows - Number(input.showGauge);
53
+ // 3. The existing footer rule, unchanged.
54
+ const todoFooter = compact && todoCount > rows - 1;
55
+ // 4. No criteria: BYTE-IDENTICAL to the pre-feature arithmetic (I-5).
56
+ if (acceptanceCount === 0) {
57
+ return {
58
+ todoItemRows: compact
59
+ ? rows - 1 - Number(todoFooter)
60
+ : rows - 2 - Number(input.showGauge),
61
+ todoFooter,
62
+ acceptance: null,
63
+ };
64
+ }
65
+ // 5. Compact rung: rows in {3,4,5}, the gauge never fires, available === rows.
66
+ if (compact) {
67
+ const base = rows - 1 - Number(todoFooter);
68
+ // Lend one row to the summary line, but never the TODO window's last row.
69
+ // `absent` is reachable ONLY at rows === 3 with a footer (base === 1), which
70
+ // is the one "panel is up, section is not" combination (AC-34).
71
+ if (base - 1 >= 1) {
72
+ return {
73
+ todoItemRows: base - 1,
74
+ todoFooter,
75
+ acceptance: { reason: 'summary', headerRows: 0, itemRows: 1 },
76
+ };
77
+ }
78
+ return {
79
+ todoItemRows: base,
80
+ todoFooter,
81
+ acceptance: { reason: 'absent', headerRows: 0, itemRows: 0 },
82
+ };
83
+ }
84
+ // 6. Full rung: rows >= 6. Three rows of chrome for the second section.
85
+ const budget = available - 3;
86
+ if (input.phase === 'verify') {
87
+ // Every step is done, so the step list carries no information - one row says
88
+ // so and the rest goes to the criteria the eye is actually on (D-7).
89
+ return {
90
+ todoItemRows: 1,
91
+ todoFooter: false,
92
+ acceptance: {
93
+ reason: 'visible',
94
+ headerRows: 1,
95
+ itemRows: clampRows(budget - 1, 1, acceptanceCount),
96
+ },
97
+ };
98
+ }
99
+ // `work` AND `done` share this branch on purpose: a plan that just passed every
100
+ // criterion must keep showing the table it passed - that is the screen the user
101
+ // wants to read - rather than fold like `verify` does.
102
+ const accItemRows = clampRows(Math.round(budget * ACCEPTANCE_SHARE), 1, Math.min(acceptanceCount, budget - 1));
103
+ return {
104
+ todoItemRows: budget - accItemRows,
105
+ todoFooter: false,
106
+ acceptance: { reason: 'visible', headerRows: 1, itemRows: accItemRows },
107
+ };
108
+ }
@@ -1,5 +1,6 @@
1
1
  /**
2
- * The `<todo_planning>` system-prompt block (todo-plan-execution §3.7).
2
+ * The `<todo_planning>` system-prompt block (todo-plan-execution §3.7;
3
+ * todo-acceptance-criteria §6.5).
3
4
  *
4
5
  * ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope, and this string
5
6
  * also reaches a model that may be running in a `cmd.exe` terminal.
@@ -7,8 +8,9 @@
7
8
  * SPLICED CONDITIONALLY by `buildSystemPrompt`, which is what preserves C-10:
8
9
  * with `todo.enabled: false` the prompt is BYTE-IDENTICAL to the pre-todo output
9
10
  * for a fixed tool array. An unconditional guidance line anywhere here would
10
- * quietly break `--no-todo`, `--no-team`, `--no-skills` and the plan-mode
11
- * snapshot all at once.
11
+ * quietly break `--no-todo`, `--no-team`, `--no-skills` and the plan-mode snapshot
12
+ * all at once. The acceptance paragraph obeys the same rule one level down: with
13
+ * `acceptance: false` the block is byte-identical to v1.
12
14
  *
13
15
  * THE BLOCK'S THRESHOLD (3+ steps) IS STRICTER THAN THE STRUCTURAL GUARD
14
16
  * (`minFreshItems`, 2). A backstop looser than the guidance never argues with a
@@ -16,24 +18,27 @@
16
18
  * separate deliverables is fine, and the prompt simply does not encourage it.
17
19
  *
18
20
  * `TODO_BLOCK_VERSION` exists so a change to this wording is greppable from a
19
- * behaviour report - INCLUDING a change to either `{{VISIBILITY}}` variant,
20
- * which is part of the block.
21
+ * behaviour report - INCLUDING a change to either `{{VISIBILITY}}` variant, which
22
+ * is part of the block.
21
23
  */
22
24
  import { TODO_BLOCK_VERSION } from './limits.js';
23
25
  export { TODO_BLOCK_VERSION };
24
26
  export interface TodoBlockParams {
25
27
  panelVisible: boolean;
28
+ /**
29
+ * `todo.acceptance`, read LIVE. False reproduces the v1 block byte for byte
30
+ * (the version constant itself is not inside the block, so it cannot spoil the
31
+ * comparison).
32
+ */
33
+ acceptance: boolean;
26
34
  }
27
35
  /**
28
- * BOUNDED AT 1600 CHARACTERS, and the number is asserted in `todo-tool.test.ts`.
36
+ * BOUNDED AT 2400 CHARACTERS, and the number is asserted in `todo-tool.test.ts`.
29
37
  *
30
38
  * The block is paid for on EVERY turn of every todo-enabled session, so anything
31
39
  * that does not change a planning decision does not belong in it (the discipline
32
- * `buildTeamBlock` records at 1200 for a shorter block). This one lands at ~1430
33
- * as designed — three bullet lists plus the `{{VISIBILITY}}` sentence — and was
34
- * NOT trimmed to reach a borrowed budget, because every line in it changes a
35
- * planning decision, which is the test that actually applies. The bound exists
36
- * so that adding a paragraph stays a decision someone makes on purpose. See
37
- * spec §15 IF-3.
40
+ * `buildTeamBlock` records at 1200 for a shorter block). v1 landed at 1433; v2
41
+ * lands at 2300 (2264 for the hidden variant), and the bound exists so that adding a
42
+ * paragraph stays a decision someone makes on purpose rather than a drift.
38
43
  */
39
44
  export declare function buildTodoBlock(params: TodoBlockParams): string;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * The `<todo_planning>` system-prompt block (todo-plan-execution §3.7).
2
+ * The `<todo_planning>` system-prompt block (todo-plan-execution §3.7;
3
+ * todo-acceptance-criteria §6.5).
3
4
  *
4
5
  * ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope, and this string
5
6
  * also reaches a model that may be running in a `cmd.exe` terminal.
@@ -7,8 +8,9 @@
7
8
  * SPLICED CONDITIONALLY by `buildSystemPrompt`, which is what preserves C-10:
8
9
  * with `todo.enabled: false` the prompt is BYTE-IDENTICAL to the pre-todo output
9
10
  * for a fixed tool array. An unconditional guidance line anywhere here would
10
- * quietly break `--no-todo`, `--no-team`, `--no-skills` and the plan-mode
11
- * snapshot all at once.
11
+ * quietly break `--no-todo`, `--no-team`, `--no-skills` and the plan-mode snapshot
12
+ * all at once. The acceptance paragraph obeys the same rule one level down: with
13
+ * `acceptance: false` the block is byte-identical to v1.
12
14
  *
13
15
  * THE BLOCK'S THRESHOLD (3+ steps) IS STRICTER THAN THE STRUCTURAL GUARD
14
16
  * (`minFreshItems`, 2). A backstop looser than the guidance never argues with a
@@ -16,61 +18,78 @@
16
18
  * separate deliverables is fine, and the prompt simply does not encourage it.
17
19
  *
18
20
  * `TODO_BLOCK_VERSION` exists so a change to this wording is greppable from a
19
- * behaviour report - INCLUDING a change to either `{{VISIBILITY}}` variant,
20
- * which is part of the block.
21
+ * behaviour report - INCLUDING a change to either `{{VISIBILITY}}` variant, which
22
+ * is part of the block.
21
23
  */
22
24
  import { TODO_BLOCK_VERSION } from './limits.js';
23
25
  export { TODO_BLOCK_VERSION };
24
26
  /**
25
27
  * The ONLY sentence in the block that mentions a display at all. Everything else
26
- * talks about "the list", which is what makes "the two variants differ in
27
- * exactly one sentence" checkable rather than approximate (AC-42).
28
+ * talks about "the list", which is what makes "the two variants differ in exactly
29
+ * one sentence" checkable rather than approximate - and it is why the acceptance
30
+ * paragraph's visibility sentence is appended HERE rather than written inline:
31
+ * with the capability on, the variants differ in exactly TWO sentences, one per
32
+ * section, in the same position and the same number of lines.
28
33
  */
29
34
  const VISIBLE = 'The user sees this list in a panel beside the conversation, so it is the only place ' +
30
35
  'they can watch a long task progress.';
31
36
  const NOT_VISIBLE = 'The user cannot see this list in this session, but keeping it is still how you stay ' +
32
37
  'on one step at a time and report what is left.';
38
+ /** The acceptance section's one visibility sentence, per variant (§6.5). */
39
+ const ACCEPT_VISIBLE = 'The criteria are shown in the same panel, under the steps, so they are also what the ' +
40
+ 'user watches during the final check.';
41
+ const ACCEPT_NOT_VISIBLE = 'The criteria are still how you keep yourself honest during the final check.';
33
42
  /**
34
- * BOUNDED AT 1600 CHARACTERS, and the number is asserted in `todo-tool.test.ts`.
43
+ * The acceptance paragraph. Five lines of guidance, and each one is there because
44
+ * it changes a planning decision:
45
+ *
46
+ * - the first says criteria exist at all and belong in the SAME call;
47
+ * - the second names the phase transition and the one-at-a-time rule;
48
+ * - the third forbids the "call it done anyway" ending;
49
+ * - the fourth is the only defence against the commonest degeneration, a
50
+ * criterion that is just the step name again (R-5).
51
+ *
52
+ * MEASURED, NOT ESTIMATED (P1-5): this paragraph plus the visibility sentences
53
+ * and two newlines adds 867 characters to a 1433-character block, so the v2 block
54
+ * is asserted at 2300 and bounded at 2400 in `todo-tool.test.ts`. Trimming it to
55
+ * fit 2200 would cost the criterion-versus-step contrast below (143 characters),
56
+ * which is the only mitigation R-5 has - see Q-8.
57
+ */
58
+ const ACCEPTANCE_LINES = [
59
+ 'Every plan also carries ACCEPTANCE CRITERIA: one line each saying how you will know',
60
+ 'that part of the work is really done. The user checks your claim against them.',
61
+ '- Plan them in the SAME todo_write call (the acceptance array), each with the check',
62
+ ' you will run for it.',
63
+ '- Once every step is completed the plan is in VERIFY: check the criteria ONE at a',
64
+ ' time, run the check you named, and record the verdict with its evidence. A verdict',
65
+ ' without evidence is not a verdict.',
66
+ '- A failed check is not a detail: fix the work, reopen that criterion (reopen: true),',
67
+ ' check it again - never call the work done while one has failed.',
68
+ '- A criterion is not a step: "Add the rail" is a step, "the rail shows 3 items on an',
69
+ ' 80x24 terminal" is a criterion.',
70
+ ];
71
+ /**
72
+ * BOUNDED AT 2400 CHARACTERS, and the number is asserted in `todo-tool.test.ts`.
35
73
  *
36
74
  * The block is paid for on EVERY turn of every todo-enabled session, so anything
37
75
  * that does not change a planning decision does not belong in it (the discipline
38
- * `buildTeamBlock` records at 1200 for a shorter block). This one lands at ~1430
39
- * as designed — three bullet lists plus the `{{VISIBILITY}}` sentence — and was
40
- * NOT trimmed to reach a borrowed budget, because every line in it changes a
41
- * planning decision, which is the test that actually applies. The bound exists
42
- * so that adding a paragraph stays a decision someone makes on purpose. See
43
- * spec §15 IF-3.
76
+ * `buildTeamBlock` records at 1200 for a shorter block). v1 landed at 1433; v2
77
+ * lands at 2300 (2264 for the hidden variant), and the bound exists so that adding a
78
+ * paragraph stays a decision someone makes on purpose rather than a drift.
44
79
  */
45
80
  export function buildTodoBlock(params) {
46
- return [
81
+ const lines = [
47
82
  '<todo_planning>',
48
83
  'For work that takes three or more distinct steps, keep a visible plan with the',
49
84
  'todo_write tool.',
50
85
  params.panelVisible ? VISIBLE : NOT_VISIBLE,
51
- '',
52
- 'Use it when:',
53
- '- the request has three or more steps that must happen in order;',
54
- '- the user gave you several things to do in one message;',
55
- '- you are part-way through a long task and the user asks what is left.',
56
- '',
57
- 'Do NOT use it for:',
58
- '- anything you can finish in one or two steps - just do it and say what you did;',
59
- '- a question, an explanation, or a search with no work attached;',
60
- '- restating a step you are already in the middle of.',
61
- '',
62
- 'How to use it:',
63
- '- Send the COMPLETE list every time. The tool replaces the whole list; it has no',
64
- ' add or update operation.',
65
- '- Exactly one item is in_progress at a time. Mark an item completed the moment',
66
- ' it is done and mark the next one in_progress in the SAME call.',
67
- '- Do not batch. Finishing three steps and reporting them together hides where you',
68
- ' are RIGHT NOW, which is the whole point of keeping the list.',
69
- '- Give each item a content ("Add the rail to AppShell") and an activeForm',
70
- ' ("Adding the rail to AppShell"). The list shows activeForm for the step in',
71
- ' progress and content for the rest.',
72
- '- Only mark an item completed when it is really finished. If you hit a blocker,',
73
- ' leave it in_progress, add an item describing what is blocking, and say so.',
74
- '</todo_planning>',
75
- ].join('\n');
86
+ ];
87
+ if (params.acceptance) {
88
+ lines.push(params.panelVisible ? ACCEPT_VISIBLE : ACCEPT_NOT_VISIBLE);
89
+ }
90
+ lines.push('', 'Use it when:', '- the request has three or more steps that must happen in order;', '- the user gave you several things to do in one message;', '- you are part-way through a long task and the user asks what is left.', '', 'Do NOT use it for:', '- anything you can finish in one or two steps - just do it and say what you did;', '- a question, an explanation, or a search with no work attached;', '- restating a step you are already in the middle of.', '', 'How to use it:', '- Send the COMPLETE list every time. The tool replaces the whole list; it has no', ' add or update operation.', '- Exactly one item is in_progress at a time. Mark an item completed the moment', ' it is done and mark the next one in_progress in the SAME call.', '- Do not batch. Finishing three steps and reporting them together hides where you', ' are RIGHT NOW, which is the whole point of keeping the list.', '- Give each item a content ("Add the rail to AppShell") and an activeForm', ' ("Adding the rail to AppShell"). The list shows activeForm for the step in', ' progress and content for the rest.', '- Only mark an item completed when it is really finished. If you hit a blocker,', ' leave it in_progress, add an item describing what is blocking, and say so.');
91
+ if (params.acceptance)
92
+ lines.push(...ACCEPTANCE_LINES);
93
+ lines.push('</todo_planning>');
94
+ return lines.join('\n');
76
95
  }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `selectRowsAround` — the generic anchored window (todo-acceptance-criteria §6.9.4).
3
+
4
+ ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope.
5
+
6
+ PURE WINDOWING, NO OFFSET STATE ANYWHERE (non-goal 2 / R-10). Ink has no absolute
7
+ positioning and this codebase already owns one hand-rolled viewport
8
+ (`ScrollViewport`) whose invariants took a whole spec to pin down; a second
9
+ scrollable region with its own offset, its own intent nonce and its own wheel band
10
+ would be the largest source of new bugs in that feature by a wide margin. The rail
11
+ therefore never scrolls - it windows around the row being worked on and reports
12
+ what is off each end.
13
+
14
+ THE DIRECT DESCENDANT OF `selectPanelRows`'s F-2 LESSON: the team panel originally
15
+ took the first five children by ARRAY INDEX, which showed the five that finished
16
+ FIRST and hid everything still working. Here the equivalent mistake is
17
+ `slice(0, maxRows)`, which on a 20-row plan shows rows 1-8 - all completed - and
18
+ hides the one the user is actually watching.
19
+
20
+ GENERIC OVER THE ROW TYPE (todo-acceptance-criteria §6.9.4): the todo window and
21
+ the acceptance window are the same algorithm over two different item shapes, so
22
+ the anchored-window fixpoint lives here once and three thin wrappers expose it.
23
+ The wrappers' outputs are field-for-field identical to the original todo-only
24
+ implementation (AC-13).
25
+ */
26
+ export interface RowWindow<T> {
27
+ visible: {
28
+ item: T;
29
+ index: number;
30
+ }[];
31
+ hiddenAbove: number;
32
+ hiddenBelow: number;
33
+ }
34
+ /**
35
+ * The anchored contiguous window, WITHOUT reserving overflow-marker rows.
36
+ *
37
+ * `maxRows` is a row count; invalid or non-positive values return an empty window
38
+ * with every row counted below it, which is what makes a caller whose arithmetic
39
+ * produced 0 render nothing rather than render everything.
40
+ */
41
+ export declare function selectCompactRowsAround<T>(items: readonly T[], maxRows: number, anchor: (items: readonly T[]) => number): RowWindow<T>;
42
+ /**
43
+ * The anchored window WITH the two overflow markers accounted for.
44
+ *
45
+ * THE ANCHOR IS ALWAYS VISIBLE, and that beats the row bound when the two
46
+ * conflict. They can only conflict below `maxRows` 3 with overflow on BOTH sides
47
+ * - one row plus two markers is three rows - where this returns the anchor and
48
+ * lets the caller's `overflow="hidden"` clip a marker. Hiding the one row the user
49
+ * is watching in order to keep a `+N above` would be the wrong trade.
50
+ */
51
+ export declare function selectRowsAround<T>(items: readonly T[], maxRows: number, anchor: (items: readonly T[]) => number): RowWindow<T>;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * `selectRowsAround` — the generic anchored window (todo-acceptance-criteria §6.9.4).
3
+
4
+ ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope.
5
+
6
+ PURE WINDOWING, NO OFFSET STATE ANYWHERE (non-goal 2 / R-10). Ink has no absolute
7
+ positioning and this codebase already owns one hand-rolled viewport
8
+ (`ScrollViewport`) whose invariants took a whole spec to pin down; a second
9
+ scrollable region with its own offset, its own intent nonce and its own wheel band
10
+ would be the largest source of new bugs in that feature by a wide margin. The rail
11
+ therefore never scrolls - it windows around the row being worked on and reports
12
+ what is off each end.
13
+
14
+ THE DIRECT DESCENDANT OF `selectPanelRows`'s F-2 LESSON: the team panel originally
15
+ took the first five children by ARRAY INDEX, which showed the five that finished
16
+ FIRST and hid everything still working. Here the equivalent mistake is
17
+ `slice(0, maxRows)`, which on a 20-row plan shows rows 1-8 - all completed - and
18
+ hides the one the user is actually watching.
19
+
20
+ GENERIC OVER THE ROW TYPE (todo-acceptance-criteria §6.9.4): the todo window and
21
+ the acceptance window are the same algorithm over two different item shapes, so
22
+ the anchored-window fixpoint lives here once and three thin wrappers expose it.
23
+ The wrappers' outputs are field-for-field identical to the original todo-only
24
+ implementation (AC-13).
25
+ */
26
+ /**
27
+ * How much of the window sits ABOVE the anchor.
28
+ *
29
+ * A QUARTER, and the asymmetry is the point: completed context above the anchor
30
+ * is sacrificed before upcoming work below it, because the user's question is
31
+ * "what is next", not "what is behind me".
32
+ */
33
+ const ABOVE_SHARE = 0.25;
34
+ function windowFor(count, anchor, slots) {
35
+ if (slots >= count)
36
+ return { start: 0, end: count };
37
+ const above = Math.min(anchor, Math.max(0, Math.floor((slots - 1) * ABOVE_SHARE)));
38
+ let start = anchor - above;
39
+ let end = start + slots;
40
+ if (end > count) {
41
+ end = count;
42
+ start = Math.max(0, end - slots);
43
+ }
44
+ return { start, end };
45
+ }
46
+ /**
47
+ * The anchored contiguous window, WITHOUT reserving overflow-marker rows.
48
+ *
49
+ * `maxRows` is a row count; invalid or non-positive values return an empty window
50
+ * with every row counted below it, which is what makes a caller whose arithmetic
51
+ * produced 0 render nothing rather than render everything.
52
+ */
53
+ export function selectCompactRowsAround(items, maxRows, anchor) {
54
+ const slots = Number.isFinite(maxRows) ? Math.max(0, Math.floor(maxRows)) : 0;
55
+ if (slots === 0 || items.length === 0) {
56
+ return { visible: [], hiddenAbove: 0, hiddenBelow: items.length };
57
+ }
58
+ const { start, end } = windowFor(items.length, anchor(items), slots);
59
+ return {
60
+ visible: items.slice(start, end).map((item, offset) => ({ item, index: start + offset })),
61
+ hiddenAbove: start,
62
+ hiddenBelow: items.length - end,
63
+ };
64
+ }
65
+ /**
66
+ * The anchored window WITH the two overflow markers accounted for.
67
+ *
68
+ * THE ANCHOR IS ALWAYS VISIBLE, and that beats the row bound when the two
69
+ * conflict. They can only conflict below `maxRows` 3 with overflow on BOTH sides
70
+ * - one row plus two markers is three rows - where this returns the anchor and
71
+ * lets the caller's `overflow="hidden"` clip a marker. Hiding the one row the user
72
+ * is watching in order to keep a `+N above` would be the wrong trade.
73
+ */
74
+ export function selectRowsAround(items, maxRows, anchor) {
75
+ const count = items.length;
76
+ const anchorIndex = anchor(items);
77
+ if (count === 0)
78
+ return { visible: [], hiddenAbove: 0, hiddenBelow: 0 };
79
+ if (maxRows <= 0) {
80
+ return {
81
+ visible: [],
82
+ hiddenAbove: anchorIndex,
83
+ hiddenBelow: count - 1 - anchorIndex,
84
+ };
85
+ }
86
+ // The marker rows and the window depend on each other: a `+N above` row costs a
87
+ // slot, which can shrink the window, which can create a `+N below`. Two passes
88
+ // reach the fixpoint for every input this can be handed; the loop bound is a
89
+ // backstop, not a live case.
90
+ let reserved = 0;
91
+ let start = 0;
92
+ let end = count;
93
+ for (let pass = 0; pass < 3; pass += 1) {
94
+ const slots = Math.max(1, maxRows - reserved);
95
+ const win = windowFor(count, anchorIndex, slots);
96
+ start = win.start;
97
+ end = win.end;
98
+ const need = (start > 0 ? 1 : 0) + (end < count ? 1 : 0);
99
+ if (need === reserved)
100
+ break;
101
+ reserved = need;
102
+ }
103
+ const visible = [];
104
+ for (let i = start; i < end; i += 1)
105
+ visible.push({ item: items[i], index: i });
106
+ return { visible, hiddenAbove: start, hiddenBelow: count - end };
107
+ }