sequant 2.3.0 → 2.5.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +125 -160
- package/dist/bin/cli.js +59 -4
- package/dist/dashboard/server.js +1 -0
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +2 -2
- package/dist/marketplace/external_plugins/sequant/README.md +6 -3
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +92 -0
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +18 -9
- package/dist/marketplace/external_plugins/sequant/hooks/relay-check.sh +107 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/behavior-rule-detection.md +205 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +21 -8
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +302 -86
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +109 -0
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +141 -22
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +83 -78
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +377 -137
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +28 -0
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +621 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +741 -232
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +47 -1
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +12 -6
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +217 -964
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/parallel-groups.md +7 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/quality-checklist.md +75 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +4 -2
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +0 -27
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +24 -44
- package/dist/src/commands/abort.d.ts +36 -0
- package/dist/src/commands/abort.js +138 -0
- package/dist/src/commands/prompt.d.ts +7 -0
- package/dist/src/commands/prompt.js +101 -7
- package/dist/src/commands/ready-tui-adapter.d.ts +59 -0
- package/dist/src/commands/ready-tui-adapter.js +130 -0
- package/dist/src/commands/ready.d.ts +49 -0
- package/dist/src/commands/ready.js +243 -0
- package/dist/src/commands/run-progress.d.ts +11 -1
- package/dist/src/commands/run-progress.js +20 -3
- package/dist/src/commands/run.js +12 -2
- package/dist/src/commands/status.js +4 -0
- package/dist/src/commands/watch.d.ts +2 -0
- package/dist/src/commands/watch.js +67 -3
- package/dist/src/lib/assess-collision-detect.js +1 -1
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +39 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +34 -2
- package/dist/src/lib/cli-ui/run-renderer.js +250 -33
- package/dist/src/lib/cli-ui/scrollback-harness.d.ts +112 -0
- package/dist/src/lib/cli-ui/scrollback-harness.js +294 -0
- package/dist/src/lib/merge-check/types.js +1 -1
- package/dist/src/lib/relay/archive.js +6 -0
- package/dist/src/lib/relay/types.d.ts +2 -0
- package/dist/src/lib/relay/types.js +9 -0
- package/dist/src/lib/settings.d.ts +34 -0
- package/dist/src/lib/settings.js +23 -1
- package/dist/src/lib/workflow/batch-executor.js +34 -18
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +48 -1
- package/dist/src/lib/workflow/drivers/aider.d.ts +7 -1
- package/dist/src/lib/workflow/drivers/aider.js +9 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -1
- package/dist/src/lib/workflow/drivers/claude-code.js +51 -2
- package/dist/src/lib/workflow/drivers/index.d.ts +1 -1
- package/dist/src/lib/workflow/event-emitter.d.ts +157 -0
- package/dist/src/lib/workflow/event-emitter.js +102 -0
- package/dist/src/lib/workflow/notice.d.ts +32 -0
- package/dist/src/lib/workflow/notice.js +38 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +9 -21
- package/dist/src/lib/workflow/phase-executor.js +105 -117
- package/dist/src/lib/workflow/phase-mapper.d.ts +26 -13
- package/dist/src/lib/workflow/phase-mapper.js +55 -33
- package/dist/src/lib/workflow/phase-registry.d.ts +127 -0
- package/dist/src/lib/workflow/phase-registry.js +233 -0
- package/dist/src/lib/workflow/platforms/github.d.ts +6 -0
- package/dist/src/lib/workflow/platforms/github.js +17 -0
- package/dist/src/lib/workflow/ready-gate.d.ts +155 -0
- package/dist/src/lib/workflow/ready-gate.js +374 -0
- package/dist/src/lib/workflow/reconcile.js +6 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +5 -55
- package/dist/src/lib/workflow/run-orchestrator.d.ts +32 -2
- package/dist/src/lib/workflow/run-orchestrator.js +125 -11
- package/dist/src/lib/workflow/state-manager.d.ts +19 -1
- package/dist/src/lib/workflow/state-manager.js +27 -1
- package/dist/src/lib/workflow/state-schema.d.ts +23 -35
- package/dist/src/lib/workflow/state-schema.js +29 -3
- package/dist/src/lib/workflow/types.d.ts +74 -15
- package/dist/src/lib/workflow/types.js +18 -13
- package/dist/src/ui/tui/App.js +8 -2
- package/dist/src/ui/tui/IssueBox.js +3 -4
- package/dist/src/ui/tui/index.d.ts +13 -4
- package/dist/src/ui/tui/index.js +19 -5
- package/dist/src/ui/tui/row-cap.d.ts +51 -0
- package/dist/src/ui/tui/row-cap.js +76 -0
- package/dist/src/ui/tui/teardown.d.ts +20 -0
- package/dist/src/ui/tui/teardown.js +29 -0
- package/dist/src/ui/tui/theme.d.ts +3 -0
- package/dist/src/ui/tui/theme.js +3 -0
- package/package.json +23 -11
- package/templates/hooks/post-tool.sh +81 -0
- package/templates/skills/assess/SKILL.md +28 -28
- package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
- package/templates/skills/qa/SKILL.md +5 -2
- package/templates/skills/setup/SKILL.md +6 -6
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Virtual-terminal harness for renderer regression tests (#647).
|
|
3
|
+
*
|
|
4
|
+
* The test stub embedded in TTYRenderer (see {@link
|
|
5
|
+
* ./run-renderer.ts#TTYTestStub}) mocks `log-update` itself — it cannot reveal
|
|
6
|
+
* whether the real `log-update` actually erases prior frames once the terminal
|
|
7
|
+
* scrolls. That gap is what allowed #624's fix to ship green while the
|
|
8
|
+
* underlying duplicate-header bug remained.
|
|
9
|
+
*
|
|
10
|
+
* This harness models a real terminal:
|
|
11
|
+
* - bounded visible viewport (rows × cols)
|
|
12
|
+
* - unbounded scrollback that captures every line that scrolls off the top
|
|
13
|
+
* - the ANSI escape vocabulary that `log-update@7` + `ansi-escapes`
|
|
14
|
+
* actually emit (cursor up/down/forward/back, eraseLine variants,
|
|
15
|
+
* SGR colour stripping, private mode set/reset, save/restore)
|
|
16
|
+
*
|
|
17
|
+
* With it, a test can wire the production renderer through a real
|
|
18
|
+
* `createLogUpdate` instance, replay an event sequence, and assert on
|
|
19
|
+
* `(visible + scrollback)` to catch any duplicate-header rendering — the
|
|
20
|
+
* exact regression #647 was opened for.
|
|
21
|
+
*/
|
|
22
|
+
import { createLogUpdate } from "log-update";
|
|
23
|
+
const ESC = "";
|
|
24
|
+
/**
|
|
25
|
+
* Minimal vt100 model: visible grid + scrollback + cursor. Strips SGR colour
|
|
26
|
+
* codes (they're styling, not content) and ignores private-mode toggles
|
|
27
|
+
* (cursor hide/show). Implements the cursor and erase escapes that
|
|
28
|
+
* `log-update@7` actually emits.
|
|
29
|
+
*/
|
|
30
|
+
export class VirtualTerminal {
|
|
31
|
+
rows;
|
|
32
|
+
cols;
|
|
33
|
+
onlcr;
|
|
34
|
+
/** visible[row][col] = char (always single-codepoint slot). */
|
|
35
|
+
visible;
|
|
36
|
+
/** Scrollback grows oldest-first as rows shift off the top. */
|
|
37
|
+
scrollback = [];
|
|
38
|
+
cursorRow = 0;
|
|
39
|
+
cursorCol = 0;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.rows = opts.rows;
|
|
42
|
+
this.cols = opts.cols;
|
|
43
|
+
this.onlcr = opts.onlcr ?? true;
|
|
44
|
+
this.visible = Array.from({ length: this.rows }, () => Array(this.cols).fill(" "));
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------- input
|
|
47
|
+
write(text) {
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (i < text.length) {
|
|
50
|
+
const ch = text[i];
|
|
51
|
+
if (ch === ESC) {
|
|
52
|
+
i = this.handleEscape(text, i);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (ch === "\n") {
|
|
56
|
+
this.linefeed();
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (ch === "\r") {
|
|
61
|
+
this.cursorCol = 0;
|
|
62
|
+
i++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === "\b") {
|
|
66
|
+
if (this.cursorCol > 0)
|
|
67
|
+
this.cursorCol--;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
this.putChar(ch);
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------- output
|
|
76
|
+
/** Visible viewport as a list of trimmed-right rows. */
|
|
77
|
+
getVisibleLines() {
|
|
78
|
+
return this.visible.map((row) => row.join("").replace(/\s+$/, ""));
|
|
79
|
+
}
|
|
80
|
+
/** Single multi-line string of (scrollback + visible). */
|
|
81
|
+
getAllText() {
|
|
82
|
+
const visibleText = this.getVisibleLines().join("\n");
|
|
83
|
+
if (this.scrollback.length === 0)
|
|
84
|
+
return visibleText;
|
|
85
|
+
return this.scrollback.join("\n") + "\n" + visibleText;
|
|
86
|
+
}
|
|
87
|
+
/** Match count of the regex against (scrollback + visible). */
|
|
88
|
+
countOccurrences(pattern) {
|
|
89
|
+
const text = this.getAllText();
|
|
90
|
+
const flags = pattern.flags.includes("g")
|
|
91
|
+
? pattern.flags
|
|
92
|
+
: pattern.flags + "g";
|
|
93
|
+
const globalPattern = new RegExp(pattern.source, flags);
|
|
94
|
+
const matches = text.match(globalPattern);
|
|
95
|
+
return matches?.length ?? 0;
|
|
96
|
+
}
|
|
97
|
+
// ------------------------------------------------------- internal: text
|
|
98
|
+
putChar(ch) {
|
|
99
|
+
if (this.cursorCol >= this.cols) {
|
|
100
|
+
// Auto-wrap into the next row. Most terminals do this; log-update wraps
|
|
101
|
+
// upstream so this rarely triggers in practice.
|
|
102
|
+
this.cursorCol = 0;
|
|
103
|
+
this.linefeed();
|
|
104
|
+
}
|
|
105
|
+
this.visible[this.cursorRow][this.cursorCol] = ch;
|
|
106
|
+
this.cursorCol++;
|
|
107
|
+
}
|
|
108
|
+
linefeed() {
|
|
109
|
+
if (this.cursorRow + 1 < this.rows) {
|
|
110
|
+
this.cursorRow++;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Bottom of viewport: scroll the top row into scrollback.
|
|
114
|
+
const top = this.visible.shift();
|
|
115
|
+
this.scrollback.push(top.join("").replace(/\s+$/, ""));
|
|
116
|
+
this.visible.push(Array(this.cols).fill(" "));
|
|
117
|
+
// Cursor stays clamped at last visible row.
|
|
118
|
+
}
|
|
119
|
+
if (this.onlcr)
|
|
120
|
+
this.cursorCol = 0;
|
|
121
|
+
}
|
|
122
|
+
// --------------------------------------------------- internal: escapes
|
|
123
|
+
/** Returns the index AFTER the consumed escape sequence. */
|
|
124
|
+
handleEscape(text, start) {
|
|
125
|
+
// Bare ESC at end → consume.
|
|
126
|
+
if (start + 1 >= text.length)
|
|
127
|
+
return text.length;
|
|
128
|
+
const next = text[start + 1];
|
|
129
|
+
// CSI: ESC [ ... <final>
|
|
130
|
+
if (next === "[") {
|
|
131
|
+
return this.handleCSI(text, start + 2);
|
|
132
|
+
}
|
|
133
|
+
// OSC: ESC ] ... BEL or ESC \
|
|
134
|
+
if (next === "]") {
|
|
135
|
+
let i = start + 2;
|
|
136
|
+
while (i < text.length) {
|
|
137
|
+
if (text[i] === "")
|
|
138
|
+
return i + 1;
|
|
139
|
+
if (text[i] === ESC && text[i + 1] === "\\")
|
|
140
|
+
return i + 2;
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
return text.length;
|
|
144
|
+
}
|
|
145
|
+
// 2-byte non-CSI escapes: ESC 7 / ESC 8 (save/restore — cursor only,
|
|
146
|
+
// safe to ignore for our uses).
|
|
147
|
+
return start + 2;
|
|
148
|
+
}
|
|
149
|
+
handleCSI(text, start) {
|
|
150
|
+
let i = start;
|
|
151
|
+
let isPrivate = false;
|
|
152
|
+
if (text[i] === "?" || text[i] === ">" || text[i] === "<") {
|
|
153
|
+
isPrivate = true;
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
let params = "";
|
|
157
|
+
while (i < text.length && /[0-9;]/.test(text[i])) {
|
|
158
|
+
params += text[i];
|
|
159
|
+
i++;
|
|
160
|
+
}
|
|
161
|
+
if (i >= text.length)
|
|
162
|
+
return text.length;
|
|
163
|
+
const final = text[i];
|
|
164
|
+
i++;
|
|
165
|
+
this.executeCSI(params, final, isPrivate);
|
|
166
|
+
return i;
|
|
167
|
+
}
|
|
168
|
+
executeCSI(params, final, isPrivate) {
|
|
169
|
+
const parts = params.length === 0 ? [] : params.split(";").map((p) => parseInt(p, 10));
|
|
170
|
+
const n = (idx, def) => {
|
|
171
|
+
const v = parts[idx];
|
|
172
|
+
return v === undefined || isNaN(v) ? def : v;
|
|
173
|
+
};
|
|
174
|
+
// Private modes (e.g. ?25l/?25h cursor hide/show) — ignore.
|
|
175
|
+
if (isPrivate)
|
|
176
|
+
return;
|
|
177
|
+
switch (final) {
|
|
178
|
+
case "A": // cursor up
|
|
179
|
+
this.cursorRow = Math.max(0, this.cursorRow - n(0, 1));
|
|
180
|
+
return;
|
|
181
|
+
case "B": // cursor down (no scroll)
|
|
182
|
+
this.cursorRow = Math.min(this.rows - 1, this.cursorRow + n(0, 1));
|
|
183
|
+
return;
|
|
184
|
+
case "C": // cursor forward
|
|
185
|
+
this.cursorCol = Math.min(this.cols - 1, this.cursorCol + n(0, 1));
|
|
186
|
+
return;
|
|
187
|
+
case "D": // cursor back
|
|
188
|
+
this.cursorCol = Math.max(0, this.cursorCol - n(0, 1));
|
|
189
|
+
return;
|
|
190
|
+
case "E": // cursor next line
|
|
191
|
+
this.cursorRow = Math.min(this.rows - 1, this.cursorRow + n(0, 1));
|
|
192
|
+
this.cursorCol = 0;
|
|
193
|
+
return;
|
|
194
|
+
case "F": // cursor prev line
|
|
195
|
+
this.cursorRow = Math.max(0, this.cursorRow - n(0, 1));
|
|
196
|
+
this.cursorCol = 0;
|
|
197
|
+
return;
|
|
198
|
+
case "G": // cursor absolute column (1-based)
|
|
199
|
+
this.cursorCol = Math.min(this.cols - 1, Math.max(0, n(0, 1) - 1));
|
|
200
|
+
return;
|
|
201
|
+
case "H": // cursor position (1-based row;col)
|
|
202
|
+
case "f":
|
|
203
|
+
this.cursorRow = Math.min(this.rows - 1, Math.max(0, n(0, 1) - 1));
|
|
204
|
+
this.cursorCol = Math.min(this.cols - 1, Math.max(0, n(1, 1) - 1));
|
|
205
|
+
return;
|
|
206
|
+
case "J": {
|
|
207
|
+
// erase in display
|
|
208
|
+
const mode = n(0, 0);
|
|
209
|
+
if (mode === 0)
|
|
210
|
+
this.eraseFromCursorToEndOfScreen();
|
|
211
|
+
else if (mode === 1)
|
|
212
|
+
this.eraseFromStartOfScreenToCursor();
|
|
213
|
+
else if (mode === 2 || mode === 3)
|
|
214
|
+
this.eraseScreen();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
case "K": {
|
|
218
|
+
// erase in line
|
|
219
|
+
const mode = n(0, 0);
|
|
220
|
+
if (mode === 0)
|
|
221
|
+
this.eraseFromCursorToEndOfLine();
|
|
222
|
+
else if (mode === 1)
|
|
223
|
+
this.eraseFromStartOfLineToCursor();
|
|
224
|
+
else if (mode === 2)
|
|
225
|
+
this.eraseLine();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
case "S": // scroll up
|
|
229
|
+
case "T": // scroll down
|
|
230
|
+
case "m": // SGR colour — ignore (we don't model styling)
|
|
231
|
+
case "s": // save cursor
|
|
232
|
+
case "u": // restore cursor
|
|
233
|
+
case "n": // device status report — ignore
|
|
234
|
+
case "h": // set mode — ignore
|
|
235
|
+
case "l": // reset mode — ignore
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
eraseLine() {
|
|
240
|
+
for (let c = 0; c < this.cols; c++)
|
|
241
|
+
this.visible[this.cursorRow][c] = " ";
|
|
242
|
+
}
|
|
243
|
+
eraseFromCursorToEndOfLine() {
|
|
244
|
+
for (let c = this.cursorCol; c < this.cols; c++)
|
|
245
|
+
this.visible[this.cursorRow][c] = " ";
|
|
246
|
+
}
|
|
247
|
+
eraseFromStartOfLineToCursor() {
|
|
248
|
+
for (let c = 0; c <= this.cursorCol; c++)
|
|
249
|
+
this.visible[this.cursorRow][c] = " ";
|
|
250
|
+
}
|
|
251
|
+
eraseFromCursorToEndOfScreen() {
|
|
252
|
+
this.eraseFromCursorToEndOfLine();
|
|
253
|
+
for (let r = this.cursorRow + 1; r < this.rows; r++) {
|
|
254
|
+
for (let c = 0; c < this.cols; c++)
|
|
255
|
+
this.visible[r][c] = " ";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
eraseFromStartOfScreenToCursor() {
|
|
259
|
+
for (let r = 0; r < this.cursorRow; r++) {
|
|
260
|
+
for (let c = 0; c < this.cols; c++)
|
|
261
|
+
this.visible[r][c] = " ";
|
|
262
|
+
}
|
|
263
|
+
this.eraseFromStartOfLineToCursor();
|
|
264
|
+
}
|
|
265
|
+
eraseScreen() {
|
|
266
|
+
for (let r = 0; r < this.rows; r++) {
|
|
267
|
+
for (let c = 0; c < this.cols; c++)
|
|
268
|
+
this.visible[r][c] = " ";
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
export function createTerminalHarness(opts) {
|
|
273
|
+
const vt = new VirtualTerminal(opts);
|
|
274
|
+
const stream = {
|
|
275
|
+
write: (chunk) => {
|
|
276
|
+
vt.write(chunk);
|
|
277
|
+
return true;
|
|
278
|
+
},
|
|
279
|
+
columns: opts.streamColumns ?? opts.cols,
|
|
280
|
+
rows: opts.streamRows ?? opts.rows,
|
|
281
|
+
isTTY: true,
|
|
282
|
+
};
|
|
283
|
+
// log-update reads `stream.columns` / `stream.rows` defensively; the cast is
|
|
284
|
+
// safe because we exercise only those fields plus `write`.
|
|
285
|
+
const lu = createLogUpdate(stream, {
|
|
286
|
+
showCursor: true,
|
|
287
|
+
});
|
|
288
|
+
return {
|
|
289
|
+
vt,
|
|
290
|
+
logUpdate: lu,
|
|
291
|
+
stdoutWrite: (s) => vt.write(s),
|
|
292
|
+
stderrWrite: (s) => vt.write(s),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
@@ -19,7 +19,7 @@ export const BatchVerdictSchema = z.enum(BATCH_VERDICTS);
|
|
|
19
19
|
*/
|
|
20
20
|
export const DEFAULT_MIRROR_PAIRS = [
|
|
21
21
|
{ source: ".claude/skills", target: "templates/skills" },
|
|
22
|
-
{ source: "hooks", target: "templates/hooks" },
|
|
22
|
+
{ source: ".claude/hooks", target: "templates/hooks" },
|
|
23
23
|
];
|
|
24
24
|
/**
|
|
25
25
|
* Get the git ref to use for diff/merge operations on a branch.
|
|
@@ -57,6 +57,10 @@ export function archiveRelayDir(issue, options) {
|
|
|
57
57
|
copyFileSync(src, join(destDir, name));
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
// Split inbox/outbox counts (#645, Gap 5). Surfaces unanswered queries
|
|
61
|
+
// (inboxCount > outboxCount) when inspecting archives post-hoc.
|
|
62
|
+
const inboxCount = countLines(join(srcDir, RELAY_INBOX));
|
|
63
|
+
const outboxCount = countLines(join(srcDir, RELAY_OUTBOX));
|
|
60
64
|
// Write meta.json.
|
|
61
65
|
const meta = RelayArchiveMetaSchema.parse({
|
|
62
66
|
issue,
|
|
@@ -64,6 +68,8 @@ export function archiveRelayDir(issue, options) {
|
|
|
64
68
|
startedAt: options.startedAt,
|
|
65
69
|
endedAt,
|
|
66
70
|
messageCount: options.messageCount,
|
|
71
|
+
inboxCount,
|
|
72
|
+
outboxCount,
|
|
67
73
|
});
|
|
68
74
|
writeFileSync(join(destDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
69
75
|
// Clear the working relay dir (inbox/outbox/cursor).
|
|
@@ -64,5 +64,7 @@ export declare const RelayArchiveMetaSchema: z.ZodObject<{
|
|
|
64
64
|
startedAt: z.ZodString;
|
|
65
65
|
endedAt: z.ZodString;
|
|
66
66
|
messageCount: z.ZodNumber;
|
|
67
|
+
inboxCount: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
outboxCount: z.ZodOptional<z.ZodNumber>;
|
|
67
69
|
}, z.core.$strip>;
|
|
68
70
|
export type RelayArchiveMeta = z.infer<typeof RelayArchiveMetaSchema>;
|
|
@@ -72,5 +72,14 @@ export const RelayArchiveMetaSchema = z.object({
|
|
|
72
72
|
phase: z.string(),
|
|
73
73
|
startedAt: z.string().datetime(),
|
|
74
74
|
endedAt: z.string().datetime(),
|
|
75
|
+
/** Total inbox + outbox messages exchanged during the run. */
|
|
75
76
|
messageCount: z.number().int().nonnegative(),
|
|
77
|
+
/**
|
|
78
|
+
* Inbox messages (user → agent). Split out from `messageCount` (#645, Gap 5)
|
|
79
|
+
* so post-hoc inspection can spot unanswered queries (inboxCount > outboxCount).
|
|
80
|
+
* Optional for backward compatibility with archives written before this split.
|
|
81
|
+
*/
|
|
82
|
+
inboxCount: z.number().int().nonnegative().optional(),
|
|
83
|
+
/** Outbox replies (agent → user). See `inboxCount` for context. */
|
|
84
|
+
outboxCount: z.number().int().nonnegative().optional(),
|
|
76
85
|
});
|
|
@@ -235,6 +235,24 @@ export interface QASettings {
|
|
|
235
235
|
*/
|
|
236
236
|
markdownOnlySafeCiPatterns: string[];
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Gate policy for the `sequant ready` post-resolve A+ QA gate (#683).
|
|
240
|
+
*
|
|
241
|
+
* - `ac` (default): loop stops once no `AC_NOT_MET` verdict remains (ACs
|
|
242
|
+
* objectively met). Remaining quality/polish gaps are documented in the gap
|
|
243
|
+
* report but NOT auto-fixed — predictable, scope-respecting behavior for a
|
|
244
|
+
* team engineer with a fixed agenda.
|
|
245
|
+
* - `a-plus` (opt-in): loop continues until `READY_FOR_MERGE`, auto-fixing
|
|
246
|
+
* quality gaps along the way — max-quality behavior for a solo maintainer.
|
|
247
|
+
*/
|
|
248
|
+
export type ReadyPolicy = "ac" | "a-plus";
|
|
249
|
+
/**
|
|
250
|
+
* Settings for the `sequant ready` command (#683).
|
|
251
|
+
*/
|
|
252
|
+
export interface ReadySettings {
|
|
253
|
+
/** Default gate policy. Overridable per-invocation with `--policy`. */
|
|
254
|
+
policy: ReadyPolicy;
|
|
255
|
+
}
|
|
238
256
|
/**
|
|
239
257
|
* Full settings schema
|
|
240
258
|
*/
|
|
@@ -249,6 +267,8 @@ export interface SequantSettings {
|
|
|
249
267
|
scopeAssessment: ScopeAssessmentSettings;
|
|
250
268
|
/** QA skill settings */
|
|
251
269
|
qa: QASettings;
|
|
270
|
+
/** `sequant ready` gate settings (#683) */
|
|
271
|
+
ready: ReadySettings;
|
|
252
272
|
}
|
|
253
273
|
/** Zod schema for RotationSettings */
|
|
254
274
|
export declare const RotationSettingsSchema: z.ZodObject<{
|
|
@@ -346,6 +366,13 @@ export declare const QASettingsSchema: z.ZodObject<{
|
|
|
346
366
|
markdownOnlyCiRelaxed: z.ZodDefault<z.ZodBoolean>;
|
|
347
367
|
markdownOnlySafeCiPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
348
368
|
}, z.core.$strip>;
|
|
369
|
+
/** Zod schema for ReadySettings (#683) */
|
|
370
|
+
export declare const ReadySettingsSchema: z.ZodObject<{
|
|
371
|
+
policy: z.ZodDefault<z.ZodEnum<{
|
|
372
|
+
ac: "ac";
|
|
373
|
+
"a-plus": "a-plus";
|
|
374
|
+
}>>;
|
|
375
|
+
}, z.core.$strip>;
|
|
349
376
|
/**
|
|
350
377
|
* Zod schema for the full SequantSettings (AC-1, AC-5).
|
|
351
378
|
*
|
|
@@ -428,6 +455,12 @@ export declare const SettingsSchema: z.ZodObject<{
|
|
|
428
455
|
markdownOnlyCiRelaxed: z.ZodDefault<z.ZodBoolean>;
|
|
429
456
|
markdownOnlySafeCiPatterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
430
457
|
}, z.core.$strip>>;
|
|
458
|
+
ready: z.ZodDefault<z.ZodObject<{
|
|
459
|
+
policy: z.ZodDefault<z.ZodEnum<{
|
|
460
|
+
ac: "ac";
|
|
461
|
+
"a-plus": "a-plus";
|
|
462
|
+
}>>;
|
|
463
|
+
}, z.core.$strip>>;
|
|
431
464
|
}, z.core.$loose>;
|
|
432
465
|
/** A single validation warning about an unknown or invalid setting */
|
|
433
466
|
export interface SettingsWarning {
|
|
@@ -477,6 +510,7 @@ export declare const DEFAULT_SCOPE_ASSESSMENT_SETTINGS: ScopeAssessmentSettings;
|
|
|
477
510
|
* Default QA settings
|
|
478
511
|
*/
|
|
479
512
|
export declare const DEFAULT_QA_SETTINGS: QASettings;
|
|
513
|
+
export declare const DEFAULT_READY_SETTINGS: ReadySettings;
|
|
480
514
|
/**
|
|
481
515
|
* Default settings
|
|
482
516
|
*/
|
package/dist/src/lib/settings.js
CHANGED
|
@@ -127,6 +127,10 @@ export const QASettingsSchema = z.object({
|
|
|
127
127
|
.array(z.string())
|
|
128
128
|
.default(["build (*)", "Plugin Structure Validation"]),
|
|
129
129
|
});
|
|
130
|
+
/** Zod schema for ReadySettings (#683) */
|
|
131
|
+
export const ReadySettingsSchema = z.object({
|
|
132
|
+
policy: z.enum(["ac", "a-plus"]).default("ac"),
|
|
133
|
+
});
|
|
130
134
|
/**
|
|
131
135
|
* Zod schema for the full SequantSettings (AC-1, AC-5).
|
|
132
136
|
*
|
|
@@ -144,6 +148,7 @@ export const SettingsSchema = z
|
|
|
144
148
|
agents: AgentSettingsSchema.default(() => AgentSettingsSchema.parse({})),
|
|
145
149
|
scopeAssessment: ScopeAssessmentSettingsSchema.default(() => ScopeAssessmentSettingsSchema.parse({})),
|
|
146
150
|
qa: QASettingsSchema.default(() => QASettingsSchema.parse({})),
|
|
151
|
+
ready: ReadySettingsSchema.default(() => ReadySettingsSchema.parse({})),
|
|
147
152
|
})
|
|
148
153
|
.passthrough();
|
|
149
154
|
/**
|
|
@@ -151,7 +156,7 @@ export const SettingsSchema = z
|
|
|
151
156
|
* Used to detect unknown/misspelled keys and produce warnings.
|
|
152
157
|
*/
|
|
153
158
|
const KNOWN_KEYS = {
|
|
154
|
-
"": new Set(["version", "run", "agents", "scopeAssessment", "qa"]),
|
|
159
|
+
"": new Set(["version", "run", "agents", "scopeAssessment", "qa", "ready"]),
|
|
155
160
|
run: new Set([
|
|
156
161
|
"logJson",
|
|
157
162
|
"logPath",
|
|
@@ -186,6 +191,7 @@ const KNOWN_KEYS = {
|
|
|
186
191
|
"markdownOnlyCiRelaxed",
|
|
187
192
|
"markdownOnlySafeCiPatterns",
|
|
188
193
|
]),
|
|
194
|
+
ready: new Set(["policy"]),
|
|
189
195
|
"run.rotation": new Set(["enabled", "maxSizeMB", "maxFiles"]),
|
|
190
196
|
"run.aider": new Set(["model", "editFormat", "extraArgs"]),
|
|
191
197
|
"scopeAssessment.trivialThresholds": new Set([
|
|
@@ -323,6 +329,9 @@ export const DEFAULT_QA_SETTINGS = {
|
|
|
323
329
|
markdownOnlyCiRelaxed: true,
|
|
324
330
|
markdownOnlySafeCiPatterns: ["build (*)", "Plugin Structure Validation"],
|
|
325
331
|
};
|
|
332
|
+
export const DEFAULT_READY_SETTINGS = {
|
|
333
|
+
policy: "ac",
|
|
334
|
+
};
|
|
326
335
|
/**
|
|
327
336
|
* Default settings
|
|
328
337
|
*/
|
|
@@ -348,6 +357,7 @@ export const DEFAULT_SETTINGS = {
|
|
|
348
357
|
agents: DEFAULT_AGENT_SETTINGS,
|
|
349
358
|
scopeAssessment: DEFAULT_SCOPE_ASSESSMENT_SETTINGS,
|
|
350
359
|
qa: DEFAULT_QA_SETTINGS,
|
|
360
|
+
ready: DEFAULT_READY_SETTINGS,
|
|
351
361
|
};
|
|
352
362
|
/**
|
|
353
363
|
* Validate aider-specific settings.
|
|
@@ -499,6 +509,12 @@ export function generateSettingsJsonc(settings) {
|
|
|
499
509
|
lines.push(` "model": ${JSON.stringify(settings.agents.model)},`);
|
|
500
510
|
lines.push(` // Isolate parallel agent groups in separate worktrees`);
|
|
501
511
|
lines.push(` "isolateParallel": ${JSON.stringify(settings.agents.isolateParallel)}`);
|
|
512
|
+
lines.push(` },`);
|
|
513
|
+
lines.push("");
|
|
514
|
+
lines.push(` // sequant ready — post-resolve A+ QA gate (#683)`);
|
|
515
|
+
lines.push(` "ready": {`);
|
|
516
|
+
lines.push(` // Gate policy: "ac" (stop at ACs met, report quality gaps) or "a-plus" (loop to READY_FOR_MERGE)`);
|
|
517
|
+
lines.push(` "policy": ${JSON.stringify(settings.ready.policy)}`);
|
|
502
518
|
lines.push(` }`);
|
|
503
519
|
lines.push("}");
|
|
504
520
|
lines.push("");
|
|
@@ -646,6 +662,12 @@ Each threshold has \`yellow\` (warning) and \`red\` (split recommended) values:
|
|
|
646
662
|
| \`markdownOnlyCiRelaxed\` | boolean | \`true\` | When diff touches only \`.md\` files, treat pending CI checks matching \`markdownOnlySafeCiPatterns\` as informational |
|
|
647
663
|
| \`markdownOnlySafeCiPatterns\` | string[] | \`["build (*)", "Plugin Structure Validation"]\` | Glob patterns for CI checks that are safe to ignore when pending on a markdown-only diff |
|
|
648
664
|
|
|
665
|
+
## \`ready\` — \`sequant ready\` Gate Settings (#683)
|
|
666
|
+
|
|
667
|
+
| Key | Type | Default | Description |
|
|
668
|
+
|-----|------|---------|-------------|
|
|
669
|
+
| \`policy\` | enum | \`"ac"\` | Gate policy. \`"ac"\` loops until ACs are objectively met (no \`AC_NOT_MET\`), reporting but not auto-fixing quality gaps. \`"a-plus"\` loops until \`READY_FOR_MERGE\`, auto-fixing quality gaps. Override per-run with \`--policy ac\\|a-plus\`. |
|
|
670
|
+
|
|
649
671
|
---
|
|
650
672
|
|
|
651
673
|
*Unknown keys are preserved but logged as warnings. This allows forward compatibility
|
|
@@ -260,7 +260,7 @@ export function getEnvConfig() {
|
|
|
260
260
|
return config;
|
|
261
261
|
}
|
|
262
262
|
export async function executeBatch(issueNumbers, batchCtx) {
|
|
263
|
-
const { config, options, issueInfoMap, worktreeMap, logWriter, stateManager, shutdownManager, packageManager, baseBranch, onProgress, } = batchCtx;
|
|
263
|
+
const { config, options, issueInfoMap, worktreeMap, logWriter, stateManager, shutdownManager, packageManager, baseBranch, onProgress, onPhasePlan, phasePauseHandle, } = batchCtx;
|
|
264
264
|
const results = [];
|
|
265
265
|
for (const issueNumber of issueNumbers) {
|
|
266
266
|
// Check if shutdown was triggered
|
|
@@ -289,6 +289,8 @@ export async function executeBatch(issueNumbers, batchCtx) {
|
|
|
289
289
|
packageManager,
|
|
290
290
|
baseBranch,
|
|
291
291
|
onProgress,
|
|
292
|
+
onPhasePlan,
|
|
293
|
+
phasePauseHandle,
|
|
292
294
|
};
|
|
293
295
|
const result = await runIssueWithLogging(ctx);
|
|
294
296
|
results.push(result);
|
|
@@ -305,7 +307,7 @@ export async function executeBatch(issueNumbers, batchCtx) {
|
|
|
305
307
|
}
|
|
306
308
|
export async function runIssueWithLogging(ctx) {
|
|
307
309
|
// Destructure context for use throughout the function
|
|
308
|
-
const { issueNumber, config, options, title: issueTitle, labels, services: { logWriter, stateManager, shutdownManager }, worktree, chain, packageManager, baseBranch, onProgress, } = ctx;
|
|
310
|
+
const { issueNumber, config, options, title: issueTitle, labels, services: { logWriter, stateManager, shutdownManager }, worktree, chain, packageManager, baseBranch, onProgress, onPhasePlan, phasePauseHandle, } = ctx;
|
|
309
311
|
const worktreePath = worktree?.path;
|
|
310
312
|
const branch = worktree?.branch;
|
|
311
313
|
const chainMode = chain?.enabled;
|
|
@@ -313,7 +315,8 @@ export async function runIssueWithLogging(ctx) {
|
|
|
313
315
|
const startTime = Date.now();
|
|
314
316
|
const phaseResults = [];
|
|
315
317
|
let loopTriggered = false;
|
|
316
|
-
|
|
318
|
+
// Cross-phase resume token, driver-tagged and cwd-bound (#674).
|
|
319
|
+
let resumeHandle;
|
|
317
320
|
// In parallel mode, suppress per-issue terminal output to prevent interleaving.
|
|
318
321
|
// The caller (run.ts) handles progress display via updateProgress().
|
|
319
322
|
const log = config.parallel ? () => { } : console.log.bind(console);
|
|
@@ -399,15 +402,15 @@ export async function runIssueWithLogging(ctx) {
|
|
|
399
402
|
}
|
|
400
403
|
const specStartTime = new Date();
|
|
401
404
|
// Note: spec runs in main repo (not worktree) for planning
|
|
402
|
-
const specResult = await executePhaseWithRetry(issueNumber, "spec", withActivityHook(config, issueNumber, "spec", onProgress),
|
|
403
|
-
shutdownManager);
|
|
405
|
+
const specResult = await executePhaseWithRetry(issueNumber, "spec", withActivityHook(config, issueNumber, "spec", onProgress), resumeHandle, worktreePath, // Will be ignored for spec (non-isolated phase)
|
|
406
|
+
shutdownManager, phasePauseHandle);
|
|
404
407
|
const specEndTime = new Date();
|
|
405
|
-
if (specResult.
|
|
406
|
-
|
|
407
|
-
//
|
|
408
|
+
if (specResult.resumeHandle) {
|
|
409
|
+
resumeHandle = specResult.resumeHandle;
|
|
410
|
+
// Persist resume token + originCwd for cross-process resume (#674).
|
|
408
411
|
if (stateManager) {
|
|
409
412
|
try {
|
|
410
|
-
await stateManager.
|
|
413
|
+
await stateManager.updateResumeHandle(issueNumber, specResult.resumeHandle);
|
|
411
414
|
}
|
|
412
415
|
catch {
|
|
413
416
|
// State tracking errors shouldn't stop execution
|
|
@@ -568,6 +571,20 @@ export async function runIssueWithLogging(ctx) {
|
|
|
568
571
|
}
|
|
569
572
|
}
|
|
570
573
|
}
|
|
574
|
+
// #672 AC-2: surface the resolved phase pipeline to the renderer so it can
|
|
575
|
+
// seed pending cells for every phase before any one of them fires. This
|
|
576
|
+
// runs once per issue after all phase-list mutations (auto-detect, resume
|
|
577
|
+
// filter, testgen/security-review insertion). The full pipeline for the row
|
|
578
|
+
// is `spec` (if it already ran) plus the remaining `phases` array.
|
|
579
|
+
if (onPhasePlan) {
|
|
580
|
+
const fullPlan = specAlreadyRan ? ["spec", ...phases] : [...phases];
|
|
581
|
+
try {
|
|
582
|
+
onPhasePlan(issueNumber, fullPlan);
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
/* renderer wiring errors must not halt execution */
|
|
586
|
+
}
|
|
587
|
+
}
|
|
571
588
|
// Build per-issue config with issue type metadata for skill env propagation
|
|
572
589
|
const lowerLabelsForType = labels.map((l) => l.toLowerCase());
|
|
573
590
|
const issueIsDocs = lowerLabelsForType.some((label) => DOCS_LABELS.some((docsLabel) => label === docsLabel));
|
|
@@ -610,15 +627,14 @@ export async function runIssueWithLogging(ctx) {
|
|
|
610
627
|
}
|
|
611
628
|
}
|
|
612
629
|
const phaseStartTime = new Date();
|
|
613
|
-
const result = await executePhaseWithRetry(issueNumber, phase, withActivityHook(issueConfig, issueNumber, phase, onProgress),
|
|
630
|
+
const result = await executePhaseWithRetry(issueNumber, phase, withActivityHook(issueConfig, issueNumber, phase, onProgress), resumeHandle, worktreePath, shutdownManager, phasePauseHandle);
|
|
614
631
|
const phaseEndTime = new Date();
|
|
615
|
-
// Capture
|
|
616
|
-
if (result.
|
|
617
|
-
|
|
618
|
-
// Update session ID in state for resume capability
|
|
632
|
+
// Capture resume handle for subsequent phases (#674).
|
|
633
|
+
if (result.resumeHandle) {
|
|
634
|
+
resumeHandle = result.resumeHandle;
|
|
619
635
|
if (stateManager) {
|
|
620
636
|
try {
|
|
621
|
-
await stateManager.
|
|
637
|
+
await stateManager.updateResumeHandle(issueNumber, result.resumeHandle);
|
|
622
638
|
}
|
|
623
639
|
catch {
|
|
624
640
|
// State tracking errors shouldn't stop execution
|
|
@@ -732,7 +748,7 @@ export async function runIssueWithLogging(ctx) {
|
|
|
732
748
|
promptContext: buildLoopContext(result),
|
|
733
749
|
};
|
|
734
750
|
const loopStartTime = new Date();
|
|
735
|
-
const loopResult = await executePhaseWithRetry(issueNumber, "loop", withActivityHook(loopConfig, issueNumber, "loop", onProgress),
|
|
751
|
+
const loopResult = await executePhaseWithRetry(issueNumber, "loop", withActivityHook(loopConfig, issueNumber, "loop", onProgress), resumeHandle, worktreePath, shutdownManager, phasePauseHandle);
|
|
736
752
|
const loopEndTime = new Date();
|
|
737
753
|
phaseResults.push(loopResult);
|
|
738
754
|
// Emit loop completion/failure progress event (AC-8)
|
|
@@ -757,8 +773,8 @@ export async function runIssueWithLogging(ctx) {
|
|
|
757
773
|
/* progress errors must not halt */
|
|
758
774
|
}
|
|
759
775
|
}
|
|
760
|
-
if (loopResult.
|
|
761
|
-
|
|
776
|
+
if (loopResult.resumeHandle) {
|
|
777
|
+
resumeHandle = loopResult.resumeHandle;
|
|
762
778
|
}
|
|
763
779
|
if (loopResult.success) {
|
|
764
780
|
// Continue to next iteration
|