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
|
@@ -15,6 +15,7 @@ import { LogWriter } from "./log-writer.js";
|
|
|
15
15
|
import { StateManager } from "./state-manager.js";
|
|
16
16
|
import { ShutdownManager } from "../shutdown.js";
|
|
17
17
|
import { LockManager, formatLockedMessage } from "../locks/index.js";
|
|
18
|
+
import { bracketedConsoleLog } from "./notice.js";
|
|
18
19
|
/** Human-readable line for the run-orchestrator's `--signal-other` log (#637). */
|
|
19
20
|
function formatSignalLine(issue, pid, result) {
|
|
20
21
|
switch (result.reason) {
|
|
@@ -36,6 +37,7 @@ import { getIssueInfo, sortByDependencies, parseBatches, runIssueWithLogging, em
|
|
|
36
37
|
import { reconcileStateAtStartup } from "./state-utils.js";
|
|
37
38
|
import { getCommitHash } from "./git-diff-utils.js";
|
|
38
39
|
import { MetricsWriter } from "./metrics-writer.js";
|
|
40
|
+
import { WorkflowEventEmitter } from "./event-emitter.js";
|
|
39
41
|
import { determineOutcome } from "./metrics-schema.js";
|
|
40
42
|
import { getTokenUsageForRun } from "./token-utils.js";
|
|
41
43
|
import { resolveRunOptions, buildExecutionConfig } from "./config-resolver.js";
|
|
@@ -65,12 +67,31 @@ export class RunOrchestrator {
|
|
|
65
67
|
cfg;
|
|
66
68
|
issueStates = new Map();
|
|
67
69
|
phaseStartTimes = new Map();
|
|
70
|
+
emitter;
|
|
68
71
|
done = false;
|
|
69
72
|
constructor(config) {
|
|
70
73
|
this.validate(config);
|
|
74
|
+
// Build the event emitter before wrapProgress so the wrapper can route
|
|
75
|
+
// status transitions through `issue_status_changed` events (AC-3).
|
|
76
|
+
this.emitter = new WorkflowEventEmitter({
|
|
77
|
+
onListenerError: (event, error) => {
|
|
78
|
+
// Mirror the orchestrator's verbose-gated non-fatal warning style.
|
|
79
|
+
// Listener failures must never propagate to the run.
|
|
80
|
+
logNonFatalWarning(` ! Event listener for "${event}" threw, ignoring`, error, config.config?.verbose ?? false);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
71
83
|
this.cfg = { ...config, onProgress: this.wrapProgress(config.onProgress) };
|
|
72
84
|
this.initIssueStates();
|
|
73
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns the workflow event emitter. External consumers (TUI, MCP server,
|
|
88
|
+
* future webhooks) call `getEmitter().on(...)` to subscribe to lifecycle
|
|
89
|
+
* events. Subscribing is opt-in — the orchestrator runs unaware of who is
|
|
90
|
+
* listening (#504, AC-3).
|
|
91
|
+
*/
|
|
92
|
+
getEmitter() {
|
|
93
|
+
return this.emitter;
|
|
94
|
+
}
|
|
74
95
|
/**
|
|
75
96
|
* Point-in-time view of the entire run.
|
|
76
97
|
*
|
|
@@ -97,9 +118,14 @@ export class RunOrchestrator {
|
|
|
97
118
|
capturedAt: new Date(),
|
|
98
119
|
};
|
|
99
120
|
}
|
|
100
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Mark the run as completed so the dashboard can unmount and drop event
|
|
123
|
+
* subscribers. Drains the emitter to prevent leaks across multiple
|
|
124
|
+
* `run()` invocations in the same process (e.g. the MCP server).
|
|
125
|
+
*/
|
|
101
126
|
markDone() {
|
|
102
127
|
this.done = true;
|
|
128
|
+
this.emitter.removeAllListeners();
|
|
103
129
|
}
|
|
104
130
|
initIssueStates() {
|
|
105
131
|
const { issueInfoMap, worktreeMap, config } = this.cfg;
|
|
@@ -129,6 +155,7 @@ export class RunOrchestrator {
|
|
|
129
155
|
if (!state)
|
|
130
156
|
return;
|
|
131
157
|
if (event === "start") {
|
|
158
|
+
const wasStatus = state.status;
|
|
132
159
|
if (!state.startedAt)
|
|
133
160
|
state.startedAt = new Date();
|
|
134
161
|
state.status = "running";
|
|
@@ -143,6 +170,19 @@ export class RunOrchestrator {
|
|
|
143
170
|
const p = findOrAppendPhase(state, phase);
|
|
144
171
|
p.status = "running";
|
|
145
172
|
p.startedAt = now;
|
|
173
|
+
// Fire-and-forget — listener safety guaranteed by the emitter (AC-5).
|
|
174
|
+
void this.emitter.emit("phase_started", {
|
|
175
|
+
issueNumber: issue,
|
|
176
|
+
phase,
|
|
177
|
+
iteration: extra?.iteration,
|
|
178
|
+
});
|
|
179
|
+
if (wasStatus !== "running") {
|
|
180
|
+
void this.emitter.emit("issue_status_changed", {
|
|
181
|
+
issueNumber: issue,
|
|
182
|
+
from: wasStatus,
|
|
183
|
+
to: "running",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
146
186
|
return;
|
|
147
187
|
}
|
|
148
188
|
if (event === "activity") {
|
|
@@ -155,6 +195,11 @@ export class RunOrchestrator {
|
|
|
155
195
|
return;
|
|
156
196
|
state.currentPhase.nowLine = line;
|
|
157
197
|
state.currentPhase.lastActivityAt = new Date();
|
|
198
|
+
void this.emitter.emit("progress", {
|
|
199
|
+
issueNumber: issue,
|
|
200
|
+
phase,
|
|
201
|
+
text: line,
|
|
202
|
+
});
|
|
158
203
|
return;
|
|
159
204
|
}
|
|
160
205
|
// complete / failed
|
|
@@ -170,18 +215,48 @@ export class RunOrchestrator {
|
|
|
170
215
|
p.status = event === "complete" ? "done" : "failed";
|
|
171
216
|
p.elapsedMs = elapsedMs;
|
|
172
217
|
state.currentPhase = undefined;
|
|
218
|
+
const durationSec = elapsedMs !== undefined ? Math.round(elapsedMs / 1000) : undefined;
|
|
173
219
|
if (event === "failed") {
|
|
220
|
+
const prev = state.status;
|
|
174
221
|
state.status = "failed";
|
|
175
222
|
state.completedAt = new Date();
|
|
223
|
+
void this.emitter.emit("phase_failed", {
|
|
224
|
+
issueNumber: issue,
|
|
225
|
+
phase,
|
|
226
|
+
duration: durationSec,
|
|
227
|
+
error: extra?.error ?? "unknown",
|
|
228
|
+
iteration: extra?.iteration,
|
|
229
|
+
});
|
|
230
|
+
if (prev !== "failed") {
|
|
231
|
+
void this.emitter.emit("issue_status_changed", {
|
|
232
|
+
issueNumber: issue,
|
|
233
|
+
from: prev,
|
|
234
|
+
to: "failed",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
176
237
|
return;
|
|
177
238
|
}
|
|
239
|
+
void this.emitter.emit("phase_completed", {
|
|
240
|
+
issueNumber: issue,
|
|
241
|
+
phase,
|
|
242
|
+
duration: durationSec ?? 0,
|
|
243
|
+
iteration: extra?.iteration,
|
|
244
|
+
});
|
|
178
245
|
// Completed phase: if it's the last phase in the plan, mark issue passed.
|
|
179
246
|
const allDone = state.phases.every((ph) => ph.status === "done" || ph.status === "failed");
|
|
180
247
|
if (allDone) {
|
|
248
|
+
const prev = state.status;
|
|
181
249
|
state.status = state.phases.some((ph) => ph.status === "failed")
|
|
182
250
|
? "failed"
|
|
183
251
|
: "passed";
|
|
184
252
|
state.completedAt = new Date();
|
|
253
|
+
if (prev !== state.status) {
|
|
254
|
+
void this.emitter.emit("issue_status_changed", {
|
|
255
|
+
issueNumber: issue,
|
|
256
|
+
from: prev,
|
|
257
|
+
to: state.status,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
185
260
|
}
|
|
186
261
|
}
|
|
187
262
|
/**
|
|
@@ -243,7 +318,7 @@ export class RunOrchestrator {
|
|
|
243
318
|
* issue discovery → worktree creation → execution → metrics → cleanup.
|
|
244
319
|
*/
|
|
245
320
|
static async run(init, issueArgs, batches) {
|
|
246
|
-
const { manifest, onProgress, settings } = init;
|
|
321
|
+
const { manifest, onProgress, phasePauseHandle, settings } = init;
|
|
247
322
|
// ── Config resolution ──────────────────────────────────────────────
|
|
248
323
|
const resolved = RunOrchestrator.resolveConfig(init, issueArgs, batches);
|
|
249
324
|
const { mergedOptions, config, baseBranch } = resolved;
|
|
@@ -450,18 +525,24 @@ export class RunOrchestrator {
|
|
|
450
525
|
packageManager: manifest.packageManager,
|
|
451
526
|
baseBranch,
|
|
452
527
|
onProgress,
|
|
528
|
+
onPhasePlan: init.onPhasePlan,
|
|
529
|
+
phasePauseHandle,
|
|
453
530
|
});
|
|
454
531
|
init.onOrchestratorReady?.(orchestrator);
|
|
455
532
|
try {
|
|
456
533
|
if (resolvedBatches) {
|
|
457
534
|
for (let batchIdx = 0; batchIdx < resolvedBatches.length; batchIdx++) {
|
|
458
535
|
const batch = resolvedBatches[batchIdx];
|
|
459
|
-
|
|
536
|
+
// #647 AC-3: between-batches in a multi-batch run, the renderer is
|
|
537
|
+
// still alive and may have a populated live zone from the previous
|
|
538
|
+
// batch. Route through `bracketedConsoleLog` so log-update's cursor
|
|
539
|
+
// model stays consistent.
|
|
540
|
+
bracketedConsoleLog(phasePauseHandle, chalk.blue(`\n Batch ${batchIdx + 1}/${resolvedBatches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
|
|
460
541
|
const batchResults = await orchestrator.execute(batch);
|
|
461
542
|
results.push(...batchResults);
|
|
462
543
|
const batchFailed = batchResults.some((r) => !r.success);
|
|
463
544
|
if (batchFailed && config.sequential) {
|
|
464
|
-
|
|
545
|
+
bracketedConsoleLog(phasePauseHandle, chalk.yellow(`\n ! Batch ${batchIdx + 1} failed, stopping batch execution`));
|
|
465
546
|
break;
|
|
466
547
|
}
|
|
467
548
|
}
|
|
@@ -546,6 +627,8 @@ export class RunOrchestrator {
|
|
|
546
627
|
packageManager: this.cfg.packageManager,
|
|
547
628
|
baseBranch: this.cfg.baseBranch,
|
|
548
629
|
onProgress: this.cfg.onProgress,
|
|
630
|
+
onPhasePlan: this.cfg.onPhasePlan,
|
|
631
|
+
phasePauseHandle: this.cfg.phasePauseHandle,
|
|
549
632
|
};
|
|
550
633
|
}
|
|
551
634
|
async executeSequential(issueNumbers, batchCtx, options) {
|
|
@@ -632,7 +715,7 @@ export class RunOrchestrator {
|
|
|
632
715
|
}
|
|
633
716
|
async executeOneIssue(args) {
|
|
634
717
|
const { issueNumber, batchCtx, chain, parallelIssueNumber } = args;
|
|
635
|
-
const { config, options, issueInfoMap, worktreeMap, logWriter, stateManager, shutdownManager, packageManager, baseBranch, onProgress, } = batchCtx;
|
|
718
|
+
const { config, options, issueInfoMap, worktreeMap, logWriter, stateManager, shutdownManager, packageManager, baseBranch, onProgress, onPhasePlan, phasePauseHandle, } = batchCtx;
|
|
636
719
|
const issueInfo = issueInfoMap.get(issueNumber) ?? {
|
|
637
720
|
title: `Issue #${issueNumber}`,
|
|
638
721
|
labels: [],
|
|
@@ -655,15 +738,46 @@ export class RunOrchestrator {
|
|
|
655
738
|
packageManager,
|
|
656
739
|
baseBranch,
|
|
657
740
|
onProgress,
|
|
741
|
+
onPhasePlan,
|
|
742
|
+
phasePauseHandle,
|
|
658
743
|
};
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
744
|
+
// Fire-and-forget — orchestrator does not await listener completion on
|
|
745
|
+
// the lifecycle bracket events. Listener safety is the emitter's job (AC-5).
|
|
746
|
+
void this.emitter.emit("run_started", { issueNumber });
|
|
747
|
+
const issueStartedAt = Date.now();
|
|
748
|
+
// `run_completed` is emitted in the finally so the bracket stays
|
|
749
|
+
// symmetric with `run_started` even if `runIssueWithLogging` throws —
|
|
750
|
+
// subscribers (MCP, dashboard) can rely on every started run ending.
|
|
751
|
+
let result;
|
|
752
|
+
try {
|
|
753
|
+
result = await runIssueWithLogging(ctx);
|
|
754
|
+
// Surface QA verdicts as a dedicated event so consumers don't have to
|
|
755
|
+
// re-parse phase output. Emits at most once per QA phase result.
|
|
756
|
+
for (const pr of result.phaseResults) {
|
|
757
|
+
if (pr.phase === "qa" && pr.verdict) {
|
|
758
|
+
void this.emitter.emit("qa_verdict", {
|
|
759
|
+
issueNumber,
|
|
760
|
+
phase: "qa",
|
|
761
|
+
verdict: pr.verdict,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (logWriter && result.prNumber && result.prUrl) {
|
|
766
|
+
logWriter.setPRInfo(result.prNumber, result.prUrl, parallelIssueNumber);
|
|
767
|
+
}
|
|
768
|
+
if (logWriter) {
|
|
769
|
+
logWriter.completeIssue(parallelIssueNumber);
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
662
772
|
}
|
|
663
|
-
|
|
664
|
-
|
|
773
|
+
finally {
|
|
774
|
+
const durationSec = Math.round((Date.now() - issueStartedAt) / 1000);
|
|
775
|
+
void this.emitter.emit("run_completed", {
|
|
776
|
+
issueNumber,
|
|
777
|
+
duration: durationSec,
|
|
778
|
+
success: result?.success ?? false,
|
|
779
|
+
});
|
|
665
780
|
}
|
|
666
|
-
return result;
|
|
667
781
|
}
|
|
668
782
|
static async recordMetrics(config, mergedOptions, results, worktreeMap, issueNumbers) {
|
|
669
783
|
const metricsWriter = new MetricsWriter({ verbose: config.verbose });
|
|
@@ -130,9 +130,27 @@ export declare class StateManager {
|
|
|
130
130
|
*/
|
|
131
131
|
updateWorktreeInfo(issueNumber: number, worktree: string, branch: string): Promise<void>;
|
|
132
132
|
/**
|
|
133
|
-
* Update session ID for an issue (for resume)
|
|
133
|
+
* Update session ID for an issue (for resume).
|
|
134
|
+
*
|
|
135
|
+
* @deprecated Use {@link updateResumeHandle} (#674). This entry point only
|
|
136
|
+
* writes the legacy `sessionId` field — without an `originCwd`, the next
|
|
137
|
+
* phase's driver-owned `canResume()` fail-safe will decline resume, so the
|
|
138
|
+
* data is effectively inert. Retained for one release to keep callers from
|
|
139
|
+
* older sequant builds compiling.
|
|
134
140
|
*/
|
|
135
141
|
updateSessionId(issueNumber: number, sessionId: string): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Update the driver-tagged resume handle for an issue (#674).
|
|
144
|
+
*
|
|
145
|
+
* Writes both the new `resumeHandle` field and mirrors the token into
|
|
146
|
+
* the deprecated `sessionId` field so state files round-trip through
|
|
147
|
+
* older sequant readers for one release.
|
|
148
|
+
*/
|
|
149
|
+
updateResumeHandle(issueNumber: number, handle: {
|
|
150
|
+
driver: string;
|
|
151
|
+
token: string;
|
|
152
|
+
originCwd: string;
|
|
153
|
+
}): Promise<void>;
|
|
136
154
|
/**
|
|
137
155
|
* Set or clear the relay state for an issue (#383).
|
|
138
156
|
*
|
|
@@ -378,7 +378,13 @@ export class StateManager {
|
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
/**
|
|
381
|
-
* Update session ID for an issue (for resume)
|
|
381
|
+
* Update session ID for an issue (for resume).
|
|
382
|
+
*
|
|
383
|
+
* @deprecated Use {@link updateResumeHandle} (#674). This entry point only
|
|
384
|
+
* writes the legacy `sessionId` field — without an `originCwd`, the next
|
|
385
|
+
* phase's driver-owned `canResume()` fail-safe will decline resume, so the
|
|
386
|
+
* data is effectively inert. Retained for one release to keep callers from
|
|
387
|
+
* older sequant builds compiling.
|
|
382
388
|
*/
|
|
383
389
|
async updateSessionId(issueNumber, sessionId) {
|
|
384
390
|
await this.withLock(async () => {
|
|
@@ -392,6 +398,26 @@ export class StateManager {
|
|
|
392
398
|
await this.saveState(state);
|
|
393
399
|
});
|
|
394
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Update the driver-tagged resume handle for an issue (#674).
|
|
403
|
+
*
|
|
404
|
+
* Writes both the new `resumeHandle` field and mirrors the token into
|
|
405
|
+
* the deprecated `sessionId` field so state files round-trip through
|
|
406
|
+
* older sequant readers for one release.
|
|
407
|
+
*/
|
|
408
|
+
async updateResumeHandle(issueNumber, handle) {
|
|
409
|
+
await this.withLock(async () => {
|
|
410
|
+
const state = await this.getState();
|
|
411
|
+
const issueState = state.issues[String(issueNumber)];
|
|
412
|
+
if (!issueState) {
|
|
413
|
+
throw new Error(`Issue #${issueNumber} not found in state`);
|
|
414
|
+
}
|
|
415
|
+
issueState.resumeHandle = handle;
|
|
416
|
+
issueState.sessionId = handle.token;
|
|
417
|
+
issueState.lastActivity = new Date().toISOString();
|
|
418
|
+
await this.saveState(state);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
395
421
|
/**
|
|
396
422
|
* Set or clear the relay state for an issue (#383).
|
|
397
423
|
*
|
|
@@ -19,9 +19,14 @@
|
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { PhaseSchema } from "./types.js";
|
|
21
21
|
/**
|
|
22
|
-
* Workflow phases in order of execution
|
|
22
|
+
* Workflow phases in order of execution.
|
|
23
|
+
*
|
|
24
|
+
* Sourced from the phase registry — replaces the previous `PhaseSchema.options`
|
|
25
|
+
* literal that existed when `PhaseSchema` was a `z.enum`. Computed at module
|
|
26
|
+
* load (after `built-in-phases.ts` has run); insertion order is the canonical
|
|
27
|
+
* pipeline order.
|
|
23
28
|
*/
|
|
24
|
-
export declare const WORKFLOW_PHASES:
|
|
29
|
+
export declare const WORKFLOW_PHASES: readonly string[];
|
|
25
30
|
/**
|
|
26
31
|
* Phase status - tracks individual phase progress
|
|
27
32
|
*/
|
|
@@ -41,6 +46,7 @@ export declare const IssueStatusSchema: z.ZodEnum<{
|
|
|
41
46
|
in_progress: "in_progress";
|
|
42
47
|
not_started: "not_started";
|
|
43
48
|
waiting_for_qa_gate: "waiting_for_qa_gate";
|
|
49
|
+
waiting_for_human_merge: "waiting_for_human_merge";
|
|
44
50
|
ready_for_merge: "ready_for_merge";
|
|
45
51
|
blocked: "blocked";
|
|
46
52
|
abandoned: "abandoned";
|
|
@@ -54,17 +60,7 @@ export type { Phase } from "./types.js";
|
|
|
54
60
|
* Embedded as HTML comments: `<!-- SEQUANT_PHASE: {...} -->`
|
|
55
61
|
*/
|
|
56
62
|
export declare const PhaseMarkerSchema: z.ZodObject<{
|
|
57
|
-
phase: z.
|
|
58
|
-
qa: "qa";
|
|
59
|
-
loop: "loop";
|
|
60
|
-
verify: "verify";
|
|
61
|
-
spec: "spec";
|
|
62
|
-
"security-review": "security-review";
|
|
63
|
-
exec: "exec";
|
|
64
|
-
testgen: "testgen";
|
|
65
|
-
test: "test";
|
|
66
|
-
merger: "merger";
|
|
67
|
-
}>;
|
|
63
|
+
phase: z.ZodString;
|
|
68
64
|
status: z.ZodEnum<{
|
|
69
65
|
pending: "pending";
|
|
70
66
|
skipped: "skipped";
|
|
@@ -227,23 +223,14 @@ export declare const IssueStateSchema: z.ZodObject<{
|
|
|
227
223
|
in_progress: "in_progress";
|
|
228
224
|
not_started: "not_started";
|
|
229
225
|
waiting_for_qa_gate: "waiting_for_qa_gate";
|
|
226
|
+
waiting_for_human_merge: "waiting_for_human_merge";
|
|
230
227
|
ready_for_merge: "ready_for_merge";
|
|
231
228
|
blocked: "blocked";
|
|
232
229
|
abandoned: "abandoned";
|
|
233
230
|
}>;
|
|
234
231
|
worktree: z.ZodOptional<z.ZodString>;
|
|
235
232
|
branch: z.ZodOptional<z.ZodString>;
|
|
236
|
-
currentPhase: z.ZodOptional<z.
|
|
237
|
-
qa: "qa";
|
|
238
|
-
loop: "loop";
|
|
239
|
-
verify: "verify";
|
|
240
|
-
spec: "spec";
|
|
241
|
-
"security-review": "security-review";
|
|
242
|
-
exec: "exec";
|
|
243
|
-
testgen: "testgen";
|
|
244
|
-
test: "test";
|
|
245
|
-
merger: "merger";
|
|
246
|
-
}>>;
|
|
233
|
+
currentPhase: z.ZodOptional<z.ZodString>;
|
|
247
234
|
phases: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
248
235
|
status: z.ZodEnum<{
|
|
249
236
|
pending: "pending";
|
|
@@ -348,6 +335,11 @@ export declare const IssueStateSchema: z.ZodObject<{
|
|
|
348
335
|
messageCount: z.ZodNumber;
|
|
349
336
|
}, z.core.$strip>>;
|
|
350
337
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
338
|
+
resumeHandle: z.ZodOptional<z.ZodObject<{
|
|
339
|
+
driver: z.ZodString;
|
|
340
|
+
token: z.ZodString;
|
|
341
|
+
originCwd: z.ZodString;
|
|
342
|
+
}, z.core.$strip>>;
|
|
351
343
|
resolvedAt: z.ZodOptional<z.ZodString>;
|
|
352
344
|
lastActivity: z.ZodString;
|
|
353
345
|
createdAt: z.ZodString;
|
|
@@ -370,23 +362,14 @@ export declare const WorkflowStateSchema: z.ZodObject<{
|
|
|
370
362
|
in_progress: "in_progress";
|
|
371
363
|
not_started: "not_started";
|
|
372
364
|
waiting_for_qa_gate: "waiting_for_qa_gate";
|
|
365
|
+
waiting_for_human_merge: "waiting_for_human_merge";
|
|
373
366
|
ready_for_merge: "ready_for_merge";
|
|
374
367
|
blocked: "blocked";
|
|
375
368
|
abandoned: "abandoned";
|
|
376
369
|
}>;
|
|
377
370
|
worktree: z.ZodOptional<z.ZodString>;
|
|
378
371
|
branch: z.ZodOptional<z.ZodString>;
|
|
379
|
-
currentPhase: z.ZodOptional<z.
|
|
380
|
-
qa: "qa";
|
|
381
|
-
loop: "loop";
|
|
382
|
-
verify: "verify";
|
|
383
|
-
spec: "spec";
|
|
384
|
-
"security-review": "security-review";
|
|
385
|
-
exec: "exec";
|
|
386
|
-
testgen: "testgen";
|
|
387
|
-
test: "test";
|
|
388
|
-
merger: "merger";
|
|
389
|
-
}>>;
|
|
372
|
+
currentPhase: z.ZodOptional<z.ZodString>;
|
|
390
373
|
phases: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
391
374
|
status: z.ZodEnum<{
|
|
392
375
|
pending: "pending";
|
|
@@ -491,6 +474,11 @@ export declare const WorkflowStateSchema: z.ZodObject<{
|
|
|
491
474
|
messageCount: z.ZodNumber;
|
|
492
475
|
}, z.core.$strip>>;
|
|
493
476
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
477
|
+
resumeHandle: z.ZodOptional<z.ZodObject<{
|
|
478
|
+
driver: z.ZodString;
|
|
479
|
+
token: z.ZodString;
|
|
480
|
+
originCwd: z.ZodString;
|
|
481
|
+
}, z.core.$strip>>;
|
|
494
482
|
resolvedAt: z.ZodOptional<z.ZodString>;
|
|
495
483
|
lastActivity: z.ZodString;
|
|
496
484
|
createdAt: z.ZodString;
|
|
@@ -19,10 +19,16 @@
|
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { ScopeAssessmentSchema } from "../scope/types.js";
|
|
21
21
|
import { PhaseSchema } from "./types.js";
|
|
22
|
+
import { getPhaseNames } from "./phase-registry.js";
|
|
22
23
|
/**
|
|
23
|
-
* Workflow phases in order of execution
|
|
24
|
+
* Workflow phases in order of execution.
|
|
25
|
+
*
|
|
26
|
+
* Sourced from the phase registry — replaces the previous `PhaseSchema.options`
|
|
27
|
+
* literal that existed when `PhaseSchema` was a `z.enum`. Computed at module
|
|
28
|
+
* load (after `built-in-phases.ts` has run); insertion order is the canonical
|
|
29
|
+
* pipeline order.
|
|
24
30
|
*/
|
|
25
|
-
export const WORKFLOW_PHASES =
|
|
31
|
+
export const WORKFLOW_PHASES = getPhaseNames();
|
|
26
32
|
/**
|
|
27
33
|
* Phase status - tracks individual phase progress
|
|
28
34
|
*/
|
|
@@ -40,6 +46,7 @@ export const IssueStatusSchema = z.enum([
|
|
|
40
46
|
"not_started", // Issue tracked but no work begun
|
|
41
47
|
"in_progress", // Actively being worked on
|
|
42
48
|
"waiting_for_qa_gate", // QA completed, waiting for gate approval in chain mode
|
|
49
|
+
"waiting_for_human_merge", // `sequant ready` (#683) finished its A+ gate; awaiting human merge decision (never auto-merges)
|
|
43
50
|
"ready_for_merge", // All phases passed, PR ready for review
|
|
44
51
|
"merged", // PR merged, work complete
|
|
45
52
|
"blocked", // Waiting on external input or dependency
|
|
@@ -213,8 +220,27 @@ export const IssueStateSchema = z.object({
|
|
|
213
220
|
qaStagnation: z.array(QAStagnationEntrySchema).optional(),
|
|
214
221
|
/** Relay state (#383); present when bidirectional relay is active */
|
|
215
222
|
relay: RelayStateSchema.optional(),
|
|
216
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* Claude session ID (for resume).
|
|
225
|
+
* @deprecated Use {@link resumeHandle} (#674). Retained as a read-path
|
|
226
|
+
* mirror of `resumeHandle.token` for one release so state files written by
|
|
227
|
+
* prior sequant builds load cleanly. Legacy entries (token without
|
|
228
|
+
* `originCwd`) intentionally do NOT resume — the driver-owned `canResume`
|
|
229
|
+
* fail-safe disables them.
|
|
230
|
+
*/
|
|
217
231
|
sessionId: z.string().optional(),
|
|
232
|
+
/**
|
|
233
|
+
* Driver-tagged resume handle (#674). Stores the driver name, the
|
|
234
|
+
* resume token, and the cwd the session was created in so the next phase
|
|
235
|
+
* can prove cwd-equality before reattaching.
|
|
236
|
+
*/
|
|
237
|
+
resumeHandle: z
|
|
238
|
+
.object({
|
|
239
|
+
driver: z.string(),
|
|
240
|
+
token: z.string(),
|
|
241
|
+
originCwd: z.string(),
|
|
242
|
+
})
|
|
243
|
+
.optional(),
|
|
218
244
|
/** When the issue transitioned to a terminal status (merged/abandoned/closed) */
|
|
219
245
|
resolvedAt: z.string().datetime().optional(),
|
|
220
246
|
/** Most recent activity timestamp */
|
|
@@ -7,27 +7,47 @@ import type { LogWriter } from "./log-writer.js";
|
|
|
7
7
|
import type { StateManager } from "./state-manager.js";
|
|
8
8
|
import type { ShutdownManager } from "../shutdown.js";
|
|
9
9
|
import type { WorktreeInfo } from "./worktree-manager.js";
|
|
10
|
+
export type { WorkflowEventEmitter, WorkflowEvents, WorkflowEventListener, IssueEventStatus, BaseEventPayload, RunEventPayload, PhaseStartedPayload, PhaseCompletedPayload, PhaseFailedPayload, IssueStatusChangedPayload, QaVerdictPayload, ProgressPayload, } from "./event-emitter.js";
|
|
10
11
|
/**
|
|
11
12
|
* Canonical Zod schema for all workflow phases.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Backed by the phase registry. `PhaseSchema.parse(name)` succeeds iff
|
|
15
|
+
* `phaseRegistry.has(name)`. The set of valid phases is the registry's
|
|
16
|
+
* keys at the time of parsing — registration happens at module load,
|
|
17
|
+
* so for normal runtime use the set is fixed by the time any code parses.
|
|
18
|
+
*
|
|
19
|
+
* This replaces the prior `z.enum([...])` literal. The set of valid names
|
|
20
|
+
* is identical for the 9 built-in phases; the only observable behavior
|
|
21
|
+
* change is that `PhaseSchema.options` is no longer available — use
|
|
22
|
+
* `getPhaseNames()` from `phase-registry.ts` instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare const PhaseSchema: z.ZodString;
|
|
25
|
+
/**
|
|
26
|
+
* Available workflow phases. Widened from a string-literal union to `string`
|
|
27
|
+
* after the registry migration — exhaustiveness checking on `switch (phase)`
|
|
28
|
+
* is now a runtime concern (see the comment in phase-executor.ts where the
|
|
29
|
+
* only relevant switch lives).
|
|
15
30
|
*/
|
|
16
|
-
export
|
|
17
|
-
qa: "qa";
|
|
18
|
-
loop: "loop";
|
|
19
|
-
verify: "verify";
|
|
20
|
-
spec: "spec";
|
|
21
|
-
"security-review": "security-review";
|
|
22
|
-
exec: "exec";
|
|
23
|
-
testgen: "testgen";
|
|
24
|
-
test: "test";
|
|
25
|
-
merger: "merger";
|
|
26
|
-
}>;
|
|
31
|
+
export type Phase = string;
|
|
27
32
|
/**
|
|
28
|
-
*
|
|
33
|
+
* Lifecycle hook for pausing the run renderer's live zone while verbose
|
|
34
|
+
* Claude streaming writes through stdout, then resuming after the agent
|
|
35
|
+
* call completes. Replaces the legacy `PhaseSpinner` argument (#618).
|
|
36
|
+
*
|
|
37
|
+
* Lives in the workflow types barrel so the cli-ui layer can implement it
|
|
38
|
+
* without the workflow layer reaching back into cli-ui (#656).
|
|
29
39
|
*/
|
|
30
|
-
export
|
|
40
|
+
export interface PhasePauseHandle {
|
|
41
|
+
pause(): void;
|
|
42
|
+
resume(): void;
|
|
43
|
+
/**
|
|
44
|
+
* #647 AC-3: print a notice line (e.g., retry/fallback message) without
|
|
45
|
+
* breaking log-update's cursor model. Implementations clear the live zone,
|
|
46
|
+
* write the line through the renderer's own stdout channel, then redraw.
|
|
47
|
+
* In quiet / non-TTY paths this degrades to a plain write.
|
|
48
|
+
*/
|
|
49
|
+
appendNotice(message: string): void;
|
|
50
|
+
}
|
|
31
51
|
/**
|
|
32
52
|
* Default phases for workflow execution
|
|
33
53
|
*/
|
|
@@ -116,6 +136,15 @@ export interface ExecutionConfig {
|
|
|
116
136
|
* Default: false (opt-in for the initial rollout).
|
|
117
137
|
*/
|
|
118
138
|
relayEnabled?: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Force full-weight (standalone) QA even under an orchestrator (#683).
|
|
141
|
+
* When true, the phase executor sets `SEQUANT_FULL_QA=1` in the agent
|
|
142
|
+
* environment for the `qa` phase. The QA skill honors this flag by running
|
|
143
|
+
* its standalone branch-freshness / process-state pre-flight checks even
|
|
144
|
+
* though `SEQUANT_ORCHESTRATOR` is also set. Used by `sequant ready` so its
|
|
145
|
+
* QA pass does NOT skip the checks that catch the #318/#529/#570 class.
|
|
146
|
+
*/
|
|
147
|
+
fullQa?: boolean;
|
|
119
148
|
}
|
|
120
149
|
/**
|
|
121
150
|
* Default execution configuration
|
|
@@ -338,6 +367,17 @@ export type ProgressCallback = (issue: number, phase: string, event: "start" | "
|
|
|
338
367
|
iteration?: number;
|
|
339
368
|
text?: string;
|
|
340
369
|
}) => void;
|
|
370
|
+
/**
|
|
371
|
+
* #672 AC-2: fired once per issue after the executor has resolved the final
|
|
372
|
+
* phase pipeline (post auto-detect, post resume filter, post testgen /
|
|
373
|
+
* security-review insertion). Lets the run renderer seed pending cells for
|
|
374
|
+
* the full roadmap before any phase fires, so users see what is about to run
|
|
375
|
+
* instead of phases appearing one at a time as they stream.
|
|
376
|
+
*
|
|
377
|
+
* Empty `phases` means "no plan known" — the renderer should fall back to
|
|
378
|
+
* streaming-only display.
|
|
379
|
+
*/
|
|
380
|
+
export type PhasePlanCallback = (issue: number, phases: string[]) => void;
|
|
341
381
|
/**
|
|
342
382
|
* Shared context for executing a batch of issues.
|
|
343
383
|
* Replaces 11 positional parameters in executeBatch (#402).
|
|
@@ -356,6 +396,17 @@ export interface BatchExecutionContext {
|
|
|
356
396
|
packageManager?: string;
|
|
357
397
|
baseBranch?: string;
|
|
358
398
|
onProgress?: ProgressCallback;
|
|
399
|
+
/** #672 AC-2: forwarded to per-issue context so batch-executor can fire it
|
|
400
|
+
* once the final phase pipeline is known. */
|
|
401
|
+
onPhasePlan?: PhasePlanCallback;
|
|
402
|
+
/**
|
|
403
|
+
* Optional live-zone pause handle (#656). When set, the phase executor calls
|
|
404
|
+
* `pause()` before forwarding verbose Claude SDK output to stdout and
|
|
405
|
+
* `resume()` after the agent call completes — so the 1Hz live grid does not
|
|
406
|
+
* collide with streaming text. Wired from the active `RunRenderer` at the
|
|
407
|
+
* composition root in `run.ts`; left undefined for quiet/TUI modes.
|
|
408
|
+
*/
|
|
409
|
+
phasePauseHandle?: PhasePauseHandle;
|
|
359
410
|
}
|
|
360
411
|
/**
|
|
361
412
|
* Context object for executing a single issue through the workflow.
|
|
@@ -405,4 +456,12 @@ export interface IssueExecutionContext {
|
|
|
405
456
|
baseBranch?: string;
|
|
406
457
|
/** Per-phase progress callback (used in parallel mode) */
|
|
407
458
|
onProgress?: ProgressCallback;
|
|
459
|
+
/** #672 AC-2: invoked once after the per-issue phase plan resolves. */
|
|
460
|
+
onPhasePlan?: PhasePlanCallback;
|
|
461
|
+
/**
|
|
462
|
+
* Optional live-zone pause handle (#656). Forwarded to
|
|
463
|
+
* `executePhaseWithRetry` so the renderer's `pause`/`resume` hooks fire
|
|
464
|
+
* around verbose Claude streaming.
|
|
465
|
+
*/
|
|
466
|
+
phasePauseHandle?: PhasePauseHandle;
|
|
408
467
|
}
|
|
@@ -2,23 +2,28 @@
|
|
|
2
2
|
* Core types for workflow execution
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
// Importing the registry triggers its side-effect registrations (built-ins
|
|
6
|
+
// live at the bottom of phase-registry.ts), guaranteeing the registry is
|
|
7
|
+
// populated before any PhaseSchema parse runs.
|
|
8
|
+
import { phaseRegistry, getPhaseNames } from "./phase-registry.js";
|
|
5
9
|
/**
|
|
6
10
|
* Canonical Zod schema for all workflow phases.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Backed by the phase registry. `PhaseSchema.parse(name)` succeeds iff
|
|
13
|
+
* `phaseRegistry.has(name)`. The set of valid phases is the registry's
|
|
14
|
+
* keys at the time of parsing — registration happens at module load,
|
|
15
|
+
* so for normal runtime use the set is fixed by the time any code parses.
|
|
16
|
+
*
|
|
17
|
+
* This replaces the prior `z.enum([...])` literal. The set of valid names
|
|
18
|
+
* is identical for the 9 built-in phases; the only observable behavior
|
|
19
|
+
* change is that `PhaseSchema.options` is no longer available — use
|
|
20
|
+
* `getPhaseNames()` from `phase-registry.ts` instead.
|
|
10
21
|
*/
|
|
11
|
-
export const PhaseSchema = z
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"test",
|
|
17
|
-
"verify",
|
|
18
|
-
"qa",
|
|
19
|
-
"loop",
|
|
20
|
-
"merger",
|
|
21
|
-
]);
|
|
22
|
+
export const PhaseSchema = z
|
|
23
|
+
.string()
|
|
24
|
+
.refine((name) => phaseRegistry.has(name), {
|
|
25
|
+
error: (issue) => `Unknown phase "${String(issue.input)}". Available: ${getPhaseNames().join(", ")}`,
|
|
26
|
+
});
|
|
22
27
|
/**
|
|
23
28
|
* Default phases for workflow execution
|
|
24
29
|
*/
|