@aragon-agent/cli 0.6.34 → 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.
- package/CHANGELOG.md +21 -0
- package/README.md +173 -7
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +67 -3
- package/dist/agent/controller.js +106 -5
- package/dist/agent/headless.js +31 -0
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- package/dist/agent/reducer.d.ts +13 -2
- package/dist/agent/reducer.js +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/agents-command.d.ts +15 -0
- package/dist/commands/agents-command.js +27 -0
- package/dist/commands/builtins.js +135 -4
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/commands/perf.d.ts +11 -0
- package/dist/commands/perf.js +2 -1
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/app-paths.d.ts +15 -0
- package/dist/config/app-paths.js +33 -2
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +28 -2
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.js +39 -0
- package/dist/config/store.js +12 -1
- package/dist/config/ui-state.d.ts +18 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +25 -0
- package/dist/exec/events.js +4 -0
- package/dist/exec/index.js +16 -1
- package/dist/exec/runner.js +10 -0
- package/dist/input/keymap.js +18 -4
- package/dist/proc/limits.d.ts +16 -0
- package/dist/proc/limits.js +16 -0
- package/dist/proc/supervisor.d.ts +20 -1
- package/dist/proc/supervisor.js +42 -14
- package/dist/prompts/command.d.ts +34 -0
- package/dist/prompts/command.js +237 -0
- package/dist/prompts/copy.d.ts +89 -0
- package/dist/prompts/copy.js +131 -0
- package/dist/prompts/fill.d.ts +51 -0
- package/dist/prompts/fill.js +46 -0
- package/dist/prompts/limits.d.ts +53 -0
- package/dist/prompts/limits.js +53 -0
- package/dist/prompts/normalize.d.ts +53 -0
- package/dist/prompts/normalize.js +190 -0
- package/dist/prompts/resolve.d.ts +31 -0
- package/dist/prompts/resolve.js +60 -0
- package/dist/prompts/search.d.ts +58 -0
- package/dist/prompts/search.js +146 -0
- package/dist/prompts/store.d.ts +86 -0
- package/dist/prompts/store.js +453 -0
- package/dist/prompts/types.d.ts +204 -0
- package/dist/prompts/types.js +13 -0
- package/dist/session/checkpoint.d.ts +8 -1
- package/dist/session/checkpoint.js +6 -0
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +13 -1
- package/dist/session/startup-session.d.ts +7 -2
- package/dist/session/startup-session.js +5 -2
- package/dist/session/store.js +3 -0
- package/dist/session/validate-session.js +64 -1
- package/dist/team/limits.d.ts +25 -0
- package/dist/team/limits.js +26 -0
- package/dist/team/runtime.js +27 -2
- package/dist/team/settled.d.ts +57 -0
- package/dist/team/settled.js +87 -0
- package/dist/team/subagent.js +48 -2
- package/dist/team/timeline.d.ts +74 -0
- package/dist/team/timeline.js +101 -0
- package/dist/team/types.d.ts +24 -1
- package/dist/todo/acceptance.d.ts +120 -0
- package/dist/todo/acceptance.js +399 -0
- package/dist/todo/follow-through.d.ts +48 -23
- package/dist/todo/follow-through.js +110 -44
- package/dist/todo/limits.d.ts +62 -1
- package/dist/todo/limits.js +62 -1
- package/dist/todo/panel-rows.d.ts +29 -39
- package/dist/todo/panel-rows.js +37 -83
- package/dist/todo/plan-sections.d.ts +60 -0
- package/dist/todo/plan-sections.js +108 -0
- package/dist/todo/prompt.d.ts +17 -12
- package/dist/todo/prompt.js +59 -40
- package/dist/todo/row-window.d.ts +51 -0
- package/dist/todo/row-window.js +107 -0
- package/dist/todo/store.d.ts +139 -31
- package/dist/todo/store.js +271 -73
- package/dist/todo/todo-tool.d.ts +33 -8
- package/dist/todo/todo-tool.js +186 -30
- package/dist/todo/types.d.ts +58 -0
- package/dist/ui/App.js +414 -54
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +17 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +22 -2
- package/dist/ui/ScrollbackShell.d.ts +22 -7
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +18 -0
- package/dist/ui/StatusBar.js +11 -3
- package/dist/ui/TeamPanel.d.ts +35 -41
- package/dist/ui/TeamPanel.js +53 -163
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +23 -3
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/entries/TodoCard.d.ts +26 -11
- package/dist/ui/entries/TodoCard.js +56 -15
- package/dist/ui/glyphs.d.ts +19 -0
- package/dist/ui/glyphs.js +4 -0
- package/dist/ui/layout/agents-board.d.ts +102 -0
- package/dist/ui/layout/agents-board.js +389 -0
- package/dist/ui/layout/cells.d.ts +61 -0
- package/dist/ui/layout/cells.js +156 -0
- package/dist/ui/layout/header-hints.d.ts +23 -3
- package/dist/ui/layout/header-hints.js +73 -7
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/status-detail-layout.js +9 -0
- package/dist/ui/layout/status-layout.d.ts +13 -0
- package/dist/ui/layout/team-row.d.ts +139 -0
- package/dist/ui/layout/team-row.js +246 -0
- package/dist/ui/layout/todo-layout.d.ts +38 -0
- package/dist/ui/layout/todo-layout.js +75 -0
- package/dist/ui/layout/virtual-window.js +32 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +24 -2
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
- package/dist/ui/overlays/PromptEditorPage.js +227 -0
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
- package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
- package/dist/ui/overlays/PromptPicker.d.ts +36 -0
- package/dist/ui/overlays/PromptPicker.js +129 -0
- package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
- package/dist/ui/overlays/PromptTextArea.js +141 -0
- package/dist/ui/overlays/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
- package/dist/ui/overlays/prompt-library-state.js +335 -0
- package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
- package/dist/ui/overlays/prompt-picker-state.js +98 -0
- package/dist/ui/scrollback/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/tail-window.d.ts +12 -0
- package/dist/ui/scrollback/tail-window.js +17 -2
- package/dist/ui/scrollback/use-scrollback-view.js +7 -0
- package/dist/ui/team-status.d.ts +166 -0
- package/dist/ui/team-status.js +252 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-startup-notices.d.ts +35 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.js +10 -2
- package/package.json +2 -2
package/dist/todo/todo-tool.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `todo_write` — the ONLY writer of `TodoStore` (todo-plan-execution §3.3 /
|
|
3
|
-
* §4
|
|
2
|
+
* `todo_write` — the ONLY writer of `TodoStore` (todo-plan-execution §3.3 / §4.1;
|
|
3
|
+
* todo-acceptance-criteria §6.4).
|
|
4
4
|
*
|
|
5
5
|
* ASCII ONLY: `src/todo/**` is inside the glyph scanner's scope.
|
|
6
6
|
*
|
|
7
7
|
* FULL-LIST REPLACEMENT, NO INCREMENTAL OPERATIONS (D-1). A delta protocol
|
|
8
8
|
* (`todo_add` / `todo_complete`) lets the model's belief and the rendered panel
|
|
9
|
-
* drift apart with nothing anywhere able to notice, and needs an id the model
|
|
10
|
-
*
|
|
11
|
-
* replacement is idempotent, order-free and self-healing.
|
|
9
|
+
* drift apart with nothing anywhere able to notice, and needs an id the model must
|
|
10
|
+
* keep stable across calls - a field it will eventually get wrong. A full
|
|
11
|
+
* replacement is idempotent, order-free and self-healing. The acceptance table
|
|
12
|
+
* rides in the SAME call for exactly that reason: a second tool would be a second
|
|
13
|
+
* writer, with two refusal texts and a model free to update only half its plan.
|
|
12
14
|
*
|
|
13
15
|
* THE SCHEMA DECLARES SHAPE, NOT POLICY (C-8): no `enum`, no `maxItems`, no
|
|
14
|
-
* `minItems`, and `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* `minItems`, and `acceptance` is not `required`. `ajv` is an OPTIONAL dependency
|
|
17
|
+
* of core, so any of those would reject a payload where it happens to be installed
|
|
18
|
+
* and repair the identical payload where it is not. Every bound lives in the
|
|
19
|
+
* `description` (what the model reads) and in `write()` (what is tested).
|
|
17
20
|
*
|
|
18
21
|
* PURE STATE: no filesystem, no network, no child process, no human wait. It
|
|
19
22
|
* therefore must NOT be routed through `withPausedWatchdog` (C-9) -
|
|
@@ -22,7 +25,8 @@
|
|
|
22
25
|
* human wait.
|
|
23
26
|
*/
|
|
24
27
|
import { errorResult, textResult } from '@aragon-agent/core';
|
|
25
|
-
import {
|
|
28
|
+
import { acceptanceSummary, countUncheckedCriteria, countUnfinishedSteps, derivePlanPhase, planOutstanding, } from './acceptance.js';
|
|
29
|
+
import { ACCEPT_LIMITS, TODO_LIMITS, TODO_RESULT_LIMITS } from './limits.js';
|
|
26
30
|
/**
|
|
27
31
|
* Turned off mid-session. A NON-ERROR result on purpose: the user turned a
|
|
28
32
|
* display off, the model did nothing wrong, and an error result invites a retry.
|
|
@@ -42,11 +46,58 @@ const TODO_WRITE_DESCRIPTION = 'Record and update the plan for a multi-step task
|
|
|
42
46
|
'`in_progress`; mark an item `completed` and the next one `in_progress` in the same call, ' +
|
|
43
47
|
`the moment the step is done. At most ${TODO_LIMITS.maxItems} items; extras are dropped, so ` +
|
|
44
48
|
'keep within it. This list is how the user follows a long task, so keep it current.';
|
|
49
|
+
/**
|
|
50
|
+
* Appended only while the capability is on, so `todo.acceptance: false` leaves the
|
|
51
|
+
* whole parameters object byte-identical to the version without this feature
|
|
52
|
+
* (AC-29 / I-5).
|
|
53
|
+
*/
|
|
54
|
+
const ACCEPTANCE_DESCRIPTION = ' A plan also carries acceptance criteria: how you will know each part is really done. ' +
|
|
55
|
+
'Send them in the same call. When every step is completed, check the criteria ONE at a ' +
|
|
56
|
+
'time, actually run the check you named, and record each verdict with its evidence. A ' +
|
|
57
|
+
'verdict without evidence is not a verdict. Report failures honestly: fix the work, ' +
|
|
58
|
+
'reopen the criterion, check it again.';
|
|
59
|
+
/** The `acceptance` JSON Schema property (§6.4.1). Shape only - no enum, no bounds. */
|
|
60
|
+
const ACCEPTANCE_PARAMETER = {
|
|
61
|
+
type: 'array',
|
|
62
|
+
description: 'How you will KNOW the work is done: one checkable criterion per line. Send the ' +
|
|
63
|
+
'complete list every time, same as todos.',
|
|
64
|
+
items: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
content: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: `One checkable statement, <= ${ACCEPT_LIMITS.contentChars} chars. Not a wish: ` +
|
|
70
|
+
'something you can point at and say yes or no.',
|
|
71
|
+
},
|
|
72
|
+
check: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: `How you will check it, <= ${ACCEPT_LIMITS.checkChars} chars. ` +
|
|
75
|
+
"e.g. 'npm test -- runs green'.",
|
|
76
|
+
},
|
|
77
|
+
status: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'pending | passed | failed. Only after you actually checked it.',
|
|
80
|
+
},
|
|
81
|
+
evidence: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: `What you saw, <= ${ACCEPT_LIMITS.evidenceChars} chars. Required for passed or ` +
|
|
84
|
+
'failed; a verdict without evidence does not count.',
|
|
85
|
+
},
|
|
86
|
+
reopen: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
description: 'true only to undo an earlier verdict and check the same criterion again.',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ['content'],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
45
94
|
export function createTodoTool(deps) {
|
|
46
95
|
return {
|
|
47
96
|
name: 'todo_write',
|
|
48
97
|
label: 'Update todos',
|
|
49
|
-
description:
|
|
98
|
+
description: deps.hasAcceptance()
|
|
99
|
+
? TODO_WRITE_DESCRIPTION + ACCEPTANCE_DESCRIPTION
|
|
100
|
+
: TODO_WRITE_DESCRIPTION,
|
|
50
101
|
parameters: {
|
|
51
102
|
type: 'object',
|
|
52
103
|
properties: {
|
|
@@ -75,49 +126,71 @@ export function createTodoTool(deps) {
|
|
|
75
126
|
required: ['content', 'status'],
|
|
76
127
|
},
|
|
77
128
|
},
|
|
129
|
+
// ABSENT ENTIRELY WHILE THE CAPABILITY IS OFF, so the model is not told
|
|
130
|
+
// about a field that would be ignored (I-5 / AC-29).
|
|
131
|
+
...(deps.hasAcceptance() ? { acceptance: ACCEPTANCE_PARAMETER } : {}),
|
|
78
132
|
},
|
|
133
|
+
// `acceptance` IS NOT REQUIRED, and cannot be: `ajv` is optional in core, so
|
|
134
|
+
// a schema-level requirement would be enforced in some installs and not in
|
|
135
|
+
// others (C-8). The gate lives in `write()` (AC-11).
|
|
79
136
|
required: ['todos'],
|
|
80
137
|
},
|
|
81
138
|
async execute(_id, params) {
|
|
82
|
-
// 1. Turned off mid-session. The tool array is immutable for the life of
|
|
83
|
-
//
|
|
84
|
-
// closure instead.
|
|
139
|
+
// 1. Turned off mid-session. The tool array is immutable for the life of the
|
|
140
|
+
// session (C-1), so `/todo off` cannot unregister - it flips this closure.
|
|
85
141
|
if (!deps.isEnabled()) {
|
|
86
142
|
deps.store.reject(TODO_OFF_REFUSAL);
|
|
87
143
|
return textResult(TODO_OFF_REFUSAL);
|
|
88
144
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
//
|
|
145
|
+
const payload = params;
|
|
146
|
+
const todos = payload.todos;
|
|
147
|
+
// 2. GATED ON AN EMPTY STORE, and that gate is the whole subtlety. Shrinking
|
|
148
|
+
// an EXISTING list to one item is legitimate - the model merged two steps
|
|
149
|
+
// - so the guard must fire only on a fresh plan.
|
|
93
150
|
if (deps.store.isEmpty() &&
|
|
94
151
|
Array.isArray(todos) &&
|
|
95
152
|
todos.length < TODO_LIMITS.minFreshItems) {
|
|
96
153
|
deps.store.reject(TOO_SMALL_REFUSAL);
|
|
97
154
|
return textResult(TOO_SMALL_REFUSAL);
|
|
98
155
|
}
|
|
99
|
-
// 3. Repair, never reject.
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
156
|
+
// 3. Repair, never reject. A refusal leaves BOTH tables untouched (I-4), and
|
|
157
|
+
// the kind decides the result shape: a broken payload is an error (today's
|
|
158
|
+
// behaviour, unchanged), an incomplete plan is not - an error would invite
|
|
159
|
+
// the model to retry the identical payload.
|
|
160
|
+
const result = deps.store.write(params);
|
|
161
|
+
if (!result.accepted) {
|
|
162
|
+
const refusal = result.refusal
|
|
163
|
+
?? 'No usable todo items: each needs a non-empty content string.';
|
|
164
|
+
return result.refusalKind === 'policy' ? textResult(refusal) : errorResult(refusal);
|
|
104
165
|
}
|
|
105
|
-
return textResult(buildResultText(result.items, result.repairs));
|
|
166
|
+
return textResult(buildResultText(result.items, result.acceptance, result.repairs, deps.hasAcceptance()));
|
|
106
167
|
},
|
|
107
168
|
};
|
|
108
169
|
}
|
|
109
170
|
/**
|
|
110
171
|
* The tool result — THE SINGLE STRONGEST LEVER ON R-b ("one at a time"), because
|
|
111
172
|
* unlike the system prompt it is re-read on every single call. There is
|
|
112
|
-
* deliberately NO RATE LIMIT to compete with it: a cap would fire exactly when
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
173
|
+
* deliberately NO RATE LIMIT to compete with it: a cap would fire exactly when the
|
|
174
|
+
* model is being most diligent and produce a refusal it cannot act on, and the
|
|
175
|
+
* costs a cap would have contained are already gone (§3.8 collapses N calls into
|
|
176
|
+
* ONE transcript entry, and the store is memory-only).
|
|
177
|
+
*
|
|
178
|
+
* FOUR PHASES, and the acceptance paragraph is inserted after the "In progress /
|
|
179
|
+
* Every item" paragraph and before any `Note:`. In `verify` and in the two `done`
|
|
180
|
+
* cases it also REPLACES that paragraph's closing sentence, because a plan that
|
|
181
|
+
* carries criteria has something more specific to say about being finished. With the
|
|
182
|
+
* capability off, or with no criteria, every branch below is byte-identical to the
|
|
183
|
+
* version without this feature (AC-28).
|
|
116
184
|
*/
|
|
117
|
-
function buildResultText(items, repairs) {
|
|
185
|
+
export function buildResultText(items, acceptance, repairs, acceptanceEnabled) {
|
|
118
186
|
const done = items.filter((item) => item.status === 'completed').length;
|
|
119
187
|
const activeIndex = items.findIndex((item) => item.status === 'in_progress');
|
|
188
|
+
const phase = derivePlanPhase(items, acceptance);
|
|
120
189
|
const lines = [`Todos updated. ${done}/${items.length} done.`];
|
|
190
|
+
// Only while the capability is on AND there is a table: those are exactly the cases
|
|
191
|
+
// in which `acceptanceParagraph` speaks for the finished plan, and exactly the cases
|
|
192
|
+
// in which `I-5` does not demand the pre-feature bytes.
|
|
193
|
+
const showsCriteria = acceptanceEnabled && acceptance.length > 0;
|
|
121
194
|
if (activeIndex >= 0) {
|
|
122
195
|
lines.push(`In progress: ${items[activeIndex].activeForm}`);
|
|
123
196
|
const next = items.slice(activeIndex + 1).find((item) => item.status === 'pending');
|
|
@@ -125,10 +198,93 @@ function buildResultText(items, repairs) {
|
|
|
125
198
|
lines.push(`Next: ${next.content}`);
|
|
126
199
|
lines.push('Call todo_write again the moment that item is finished - exactly one item in', 'progress at a time, and do not batch several completions into one call.');
|
|
127
200
|
}
|
|
128
|
-
else {
|
|
201
|
+
else if (showsCriteria && phase === 'verify') {
|
|
202
|
+
// REPLACES the paragraph rather than joining it (P1-1). Saying "Every item is
|
|
203
|
+
// complete. Tell the user what you did." and then "Check ONE criterion now" in
|
|
204
|
+
// the same result would end the turn on the first sentence.
|
|
205
|
+
lines.push('Every step is complete. Now verify the acceptance criteria.');
|
|
206
|
+
}
|
|
207
|
+
else if (!(showsCriteria && phase === 'done')) {
|
|
129
208
|
lines.push('Every item is complete. Tell the user what you did.');
|
|
130
209
|
}
|
|
210
|
+
if (showsCriteria) {
|
|
211
|
+
// THE SAME REPLACEMENT RULE AS THE `verify` BRANCH ABOVE, for both `done` cases
|
|
212
|
+
// (§6.4.4). Leaving the generic line in front of the paragraph would print "Tell
|
|
213
|
+
// the user what you did" immediately before "Do not describe the work as done"
|
|
214
|
+
// when a criterion failed - the one sentence this feature exists to say, cancelled
|
|
215
|
+
// by the line above it.
|
|
216
|
+
lines.push(...acceptanceParagraph(items, acceptance, phase));
|
|
217
|
+
}
|
|
131
218
|
for (const repair of repairs)
|
|
132
219
|
lines.push(`Note: ${repair}`);
|
|
133
|
-
return lines
|
|
220
|
+
return boundText(lines);
|
|
221
|
+
}
|
|
222
|
+
/** The phase-specific acceptance paragraph (§6.4.4), verbatim. */
|
|
223
|
+
function acceptanceParagraph(items, acceptance, phase) {
|
|
224
|
+
// `acceptanceSummary` with a DEAD clock, not a second copy of the counting: the
|
|
225
|
+
// paragraph and the rail must agree about `checked`/`failed`, and two loops over
|
|
226
|
+
// the same table is exactly how they stop agreeing.
|
|
227
|
+
const snapshotLike = acceptanceSummary(acceptance, () => 0);
|
|
228
|
+
const outstanding = {
|
|
229
|
+
steps: countUnfinishedSteps(items),
|
|
230
|
+
checks: countUncheckedCriteria(acceptance),
|
|
231
|
+
};
|
|
232
|
+
if (phase === 'work') {
|
|
233
|
+
return [
|
|
234
|
+
`Acceptance: ${snapshotLike.checkedCount}/${snapshotLike.total} checked. ` +
|
|
235
|
+
'Check them one at a time once every step is completed.',
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
if (phase === 'done' && snapshotLike.failedCount === 0) {
|
|
239
|
+
return [
|
|
240
|
+
`Every step is complete and all ${snapshotLike.total} acceptance criteria passed. ` +
|
|
241
|
+
'Tell the user what you did',
|
|
242
|
+
'and how you checked it.',
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
if (phase === 'done') {
|
|
246
|
+
return [
|
|
247
|
+
`Every step is complete but ${snapshotLike.failedCount} of ${snapshotLike.total} ` +
|
|
248
|
+
'acceptance criteria FAILED. Do not describe the work as',
|
|
249
|
+
'done: fix it, reopen the failed criterion (reopen: true), check it again - or tell the user',
|
|
250
|
+
'plainly what is still broken.',
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
const lines = [
|
|
254
|
+
`Acceptance: ${snapshotLike.checkedCount}/${snapshotLike.total} checked`
|
|
255
|
+
+ (snapshotLike.failedCount > 0 ? `, ${snapshotLike.failedCount} failed.` : '.'),
|
|
256
|
+
];
|
|
257
|
+
const open = planOutstanding(items, acceptance, false);
|
|
258
|
+
if (open.length > 0)
|
|
259
|
+
lines.push('Unchecked:', ...open.map((row) => ` ${row}`));
|
|
260
|
+
if (outstanding.checks > open.length) {
|
|
261
|
+
const hidden = outstanding.checks - open.length;
|
|
262
|
+
lines.push(` ... and ${hidden} more (send the complete list to see them all)`);
|
|
263
|
+
}
|
|
264
|
+
lines.push('Check ONE criterion now: actually run its check, then send the complete list with', 'that criterion passed or failed and the evidence you saw.');
|
|
265
|
+
return lines;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* I-11's ceiling on the one text the model re-reads on every call.
|
|
269
|
+
*
|
|
270
|
+
* Truncation is by LINES, because a paragraph cut mid-sentence reads as a model
|
|
271
|
+
* error while a missing last line reads as brevity - and the note says which one
|
|
272
|
+
* happened.
|
|
273
|
+
*/
|
|
274
|
+
function boundText(lines) {
|
|
275
|
+
const text = lines.join('\n');
|
|
276
|
+
if (text.length <= TODO_RESULT_LIMITS.maxChars)
|
|
277
|
+
return text;
|
|
278
|
+
const kept = [];
|
|
279
|
+
let used = 0;
|
|
280
|
+
const budget = TODO_RESULT_LIMITS.maxChars - TODO_RESULT_LIMITS.truncationNote.length - 1;
|
|
281
|
+
for (const line of lines) {
|
|
282
|
+
const cost = line.length + (kept.length > 0 ? 1 : 0);
|
|
283
|
+
if (used + cost > budget)
|
|
284
|
+
break;
|
|
285
|
+
kept.push(line);
|
|
286
|
+
used += cost;
|
|
287
|
+
}
|
|
288
|
+
kept.push(TODO_RESULT_LIMITS.truncationNote);
|
|
289
|
+
return kept.join('\n');
|
|
134
290
|
}
|
package/dist/todo/types.d.ts
CHANGED
|
@@ -24,6 +24,53 @@ export interface TodoItem {
|
|
|
24
24
|
activeForm: string;
|
|
25
25
|
status: TodoStatus;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The two settled states of a criterion, plus "not checked" and "being checked"
|
|
29
|
+
* (todo-acceptance-criteria §6.1). Mutually exclusive and exhaustive.
|
|
30
|
+
*
|
|
31
|
+
* `in_progress` IS A STORE-OWNED DERIVED STATE (v2, P0-1): the model cannot write
|
|
32
|
+
* it, the tool schema does not expose it, normalization always downgrades it to
|
|
33
|
+
* `pending`, and `TodoStore.write()` re-derives at most one after every commit.
|
|
34
|
+
* That is the same discipline `TodoItem`'s "exactly one in_progress" follows: one
|
|
35
|
+
* state has exactly one writer, otherwise the derived phase, the rail's anchor
|
|
36
|
+
* and the transcript card each read a different "criterion being checked".
|
|
37
|
+
*/
|
|
38
|
+
export type AcceptanceStatus = 'pending' | 'in_progress' | 'passed' | 'failed';
|
|
39
|
+
export interface AcceptanceItem {
|
|
40
|
+
/** One checkable statement, sanitized, <= ACCEPT_LIMITS.contentChars. */
|
|
41
|
+
content: string;
|
|
42
|
+
/** How it will be checked (command / file / observation), <= checkChars. */
|
|
43
|
+
check: string;
|
|
44
|
+
status: AcceptanceStatus;
|
|
45
|
+
/** Required for `passed` / `failed`; otherwise the verdict drops to `pending`. */
|
|
46
|
+
evidence: string;
|
|
47
|
+
/**
|
|
48
|
+
* Explicit re-open. Only honoured when the same criterion's normalized
|
|
49
|
+
* `content` is unchanged (§6.2.1 step 9). INPUT ONLY: never stored, consumed
|
|
50
|
+
* and discarded by `write()`.
|
|
51
|
+
*/
|
|
52
|
+
reopen?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** The immutable projection, isomorphic to `TodoSnapshot`. Rebuilt per commit. */
|
|
55
|
+
export interface AcceptanceSnapshot {
|
|
56
|
+
items: AcceptanceItem[];
|
|
57
|
+
total: number;
|
|
58
|
+
/** `passed` + `failed`. */
|
|
59
|
+
checkedCount: number;
|
|
60
|
+
passedCount: number;
|
|
61
|
+
failedCount: number;
|
|
62
|
+
/** Injected clock, for the same reason `TodoSnapshot.updatedAt` has one (P2-4). */
|
|
63
|
+
updatedAt: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `work` - at least one step is not completed.
|
|
67
|
+
* `verify` - every step is completed and at least one criterion is unresolved.
|
|
68
|
+
* `done` - every step is completed and every criterion is settled (or there is
|
|
69
|
+
* no acceptance table at all).
|
|
70
|
+
*
|
|
71
|
+
* DERIVED, NEVER STORED (I-3): `derivePlanPhase` is the only judge.
|
|
72
|
+
*/
|
|
73
|
+
export type PlanPhase = 'work' | 'verify' | 'done';
|
|
27
74
|
/** The immutable projection the UI renders. Rebuilt on every write. */
|
|
28
75
|
export interface TodoSnapshot {
|
|
29
76
|
items: TodoItem[];
|
|
@@ -38,6 +85,17 @@ export interface TodoSnapshot {
|
|
|
38
85
|
* that fails on a slow machine and nowhere else (P2-4).
|
|
39
86
|
*/
|
|
40
87
|
updatedAt: number;
|
|
88
|
+
/**
|
|
89
|
+
* The acceptance projection; `null` when there are no criteria (or the
|
|
90
|
+
* capability is off).
|
|
91
|
+
*
|
|
92
|
+
* REQUIRED, NOT OPTIONAL (fail-closed, §6.1): `TodoStore` is the only writer and
|
|
93
|
+
* it always knows what belongs here. Optional would let one call site silently
|
|
94
|
+
* under-report an entire table. The cost is the 15 existing test fixtures that
|
|
95
|
+
* spell out a `TodoSnapshot` literal (§7.3) - a mechanical edit, done in the
|
|
96
|
+
* same step as this field.
|
|
97
|
+
*/
|
|
98
|
+
acceptance: AcceptanceSnapshot | null;
|
|
41
99
|
}
|
|
42
100
|
/**
|
|
43
101
|
* Why a list went away.
|