sequant 2.1.2 → 2.3.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +73 -0
- package/dist/bin/cli.js +95 -9
- package/dist/src/commands/doctor.d.ts +25 -0
- package/dist/src/commands/doctor.js +36 -1
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +118 -0
- package/dist/src/commands/locks.d.ts +67 -0
- package/dist/src/commands/locks.js +290 -0
- package/dist/src/commands/merge.js +11 -0
- package/dist/src/commands/prompt.d.ts +39 -0
- package/dist/src/commands/prompt.js +179 -0
- package/dist/src/commands/run-display.d.ts +26 -0
- package/dist/src/commands/run-display.js +150 -0
- package/dist/src/commands/run-progress.d.ts +32 -0
- package/dist/src/commands/run-progress.js +76 -0
- package/dist/src/commands/run.js +83 -73
- package/dist/src/commands/stats.d.ts +2 -0
- package/dist/src/commands/stats.js +94 -8
- package/dist/src/commands/status.js +27 -1
- package/dist/src/commands/watch.d.ts +16 -0
- package/dist/src/commands/watch.js +147 -0
- package/dist/src/lib/ac-linter.d.ts +1 -1
- package/dist/src/lib/ac-linter.js +81 -0
- package/dist/src/lib/assess-collision-detect.d.ts +91 -0
- package/dist/src/lib/assess-collision-detect.js +217 -0
- package/dist/src/lib/assess-comment-parser.d.ts +59 -1
- package/dist/src/lib/assess-comment-parser.js +124 -2
- package/dist/src/lib/cli-ui/format.d.ts +19 -0
- package/dist/src/lib/cli-ui/format.js +34 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +181 -0
- package/dist/src/lib/cli-ui/run-renderer-types.js +7 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +239 -0
- package/dist/src/lib/cli-ui/run-renderer.js +1173 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.d.ts +94 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.js +467 -0
- package/dist/src/lib/locks/index.d.ts +7 -0
- package/dist/src/lib/locks/index.js +5 -0
- package/dist/src/lib/locks/lock-manager.d.ts +168 -0
- package/dist/src/lib/locks/lock-manager.js +433 -0
- package/dist/src/lib/locks/types.d.ts +59 -0
- package/dist/src/lib/locks/types.js +31 -0
- package/dist/src/lib/qa/markdown-only-ci.d.ts +46 -0
- package/dist/src/lib/qa/markdown-only-ci.js +74 -0
- package/dist/src/lib/relay/activation.d.ts +60 -0
- package/dist/src/lib/relay/activation.js +122 -0
- package/dist/src/lib/relay/archive.d.ts +34 -0
- package/dist/src/lib/relay/archive.js +106 -0
- package/dist/src/lib/relay/frame.d.ts +20 -0
- package/dist/src/lib/relay/frame.js +76 -0
- package/dist/src/lib/relay/index.d.ts +13 -0
- package/dist/src/lib/relay/index.js +13 -0
- package/dist/src/lib/relay/paths.d.ts +43 -0
- package/dist/src/lib/relay/paths.js +59 -0
- package/dist/src/lib/relay/pid.d.ts +34 -0
- package/dist/src/lib/relay/pid.js +72 -0
- package/dist/src/lib/relay/reader.d.ts +35 -0
- package/dist/src/lib/relay/reader.js +115 -0
- package/dist/src/lib/relay/types.d.ts +68 -0
- package/dist/src/lib/relay/types.js +76 -0
- package/dist/src/lib/relay/writer.d.ts +48 -0
- package/dist/src/lib/relay/writer.js +113 -0
- package/dist/src/lib/settings.d.ts +31 -1
- package/dist/src/lib/settings.js +18 -3
- package/dist/src/lib/skill-version.d.ts +19 -0
- package/dist/src/lib/skill-version.js +68 -0
- package/dist/src/lib/templates.d.ts +1 -0
- package/dist/src/lib/templates.js +1 -1
- package/dist/src/lib/version-check.d.ts +60 -5
- package/dist/src/lib/version-check.js +97 -9
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +249 -176
- package/dist/src/lib/workflow/config-resolver.js +4 -0
- package/dist/src/lib/workflow/heartbeat.d.ts +71 -0
- package/dist/src/lib/workflow/heartbeat.js +194 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +88 -3
- package/dist/src/lib/workflow/phase-executor.js +276 -52
- package/dist/src/lib/workflow/phase-mapper.d.ts +3 -2
- package/dist/src/lib/workflow/phase-mapper.js +17 -20
- package/dist/src/lib/workflow/platforms/github.d.ts +1 -1
- package/dist/src/lib/workflow/platforms/github.js +20 -3
- package/dist/src/lib/workflow/pr-status.d.ts +18 -2
- package/dist/src/lib/workflow/pr-status.js +41 -9
- package/dist/src/lib/workflow/qa-stagnation.d.ts +117 -0
- package/dist/src/lib/workflow/qa-stagnation.js +179 -0
- package/dist/src/lib/workflow/run-orchestrator.d.ts +76 -0
- package/dist/src/lib/workflow/run-orchestrator.js +382 -29
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/run-state.d.ts +71 -0
- package/dist/src/lib/workflow/run-state.js +14 -0
- package/dist/src/lib/workflow/state-cleanup.d.ts +13 -5
- package/dist/src/lib/workflow/state-cleanup.js +17 -5
- package/dist/src/lib/workflow/state-manager.d.ts +12 -1
- package/dist/src/lib/workflow/state-manager.js +37 -0
- package/dist/src/lib/workflow/state-schema.d.ts +62 -0
- package/dist/src/lib/workflow/state-schema.js +35 -1
- package/dist/src/lib/workflow/types.d.ts +74 -1
- package/dist/src/lib/workflow/worktree-manager.d.ts +12 -4
- package/dist/src/lib/workflow/worktree-manager.js +76 -17
- package/dist/src/mcp/tools/run.d.ts +44 -0
- package/dist/src/mcp/tools/run.js +104 -13
- package/dist/src/ui/tui/App.d.ts +14 -0
- package/dist/src/ui/tui/App.js +41 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +10 -0
- package/dist/src/ui/tui/ElapsedTimer.js +31 -0
- package/dist/src/ui/tui/Header.d.ts +6 -0
- package/dist/src/ui/tui/Header.js +15 -0
- package/dist/src/ui/tui/IssueBox.d.ts +16 -0
- package/dist/src/ui/tui/IssueBox.js +68 -0
- package/dist/src/ui/tui/Spinner.d.ts +9 -0
- package/dist/src/ui/tui/Spinner.js +18 -0
- package/dist/src/ui/tui/index.d.ts +15 -0
- package/dist/src/ui/tui/index.js +29 -0
- package/dist/src/ui/tui/theme.d.ts +29 -0
- package/dist/src/ui/tui/theme.js +52 -0
- package/dist/src/ui/tui/truncate.d.ts +11 -0
- package/dist/src/ui/tui/truncate.js +31 -0
- package/package.json +10 -3
- package/templates/agents/sequant-explorer.md +1 -0
- package/templates/agents/sequant-qa-checker.md +2 -1
- package/templates/agents/sequant-testgen.md +1 -0
- package/templates/hooks/post-tool.sh +11 -0
- package/templates/hooks/pre-tool.sh +18 -9
- package/templates/hooks/relay-check.sh +107 -0
- package/templates/relay/frame.txt +11 -0
- package/templates/scripts/cleanup-worktree.sh +25 -3
- package/templates/scripts/new-feature.sh +6 -0
- package/templates/skills/_shared/references/behavior-rule-detection.md +205 -0
- package/templates/skills/_shared/references/subagent-types.md +21 -8
- package/templates/skills/assess/SKILL.md +261 -94
- package/templates/skills/assess/references/predicted-collision-detection.md +109 -0
- package/templates/skills/docs/SKILL.md +141 -22
- package/templates/skills/exec/SKILL.md +10 -49
- package/templates/skills/fullsolve/SKILL.md +80 -32
- package/templates/skills/loop/SKILL.md +28 -0
- package/templates/skills/merger/SKILL.md +621 -0
- package/templates/skills/qa/SKILL.md +746 -8
- package/templates/skills/qa/scripts/quality-checks.sh +47 -1
- package/templates/skills/setup/SKILL.md +6 -0
- package/templates/skills/spec/SKILL.md +217 -964
- package/templates/skills/spec/references/parallel-groups.md +7 -0
- package/templates/skills/spec/references/quality-checklist.md +75 -0
- package/templates/skills/spec/references/recommended-workflow.md +4 -2
- package/templates/skills/test/SKILL.md +0 -27
- package/templates/skills/testgen/SKILL.md +24 -44
|
@@ -188,10 +188,18 @@ export async function cleanupStaleEntries(options = {}) {
|
|
|
188
188
|
/**
|
|
189
189
|
* Lightweight state reconciliation at run start
|
|
190
190
|
*
|
|
191
|
-
* Checks issues in `ready_for_merge`
|
|
192
|
-
* if their PRs are merged or their branches
|
|
191
|
+
* Checks issues in `ready_for_merge`, `in_progress`, or `waiting_for_qa_gate`
|
|
192
|
+
* state and advances them to `merged` if their PRs are merged or their branches
|
|
193
|
+
* are in main.
|
|
193
194
|
*
|
|
194
|
-
*
|
|
195
|
+
* Including `in_progress` covers the case where a PR was merged outside
|
|
196
|
+
* this sequant session (separate process, `gh pr merge`, web UI) — without
|
|
197
|
+
* it, the run command would re-execute already-merged issues. See #592.
|
|
198
|
+
*
|
|
199
|
+
* Including `waiting_for_qa_gate` covers the symmetric case where a PR
|
|
200
|
+
* awaiting human QA-gate approval is merged externally before the next
|
|
201
|
+
* sequant session — without it, `sequant run <N>` re-executes the QA phase
|
|
202
|
+
* against already-merged work. See #606.
|
|
195
203
|
*
|
|
196
204
|
* @param options - Reconciliation options
|
|
197
205
|
* @returns Result with lists of advanced and still-pending issues
|
|
@@ -213,9 +221,13 @@ export async function reconcileStateAtStartup(options = {}) {
|
|
|
213
221
|
const state = await manager.getState();
|
|
214
222
|
const advanced = [];
|
|
215
223
|
const stillPending = [];
|
|
216
|
-
// Find issues in ready_for_merge state
|
|
224
|
+
// Find issues in ready_for_merge, in_progress, or waiting_for_qa_gate state.
|
|
225
|
+
// in_progress covers PRs merged outside this session (#592).
|
|
226
|
+
// waiting_for_qa_gate covers PRs merged before the next QA-gate run (#606).
|
|
217
227
|
for (const [issueNumStr, issueState] of Object.entries(state.issues)) {
|
|
218
|
-
if (issueState.status !== "ready_for_merge"
|
|
228
|
+
if (issueState.status !== "ready_for_merge" &&
|
|
229
|
+
issueState.status !== "in_progress" &&
|
|
230
|
+
issueState.status !== "waiting_for_qa_gate") {
|
|
219
231
|
continue;
|
|
220
232
|
}
|
|
221
233
|
const issueNum = parseInt(issueNumStr, 10);
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* const state = await manager.getIssueState(42);
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
|
-
import { type WorkflowState, type IssueState, type Phase, type PhaseStatus, type IssueStatus, type PRInfo, type AcceptanceCriteria, type ACStatus } from "./state-schema.js";
|
|
23
|
+
import { type WorkflowState, type IssueState, type Phase, type PhaseStatus, type IssueStatus, type PRInfo, type AcceptanceCriteria, type ACStatus, type RelayState } from "./state-schema.js";
|
|
24
24
|
import type { ScopeAssessment } from "../scope/types.js";
|
|
25
25
|
export interface StateManagerOptions {
|
|
26
26
|
/** Path to state file (default: .sequant/state.json) */
|
|
@@ -133,6 +133,17 @@ export declare class StateManager {
|
|
|
133
133
|
* Update session ID for an issue (for resume)
|
|
134
134
|
*/
|
|
135
135
|
updateSessionId(issueNumber: number, sessionId: string): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Set or clear the relay state for an issue (#383).
|
|
138
|
+
*
|
|
139
|
+
* Pass `null` to remove the relay field entirely (deactivation). Pass an
|
|
140
|
+
* object to overwrite the current relay state (activation or refresh).
|
|
141
|
+
*/
|
|
142
|
+
setRelayState(issueNumber: number, relay: RelayState | null): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Increment the relay message counter. No-op when relay isn't active.
|
|
145
|
+
*/
|
|
146
|
+
incrementRelayMessageCount(issueNumber: number, delta?: number): Promise<void>;
|
|
136
147
|
/**
|
|
137
148
|
* Update loop iteration for an issue
|
|
138
149
|
*/
|
|
@@ -392,6 +392,43 @@ export class StateManager {
|
|
|
392
392
|
await this.saveState(state);
|
|
393
393
|
});
|
|
394
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Set or clear the relay state for an issue (#383).
|
|
397
|
+
*
|
|
398
|
+
* Pass `null` to remove the relay field entirely (deactivation). Pass an
|
|
399
|
+
* object to overwrite the current relay state (activation or refresh).
|
|
400
|
+
*/
|
|
401
|
+
async setRelayState(issueNumber, relay) {
|
|
402
|
+
await this.withLock(async () => {
|
|
403
|
+
const state = await this.getState();
|
|
404
|
+
const issueState = state.issues[String(issueNumber)];
|
|
405
|
+
if (!issueState) {
|
|
406
|
+
throw new Error(`Issue #${issueNumber} not found in state`);
|
|
407
|
+
}
|
|
408
|
+
if (relay === null) {
|
|
409
|
+
delete issueState.relay;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
issueState.relay = relay;
|
|
413
|
+
}
|
|
414
|
+
issueState.lastActivity = new Date().toISOString();
|
|
415
|
+
await this.saveState(state);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Increment the relay message counter. No-op when relay isn't active.
|
|
420
|
+
*/
|
|
421
|
+
async incrementRelayMessageCount(issueNumber, delta = 1) {
|
|
422
|
+
await this.withLock(async () => {
|
|
423
|
+
const state = await this.getState();
|
|
424
|
+
const issueState = state.issues[String(issueNumber)];
|
|
425
|
+
if (!issueState || !issueState.relay)
|
|
426
|
+
return;
|
|
427
|
+
issueState.relay.messageCount += delta;
|
|
428
|
+
issueState.lastActivity = new Date().toISOString();
|
|
429
|
+
await this.saveState(state);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
395
432
|
/**
|
|
396
433
|
* Update loop iteration for an issue
|
|
397
434
|
*/
|
|
@@ -186,6 +186,36 @@ export declare const AcceptanceCriteriaSchema: z.ZodObject<{
|
|
|
186
186
|
}, z.core.$strip>;
|
|
187
187
|
}, z.core.$strip>;
|
|
188
188
|
export type AcceptanceCriteria = z.infer<typeof AcceptanceCriteriaSchema>;
|
|
189
|
+
/**
|
|
190
|
+
* Single QA stagnation observation.
|
|
191
|
+
*
|
|
192
|
+
* Recorded when fullsolve detects a same-SHA same-verdict cycle so future
|
|
193
|
+
* runs can spot "we've been here before" without re-deriving it from comments.
|
|
194
|
+
*/
|
|
195
|
+
export declare const QAStagnationEntrySchema: z.ZodObject<{
|
|
196
|
+
sha: z.ZodString;
|
|
197
|
+
verdict: z.ZodString;
|
|
198
|
+
detectedAt: z.ZodString;
|
|
199
|
+
iteration: z.ZodNumber;
|
|
200
|
+
reason: z.ZodEnum<{
|
|
201
|
+
SAME_SHA_NO_PROGRESS: "SAME_SHA_NO_PROGRESS";
|
|
202
|
+
LOOP_NO_DIFF: "LOOP_NO_DIFF";
|
|
203
|
+
}>;
|
|
204
|
+
}, z.core.$strip>;
|
|
205
|
+
export type QAStagnationEntry = z.infer<typeof QAStagnationEntrySchema>;
|
|
206
|
+
/**
|
|
207
|
+
* Optional relay state for the interactive bidirectional channel (#383).
|
|
208
|
+
*
|
|
209
|
+
* Present when `sequant run` has activated the relay for an issue, absent
|
|
210
|
+
* otherwise. Legacy state files (no relay field) still parse successfully.
|
|
211
|
+
*/
|
|
212
|
+
export declare const RelayStateSchema: z.ZodObject<{
|
|
213
|
+
enabled: z.ZodBoolean;
|
|
214
|
+
pid: z.ZodNumber;
|
|
215
|
+
startedAt: z.ZodString;
|
|
216
|
+
messageCount: z.ZodNumber;
|
|
217
|
+
}, z.core.$strip>;
|
|
218
|
+
export type RelayState = z.infer<typeof RelayStateSchema>;
|
|
189
219
|
/**
|
|
190
220
|
* Complete state for a single issue
|
|
191
221
|
*/
|
|
@@ -301,6 +331,22 @@ export declare const IssueStateSchema: z.ZodObject<{
|
|
|
301
331
|
}, z.core.$strip>;
|
|
302
332
|
recommendation: z.ZodString;
|
|
303
333
|
}, z.core.$strip>>;
|
|
334
|
+
qaStagnation: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
335
|
+
sha: z.ZodString;
|
|
336
|
+
verdict: z.ZodString;
|
|
337
|
+
detectedAt: z.ZodString;
|
|
338
|
+
iteration: z.ZodNumber;
|
|
339
|
+
reason: z.ZodEnum<{
|
|
340
|
+
SAME_SHA_NO_PROGRESS: "SAME_SHA_NO_PROGRESS";
|
|
341
|
+
LOOP_NO_DIFF: "LOOP_NO_DIFF";
|
|
342
|
+
}>;
|
|
343
|
+
}, z.core.$strip>>>;
|
|
344
|
+
relay: z.ZodOptional<z.ZodObject<{
|
|
345
|
+
enabled: z.ZodBoolean;
|
|
346
|
+
pid: z.ZodNumber;
|
|
347
|
+
startedAt: z.ZodString;
|
|
348
|
+
messageCount: z.ZodNumber;
|
|
349
|
+
}, z.core.$strip>>;
|
|
304
350
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
305
351
|
resolvedAt: z.ZodOptional<z.ZodString>;
|
|
306
352
|
lastActivity: z.ZodString;
|
|
@@ -428,6 +474,22 @@ export declare const WorkflowStateSchema: z.ZodObject<{
|
|
|
428
474
|
}, z.core.$strip>;
|
|
429
475
|
recommendation: z.ZodString;
|
|
430
476
|
}, z.core.$strip>>;
|
|
477
|
+
qaStagnation: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
478
|
+
sha: z.ZodString;
|
|
479
|
+
verdict: z.ZodString;
|
|
480
|
+
detectedAt: z.ZodString;
|
|
481
|
+
iteration: z.ZodNumber;
|
|
482
|
+
reason: z.ZodEnum<{
|
|
483
|
+
SAME_SHA_NO_PROGRESS: "SAME_SHA_NO_PROGRESS";
|
|
484
|
+
LOOP_NO_DIFF: "LOOP_NO_DIFF";
|
|
485
|
+
}>;
|
|
486
|
+
}, z.core.$strip>>>;
|
|
487
|
+
relay: z.ZodOptional<z.ZodObject<{
|
|
488
|
+
enabled: z.ZodBoolean;
|
|
489
|
+
pid: z.ZodNumber;
|
|
490
|
+
startedAt: z.ZodString;
|
|
491
|
+
messageCount: z.ZodNumber;
|
|
492
|
+
}, z.core.$strip>>;
|
|
431
493
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
432
494
|
resolvedAt: z.ZodOptional<z.ZodString>;
|
|
433
495
|
lastActivity: z.ZodString;
|
|
@@ -31,7 +31,7 @@ export const PhaseStatusSchema = z.enum([
|
|
|
31
31
|
"in_progress", // Phase currently executing
|
|
32
32
|
"completed", // Phase finished successfully
|
|
33
33
|
"failed", // Phase finished with errors
|
|
34
|
-
"skipped", // Phase intentionally skipped (e.g.,
|
|
34
|
+
"skipped", // Phase intentionally skipped (e.g., prior phase marker already exists)
|
|
35
35
|
]);
|
|
36
36
|
/**
|
|
37
37
|
* Issue status - tracks overall issue progress
|
|
@@ -153,6 +153,36 @@ export const AcceptanceCriteriaSchema = z.object({
|
|
|
153
153
|
blocked: z.number().int().nonnegative(),
|
|
154
154
|
}),
|
|
155
155
|
});
|
|
156
|
+
/**
|
|
157
|
+
* Single QA stagnation observation.
|
|
158
|
+
*
|
|
159
|
+
* Recorded when fullsolve detects a same-SHA same-verdict cycle so future
|
|
160
|
+
* runs can spot "we've been here before" without re-deriving it from comments.
|
|
161
|
+
*/
|
|
162
|
+
export const QAStagnationEntrySchema = z.object({
|
|
163
|
+
/** HEAD SHA at the time of detection */
|
|
164
|
+
sha: z.string(),
|
|
165
|
+
/** QA verdict that repeated (e.g., AC_NOT_MET) */
|
|
166
|
+
verdict: z.string(),
|
|
167
|
+
/** ISO 8601 timestamp of detection */
|
|
168
|
+
detectedAt: z.string().datetime(),
|
|
169
|
+
/** QA loop iteration number where stagnation was detected */
|
|
170
|
+
iteration: z.number().int().nonnegative(),
|
|
171
|
+
/** Reason code: SAME_SHA_NO_PROGRESS or LOOP_NO_DIFF */
|
|
172
|
+
reason: z.enum(["SAME_SHA_NO_PROGRESS", "LOOP_NO_DIFF"]),
|
|
173
|
+
});
|
|
174
|
+
/**
|
|
175
|
+
* Optional relay state for the interactive bidirectional channel (#383).
|
|
176
|
+
*
|
|
177
|
+
* Present when `sequant run` has activated the relay for an issue, absent
|
|
178
|
+
* otherwise. Legacy state files (no relay field) still parse successfully.
|
|
179
|
+
*/
|
|
180
|
+
export const RelayStateSchema = z.object({
|
|
181
|
+
enabled: z.boolean(),
|
|
182
|
+
pid: z.number().int().positive(),
|
|
183
|
+
startedAt: z.string().datetime(),
|
|
184
|
+
messageCount: z.number().int().nonnegative(),
|
|
185
|
+
});
|
|
156
186
|
/**
|
|
157
187
|
* Complete state for a single issue
|
|
158
188
|
*/
|
|
@@ -179,6 +209,10 @@ export const IssueStateSchema = z.object({
|
|
|
179
209
|
acceptanceCriteria: AcceptanceCriteriaSchema.optional(),
|
|
180
210
|
/** Scope assessment result (if performed by /spec) */
|
|
181
211
|
scopeAssessment: ScopeAssessmentSchema.optional(),
|
|
212
|
+
/** QA stagnation log: same-SHA same-verdict cycles detected during fullsolve */
|
|
213
|
+
qaStagnation: z.array(QAStagnationEntrySchema).optional(),
|
|
214
|
+
/** Relay state (#383); present when bidirectional relay is active */
|
|
215
|
+
relay: RelayStateSchema.optional(),
|
|
182
216
|
/** Claude session ID (for resume) */
|
|
183
217
|
sessionId: z.string().optional(),
|
|
184
218
|
/** When the issue transitioned to a terminal status (merged/abandoned/closed) */
|
|
@@ -101,6 +101,21 @@ export interface ExecutionConfig {
|
|
|
101
101
|
* Propagated as SEQUANT_FAILED_ACS env var to skills.
|
|
102
102
|
*/
|
|
103
103
|
failedAcs?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Runtime callback invoked when the agent driver emits a chunk of output
|
|
106
|
+
* during phase execution (#543). Used by the multi-issue TUI to enrich
|
|
107
|
+
* `nowLine` with sub-phase activity. Not serialized — set per-call by the
|
|
108
|
+
* orchestrator. The phase executor throttles calls to ~10 Hz so the TUI's
|
|
109
|
+
* poll budget is preserved.
|
|
110
|
+
*/
|
|
111
|
+
onActivity?: (text: string) => void;
|
|
112
|
+
/**
|
|
113
|
+
* Enable interactive relay (#383). When true, phase-executor sets
|
|
114
|
+
* `SEQUANT_RELAY=true` in the agent environment so the PostToolUse hook
|
|
115
|
+
* starts polling `<worktree>/.sequant/relay/inbox.jsonl` for user messages.
|
|
116
|
+
* Default: false (opt-in for the initial rollout).
|
|
117
|
+
*/
|
|
118
|
+
relayEnabled?: boolean;
|
|
104
119
|
}
|
|
105
120
|
/**
|
|
106
121
|
* Default execution configuration
|
|
@@ -143,6 +158,18 @@ export interface IssueResult {
|
|
|
143
158
|
prNumber?: number;
|
|
144
159
|
/** PR URL if created after successful QA */
|
|
145
160
|
prUrl?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Set when the issue was skipped because another sequant session holds
|
|
163
|
+
* the per-issue lock (#625). Surfaced in the summary as
|
|
164
|
+
* `locked by PID <n>`. When present, `success` is false and the issue
|
|
165
|
+
* was not executed.
|
|
166
|
+
*/
|
|
167
|
+
locked?: {
|
|
168
|
+
pid: number;
|
|
169
|
+
hostname: string;
|
|
170
|
+
startedAt: string;
|
|
171
|
+
command: string;
|
|
172
|
+
};
|
|
146
173
|
}
|
|
147
174
|
/**
|
|
148
175
|
* CLI options for the run command, merged with settings and env config.
|
|
@@ -163,6 +190,7 @@ export interface RunOptions {
|
|
|
163
190
|
smartTests?: boolean;
|
|
164
191
|
noSmartTests?: boolean;
|
|
165
192
|
testgen?: boolean;
|
|
193
|
+
securityReview?: boolean;
|
|
166
194
|
autoDetectPhases?: boolean;
|
|
167
195
|
/** Enable automatic worktree creation for issue isolation */
|
|
168
196
|
worktreeIsolation?: boolean;
|
|
@@ -172,6 +200,12 @@ export interface RunOptions {
|
|
|
172
200
|
quiet?: boolean;
|
|
173
201
|
/** Chain issues: each branches from previous (requires --sequential) */
|
|
174
202
|
chain?: boolean;
|
|
203
|
+
/**
|
|
204
|
+
* Stacked PRs: each non-first PR targets its predecessor branch instead of
|
|
205
|
+
* `main`. Implies --chain. The final PR still targets `main` so partial
|
|
206
|
+
* progress can land without the whole stack. (#605)
|
|
207
|
+
*/
|
|
208
|
+
stacked?: boolean;
|
|
175
209
|
/**
|
|
176
210
|
* Wait for QA pass before starting next issue in chain mode.
|
|
177
211
|
* When enabled, the chain pauses if QA fails, preventing downstream issues
|
|
@@ -239,6 +273,24 @@ export interface RunOptions {
|
|
|
239
273
|
* Resolution priority: CLI flag → settings.agents.isolateParallel → false
|
|
240
274
|
*/
|
|
241
275
|
isolateParallel?: boolean;
|
|
276
|
+
/**
|
|
277
|
+
* Render a live multi-issue dashboard during the run.
|
|
278
|
+
* Requires a TTY; auto-falls back to linear output when stdout is piped.
|
|
279
|
+
* Experimental — surface and behavior may change.
|
|
280
|
+
*/
|
|
281
|
+
experimentalTui?: boolean;
|
|
282
|
+
/**
|
|
283
|
+
* With `--force`, SIGTERM the prior PID holding the per-issue lock
|
|
284
|
+
* before claiming it. Only acts on same-host alive PIDs. (#625)
|
|
285
|
+
*/
|
|
286
|
+
signalOther?: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Interactive relay (#383). Set via `--no-relay`, which Commander surfaces as
|
|
289
|
+
* `options.relay = false`. When `false`, the PostToolUse hook is not
|
|
290
|
+
* activated and `sequant prompt` cannot reach this run.
|
|
291
|
+
* Resolution priority: this CLI flag → settings.run.relay → default (true).
|
|
292
|
+
*/
|
|
293
|
+
relay?: boolean;
|
|
242
294
|
}
|
|
243
295
|
/**
|
|
244
296
|
* CLI arguments for run command
|
|
@@ -271,10 +323,20 @@ export interface BatchResult {
|
|
|
271
323
|
/**
|
|
272
324
|
* Callback type for per-phase progress updates.
|
|
273
325
|
* Used by parallel mode in run.ts to render phase status to the terminal.
|
|
326
|
+
*
|
|
327
|
+
* `extra.iteration` (#624 Item 3): outer quality-loop iteration. Threaded
|
|
328
|
+
* through to the renderer as `(attempt N/M)` on retried phase events and
|
|
329
|
+
* `loop N/M` on loop-phase live-zone status cells.
|
|
330
|
+
*
|
|
331
|
+
* `"activity"` (#543): sub-phase activity ping. `extra.text` carries a short
|
|
332
|
+
* one-line snippet (e.g. last line of agent output) for the dashboard's
|
|
333
|
+
* `nowLine`. Fires at most ~10 Hz from the phase executor.
|
|
274
334
|
*/
|
|
275
|
-
export type ProgressCallback = (issue: number, phase: string, event: "start" | "complete" | "failed", extra?: {
|
|
335
|
+
export type ProgressCallback = (issue: number, phase: string, event: "start" | "complete" | "failed" | "activity", extra?: {
|
|
276
336
|
durationSeconds?: number;
|
|
277
337
|
error?: string;
|
|
338
|
+
iteration?: number;
|
|
339
|
+
text?: string;
|
|
278
340
|
}) => void;
|
|
279
341
|
/**
|
|
280
342
|
* Shared context for executing a batch of issues.
|
|
@@ -325,6 +387,17 @@ export interface IssueExecutionContext {
|
|
|
325
387
|
chain?: {
|
|
326
388
|
enabled: boolean;
|
|
327
389
|
isLast: boolean;
|
|
390
|
+
/**
|
|
391
|
+
* Stacked-PR base branch for this issue. Set only when --stacked is active
|
|
392
|
+
* and this issue has a predecessor in the chain. When set, createPR targets
|
|
393
|
+
* this branch instead of `main`. (#605)
|
|
394
|
+
*/
|
|
395
|
+
predecessorBranch?: string;
|
|
396
|
+
/**
|
|
397
|
+
* Pre-rendered stack manifest line for the PR body, e.g.
|
|
398
|
+
* `Part of stack: #100 → #101 (this) → #102`. Set only under --stacked.
|
|
399
|
+
*/
|
|
400
|
+
stackManifest?: string;
|
|
328
401
|
};
|
|
329
402
|
/** Package manager name (e.g., "npm", "pnpm") */
|
|
330
403
|
packageManager?: string;
|
|
@@ -156,11 +156,12 @@ export declare function ensureWorktreesChain(issues: Array<{
|
|
|
156
156
|
title: string;
|
|
157
157
|
}>, verbose: boolean, packageManager?: string, baseBranch?: string): Promise<Map<number, WorktreeInfo>>;
|
|
158
158
|
/**
|
|
159
|
-
* Create a checkpoint commit in the worktree after QA passes
|
|
160
|
-
*
|
|
159
|
+
* Create a checkpoint commit in the worktree after QA passes.
|
|
160
|
+
* Only stages files that were touched by the issue's commits (diff vs baseBranch).
|
|
161
|
+
* If unrelated dirty files exist, emits a warning and skips the checkpoint.
|
|
161
162
|
* @internal Exported for testing
|
|
162
163
|
*/
|
|
163
|
-
export declare function createCheckpointCommit(worktreePath: string, issueNumber: number, verbose: boolean): boolean;
|
|
164
|
+
export declare function createCheckpointCommit(worktreePath: string, issueNumber: number, verbose: boolean, baseBranch?: string): boolean;
|
|
164
165
|
/**
|
|
165
166
|
* Check if any lockfile changed during a rebase and re-run install if needed.
|
|
166
167
|
* This prevents dependency drift when the lockfile was updated on main.
|
|
@@ -199,7 +200,14 @@ export declare function rebaseBeforePR(worktreePath: string, issueNumber: number
|
|
|
199
200
|
* @param issueTitle Issue title (for PR title)
|
|
200
201
|
* @param branch Branch name
|
|
201
202
|
* @param verbose Whether to show verbose output
|
|
203
|
+
* @param labels Issue labels (used to pick `fix(...)` vs `feat(...)` prefix)
|
|
204
|
+
* @param stackOptions When set under --stacked, `prBase` overrides the default
|
|
205
|
+
* PR target (otherwise gh defaults to the repo's default branch) and
|
|
206
|
+
* `stackManifest` is appended to the PR body. (#605)
|
|
202
207
|
* @returns PRCreationResult with PR info or error
|
|
203
208
|
* @internal Exported for testing
|
|
204
209
|
*/
|
|
205
|
-
export declare function createPR(worktreePath: string, issueNumber: number, issueTitle: string, branch: string, verbose: boolean, labels?: string[]
|
|
210
|
+
export declare function createPR(worktreePath: string, issueNumber: number, issueTitle: string, branch: string, verbose: boolean, labels?: string[], stackOptions?: {
|
|
211
|
+
prBase?: string;
|
|
212
|
+
stackManifest?: string;
|
|
213
|
+
}): PRCreationResult;
|
|
@@ -612,30 +612,80 @@ export async function ensureWorktreesChain(issues, verbose, packageManager, base
|
|
|
612
612
|
return worktrees;
|
|
613
613
|
}
|
|
614
614
|
/**
|
|
615
|
-
* Create a checkpoint commit in the worktree after QA passes
|
|
616
|
-
*
|
|
615
|
+
* Create a checkpoint commit in the worktree after QA passes.
|
|
616
|
+
* Only stages files that were touched by the issue's commits (diff vs baseBranch).
|
|
617
|
+
* If unrelated dirty files exist, emits a warning and skips the checkpoint.
|
|
617
618
|
* @internal Exported for testing
|
|
618
619
|
*/
|
|
619
|
-
export function createCheckpointCommit(worktreePath, issueNumber, verbose) {
|
|
620
|
-
// Check if there are uncommitted changes
|
|
621
|
-
|
|
620
|
+
export function createCheckpointCommit(worktreePath, issueNumber, verbose, baseBranch) {
|
|
621
|
+
// Check if there are uncommitted changes.
|
|
622
|
+
// Use -z (NUL-terminated) so paths with unicode or special chars aren't quoted/escaped.
|
|
623
|
+
const statusResult = spawnSync("git", ["-C", worktreePath, "status", "--porcelain", "-z"], { stdio: "pipe" });
|
|
622
624
|
if (statusResult.status !== 0) {
|
|
623
625
|
if (verbose) {
|
|
624
626
|
console.log(chalk.yellow(` ! Could not check git status for checkpoint`));
|
|
625
627
|
}
|
|
626
628
|
return false;
|
|
627
629
|
}
|
|
628
|
-
const
|
|
629
|
-
if (
|
|
630
|
+
const statusRaw = statusResult.stdout.toString();
|
|
631
|
+
if (statusRaw.length === 0) {
|
|
630
632
|
if (verbose) {
|
|
631
633
|
console.log(chalk.gray(` 📌 No changes to checkpoint (already committed)`));
|
|
632
634
|
}
|
|
633
635
|
return true;
|
|
634
636
|
}
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
637
|
+
// Parse NUL-separated porcelain entries. Each entry is "XY path".
|
|
638
|
+
// For renames/copies, the next entry is the old path and must be consumed.
|
|
639
|
+
const entries = statusRaw.split("\0").filter((e) => e.length > 0);
|
|
640
|
+
const dirtyFiles = [];
|
|
641
|
+
for (let i = 0; i < entries.length; i++) {
|
|
642
|
+
const entry = entries[i];
|
|
643
|
+
const xy = entry.slice(0, 2);
|
|
644
|
+
const path = entry.slice(3);
|
|
645
|
+
if (path)
|
|
646
|
+
dirtyFiles.push(path);
|
|
647
|
+
// Rename (R) and copy (C) entries are followed by the original path — skip it
|
|
648
|
+
if (xy[0] === "R" || xy[0] === "C") {
|
|
649
|
+
i++;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Determine which files to stage.
|
|
653
|
+
// When baseBranch is provided (chain mode), scope to feature paths only.
|
|
654
|
+
// When baseBranch is absent (non-chain), treat all dirty files as in-scope.
|
|
655
|
+
let inScope;
|
|
656
|
+
if (baseBranch) {
|
|
657
|
+
const diffResult = spawnSync("git", ["-C", worktreePath, "diff", "--name-only", "-z", `${baseBranch}...HEAD`], { stdio: "pipe" });
|
|
658
|
+
const featurePaths = new Set();
|
|
659
|
+
if (diffResult.status === 0) {
|
|
660
|
+
diffResult.stdout
|
|
661
|
+
.toString()
|
|
662
|
+
.split("\0")
|
|
663
|
+
.filter((p) => p.length > 0)
|
|
664
|
+
.forEach((p) => featurePaths.add(p));
|
|
665
|
+
}
|
|
666
|
+
inScope = dirtyFiles.filter((f) => featurePaths.has(f));
|
|
667
|
+
const outOfScope = dirtyFiles.filter((f) => !featurePaths.has(f));
|
|
668
|
+
// AC-2: If unrelated dirty files exist, warn and skip checkpoint
|
|
669
|
+
if (outOfScope.length > 0) {
|
|
670
|
+
console.log(chalk.yellow(` ⚠ Skipping checkpoint for #${issueNumber}: ${outOfScope.length} unrelated dirty file(s) in worktree:`));
|
|
671
|
+
for (const f of outOfScope) {
|
|
672
|
+
console.log(chalk.yellow(` - ${f}`));
|
|
673
|
+
}
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// Non-chain mode: all dirty files are in-scope
|
|
679
|
+
inScope = dirtyFiles;
|
|
680
|
+
}
|
|
681
|
+
if (inScope.length === 0) {
|
|
682
|
+
if (verbose) {
|
|
683
|
+
console.log(chalk.gray(` 📌 No in-scope changes to checkpoint`));
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
// AC-1: Stage only in-scope feature paths
|
|
688
|
+
const addResult = spawnSync("git", ["-C", worktreePath, "add", "--", ...inScope], { stdio: "pipe" });
|
|
639
689
|
if (addResult.status !== 0) {
|
|
640
690
|
if (verbose) {
|
|
641
691
|
console.log(chalk.yellow(` ! Could not stage changes for checkpoint`));
|
|
@@ -793,10 +843,14 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
|
|
|
793
843
|
* @param issueTitle Issue title (for PR title)
|
|
794
844
|
* @param branch Branch name
|
|
795
845
|
* @param verbose Whether to show verbose output
|
|
846
|
+
* @param labels Issue labels (used to pick `fix(...)` vs `feat(...)` prefix)
|
|
847
|
+
* @param stackOptions When set under --stacked, `prBase` overrides the default
|
|
848
|
+
* PR target (otherwise gh defaults to the repo's default branch) and
|
|
849
|
+
* `stackManifest` is appended to the PR body. (#605)
|
|
796
850
|
* @returns PRCreationResult with PR info or error
|
|
797
851
|
* @internal Exported for testing
|
|
798
852
|
*/
|
|
799
|
-
export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels) {
|
|
853
|
+
export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels, stackOptions) {
|
|
800
854
|
const github = new GitHubProvider();
|
|
801
855
|
// Step 1: Check for existing PR on this branch
|
|
802
856
|
const existingPRInfo = github.viewPRByBranchSync(branch, worktreePath);
|
|
@@ -832,17 +886,22 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
|
|
|
832
886
|
const isBug = labels?.some((l) => /^bug/i.test(l));
|
|
833
887
|
const prefix = isBug ? "fix" : "feat";
|
|
834
888
|
const prTitle = `${prefix}(#${issueNumber}): ${issueTitle}`;
|
|
835
|
-
const
|
|
889
|
+
const bodyLines = [
|
|
836
890
|
`## Summary`,
|
|
837
891
|
``,
|
|
838
892
|
`Automated PR for issue #${issueNumber}.`,
|
|
839
893
|
``,
|
|
840
894
|
`Fixes #${issueNumber}`,
|
|
841
895
|
``,
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
896
|
+
];
|
|
897
|
+
// #605 AC-4: emit stack manifest before the trailer so reviewers see the
|
|
898
|
+
// chain at the top of the body. Manifest is only set under --stacked.
|
|
899
|
+
if (stackOptions?.stackManifest) {
|
|
900
|
+
bodyLines.push(stackOptions.stackManifest, ``);
|
|
901
|
+
}
|
|
902
|
+
bodyLines.push(`---`, `🤖 Generated by \`sequant run\``);
|
|
903
|
+
const prBody = bodyLines.join("\n");
|
|
904
|
+
const prResult = github.createPRCliSync(prTitle, prBody, branch, worktreePath, stackOptions?.prBase);
|
|
846
905
|
if (prResult.exitCode !== 0) {
|
|
847
906
|
const prError = prResult.stderr.trim() || "Unknown error";
|
|
848
907
|
// Check if PR already exists (race condition or push-before-PR scenarios)
|
|
@@ -50,6 +50,16 @@ interface RunToolResponse {
|
|
|
50
50
|
* spawnAsync(command, [...prefixArgs, "run", ...userArgs])
|
|
51
51
|
*/
|
|
52
52
|
export declare function resolveCliBinary(): [string, string[]];
|
|
53
|
+
/**
|
|
54
|
+
* Find and parse a run log file by its exact runId suffix (#631).
|
|
55
|
+
*
|
|
56
|
+
* Avoids the recency-window heuristic in `readLatestRunLog`, which can
|
|
57
|
+
* return another concurrent run's log or a stale same-issue log when
|
|
58
|
+
* filesystem ordering doesn't favor the current run.
|
|
59
|
+
*
|
|
60
|
+
* Returns null on miss, parse failure, or I/O error (no new failure mode).
|
|
61
|
+
*/
|
|
62
|
+
export declare function readRunLogById(runId: string): Promise<RunLog | null>;
|
|
53
63
|
/**
|
|
54
64
|
* Find and parse the most recent run log file.
|
|
55
65
|
*
|
|
@@ -58,10 +68,26 @@ export declare function resolveCliBinary(): [string, string[]];
|
|
|
58
68
|
* stale logs from a previous run being returned.
|
|
59
69
|
*/
|
|
60
70
|
export declare function readLatestRunLog(runStartTime?: Date): Promise<RunLog | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the run log for an MCP `sequant_run` invocation (#631).
|
|
73
|
+
*
|
|
74
|
+
* Prefers exact-filename lookup by captured runId; falls back to the
|
|
75
|
+
* time-window heuristic when no runId was captured (older CLI, startup
|
|
76
|
+
* race) or when the runId lookup returned null (corrupted file, slow
|
|
77
|
+
* fsync). On lookup-miss with a captured runId, emit a debug line on
|
|
78
|
+
* the MCP server's own stderr so the silent fallback is observable.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolveRunLog(capturedRunId: string | null, runStartTime: Date): Promise<RunLog | null>;
|
|
61
81
|
/**
|
|
62
82
|
* Build a structured response from a parsed RunLog
|
|
63
83
|
*/
|
|
64
84
|
export declare function buildStructuredResponse(runLog: RunLog, rawOutput: string, overallStatus: "success" | "failure", exitCode?: number | null, errorOutput?: string): RunToolResponse;
|
|
85
|
+
/**
|
|
86
|
+
* Parse a SEQUANT_RUN_ID line emitted by the batch executor (#631).
|
|
87
|
+
* Returns the runId UUID or null if the line isn't a runId line or the
|
|
88
|
+
* payload isn't a well-formed UUID.
|
|
89
|
+
*/
|
|
90
|
+
export declare function parseRunIdLine(line: string): string | null;
|
|
65
91
|
/** Parsed progress event from a SEQUANT_PROGRESS line. */
|
|
66
92
|
export interface ProgressEvent {
|
|
67
93
|
issue: number;
|
|
@@ -75,6 +101,24 @@ export interface ProgressEvent {
|
|
|
75
101
|
* Returns the parsed event or null if the line isn't a progress line.
|
|
76
102
|
*/
|
|
77
103
|
export declare function parseProgressLine(line: string): ProgressEvent | null;
|
|
104
|
+
/**
|
|
105
|
+
* Stateful capture of the per-run UUID emitted on stderr by the spawned
|
|
106
|
+
* CLI (#631). Each MCP request creates its own capture instance.
|
|
107
|
+
*
|
|
108
|
+
* `routeLine` consumes a complete stderr line:
|
|
109
|
+
* - Until a `SEQUANT_RUN_ID:` line is seen, attempts to capture it.
|
|
110
|
+
* - After capture (or for non-runId lines), delegates to `parseProgressLine`
|
|
111
|
+
* and returns the parsed `ProgressEvent`, if any.
|
|
112
|
+
*
|
|
113
|
+
* Returning `ProgressEvent | null` (instead of side-effecting) keeps the
|
|
114
|
+
* factory pure and lets callers wire emission (`emitProgress`) at the
|
|
115
|
+
* outer layer. This separation also makes the capture logic directly
|
|
116
|
+
* testable without driving the full MCP request handler.
|
|
117
|
+
*/
|
|
118
|
+
export declare function createRunIdCapture(): {
|
|
119
|
+
routeLine: (line: string) => ProgressEvent | null;
|
|
120
|
+
getCapturedRunId: () => string | null;
|
|
121
|
+
};
|
|
78
122
|
/**
|
|
79
123
|
* Build a human-readable message for a progress notification (AC-3).
|
|
80
124
|
* @internal Exported for testing only.
|