sequant 1.20.2 → 2.0.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 -4
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +29 -9
- package/dist/bin/cli.js +25 -2
- package/dist/src/commands/doctor.js +42 -9
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +52 -0
- package/dist/src/commands/logs.d.ts +1 -0
- package/dist/src/commands/logs.js +18 -2
- package/dist/src/commands/run.d.ts +7 -0
- package/dist/src/commands/run.js +235 -68
- package/dist/src/commands/serve.d.ts +13 -0
- package/dist/src/commands/serve.js +131 -0
- package/dist/src/commands/stats.d.ts +1 -0
- package/dist/src/commands/stats.js +185 -26
- package/dist/src/commands/status.d.ts +2 -0
- package/dist/src/commands/status.js +99 -50
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +4 -1
- package/dist/src/lib/ac-parser.d.ts +2 -0
- package/dist/src/lib/ac-parser.js +12 -2
- package/dist/src/lib/assess-comment-parser.d.ts +137 -0
- package/dist/src/lib/assess-comment-parser.js +344 -0
- package/dist/src/lib/ci/config.d.ts +22 -0
- package/dist/src/lib/ci/config.js +134 -0
- package/dist/src/lib/ci/index.d.ts +12 -0
- package/dist/src/lib/ci/index.js +10 -0
- package/dist/src/lib/ci/inputs.d.ts +29 -0
- package/dist/src/lib/ci/inputs.js +103 -0
- package/dist/src/lib/ci/labels.d.ts +34 -0
- package/dist/src/lib/ci/labels.js +101 -0
- package/dist/src/lib/ci/outputs.d.ts +25 -0
- package/dist/src/lib/ci/outputs.js +84 -0
- package/dist/src/lib/ci/triggers.d.ts +9 -0
- package/dist/src/lib/ci/triggers.js +86 -0
- package/dist/src/lib/ci/types.d.ts +131 -0
- package/dist/src/lib/ci/types.js +47 -0
- package/dist/src/lib/mcp-config.d.ts +54 -0
- package/dist/src/lib/mcp-config.js +172 -0
- package/dist/src/lib/merge-check/index.js +6 -12
- package/dist/src/lib/merge-check/types.d.ts +20 -7
- package/dist/src/lib/merge-check/types.js +11 -0
- package/dist/src/lib/phase-signal.d.ts +3 -3
- package/dist/src/lib/phase-signal.js +5 -3
- package/dist/src/lib/settings.d.ts +52 -0
- package/dist/src/lib/settings.js +41 -0
- package/dist/src/lib/shutdown.d.ts +16 -5
- package/dist/src/lib/shutdown.js +32 -12
- package/dist/src/lib/solve-comment-parser.d.ts +9 -102
- package/dist/src/lib/solve-comment-parser.js +13 -248
- package/dist/src/lib/stacks.d.ts +8 -0
- package/dist/src/lib/stacks.js +34 -0
- package/dist/src/lib/system.js +3 -7
- package/dist/src/lib/test-tautology-detector.d.ts +10 -0
- package/dist/src/lib/test-tautology-detector.js +43 -4
- package/dist/src/lib/upstream/assessment.js +9 -59
- package/dist/src/lib/upstream/issues.js +12 -75
- package/dist/src/lib/version-check.d.ts +2 -2
- package/dist/src/lib/version-check.js +6 -3
- package/dist/src/lib/version.d.ts +4 -0
- package/dist/src/lib/version.js +25 -0
- package/dist/src/lib/workflow/batch-executor.d.ts +18 -86
- package/dist/src/lib/workflow/batch-executor.js +232 -55
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +56 -0
- package/dist/src/lib/workflow/drivers/agent-driver.js +8 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +18 -0
- package/dist/src/lib/workflow/drivers/aider.js +160 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +165 -0
- package/dist/src/lib/workflow/drivers/index.d.ts +20 -0
- package/dist/src/lib/workflow/drivers/index.js +27 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +16 -0
- package/dist/src/lib/workflow/error-classifier.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +6 -3
- package/dist/src/lib/workflow/log-writer.js +57 -27
- package/dist/src/lib/workflow/metrics-schema.d.ts +9 -9
- package/dist/src/lib/workflow/phase-detection.d.ts +23 -0
- package/dist/src/lib/workflow/phase-detection.js +45 -29
- package/dist/src/lib/workflow/phase-executor.d.ts +42 -3
- package/dist/src/lib/workflow/phase-executor.js +345 -220
- package/dist/src/lib/workflow/phase-mapper.d.ts +1 -1
- package/dist/src/lib/workflow/phase-mapper.js +7 -7
- package/dist/src/lib/workflow/platforms/github.d.ts +157 -0
- package/dist/src/lib/workflow/platforms/github.js +466 -0
- package/dist/src/lib/workflow/platforms/index.d.ts +17 -0
- package/dist/src/lib/workflow/platforms/index.js +25 -0
- package/dist/src/lib/workflow/platforms/platform-provider.d.ts +67 -0
- package/dist/src/lib/workflow/platforms/platform-provider.js +8 -0
- package/dist/src/lib/workflow/pr-status.d.ts +2 -4
- package/dist/src/lib/workflow/pr-status.js +3 -16
- package/dist/src/lib/workflow/qa-cache.d.ts +58 -0
- package/dist/src/lib/workflow/qa-cache.js +88 -0
- package/dist/src/lib/workflow/reconcile.d.ts +69 -0
- package/dist/src/lib/workflow/reconcile.js +290 -0
- package/dist/src/lib/workflow/ring-buffer.d.ts +17 -0
- package/dist/src/lib/workflow/ring-buffer.js +37 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +115 -24
- package/dist/src/lib/workflow/run-log-schema.js +47 -12
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/state-cleanup.js +21 -0
- package/dist/src/lib/workflow/state-manager.d.ts +34 -3
- package/dist/src/lib/workflow/state-manager.js +278 -126
- package/dist/src/lib/workflow/state-schema.d.ts +34 -30
- package/dist/src/lib/workflow/state-schema.js +35 -25
- package/dist/src/lib/workflow/state-utils.d.ts +3 -1
- package/dist/src/lib/workflow/state-utils.js +1 -0
- package/dist/src/lib/workflow/types.d.ts +208 -6
- package/dist/src/lib/workflow/types.js +20 -1
- package/dist/src/lib/workflow/worktree-discovery.d.ts +1 -1
- package/dist/src/lib/workflow/worktree-discovery.js +6 -14
- package/dist/src/lib/workflow/worktree-manager.js +33 -51
- package/dist/src/mcp/index.d.ts +4 -0
- package/dist/src/mcp/index.js +4 -0
- package/dist/src/mcp/resources.d.ts +7 -0
- package/dist/src/mcp/resources.js +111 -0
- package/dist/src/mcp/run-registry.d.ts +34 -0
- package/dist/src/mcp/run-registry.js +42 -0
- package/dist/src/mcp/server.d.ts +12 -0
- package/dist/src/mcp/server.js +50 -0
- package/dist/src/mcp/tools/logs.d.ts +7 -0
- package/dist/src/mcp/tools/logs.js +149 -0
- package/dist/src/mcp/tools/run.d.ts +121 -0
- package/dist/src/mcp/tools/run.js +591 -0
- package/dist/src/mcp/tools/status.d.ts +7 -0
- package/dist/src/mcp/tools/status.js +127 -0
- package/package.json +10 -1
- package/templates/hooks/post-tool.sh +19 -8
- package/templates/hooks/pre-tool.sh +36 -49
- package/templates/mcp.json +6 -0
- package/templates/skills/assess/SKILL.md +354 -352
- package/templates/skills/exec/SKILL.md +64 -1
- package/templates/skills/fullsolve/SKILL.md +35 -4
- package/templates/skills/qa/SKILL.md +486 -9
- package/templates/skills/qa/scripts/quality-checks.sh +1 -1
- package/templates/skills/setup/SKILL.md +386 -0
- package/templates/skills/solve/SKILL.md +38 -664
- package/templates/skills/spec/SKILL.md +90 -31
package/dist/src/commands/run.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { spawnSync } from "child_process";
|
|
12
|
+
import pLimit from "p-limit";
|
|
12
13
|
import { getManifest } from "../lib/manifest.js";
|
|
14
|
+
import { formatElapsedTime } from "../lib/phase-spinner.js";
|
|
13
15
|
import { getSettings } from "../lib/settings.js";
|
|
14
16
|
import { LogWriter } from "../lib/workflow/log-writer.js";
|
|
15
17
|
import { StateManager } from "../lib/workflow/state-manager.js";
|
|
@@ -23,6 +25,13 @@ import { getCommitHash } from "../lib/workflow/git-diff-utils.js";
|
|
|
23
25
|
import { getTokenUsageForRun } from "../lib/workflow/token-utils.js";
|
|
24
26
|
import { reconcileStateAtStartup } from "../lib/workflow/state-utils.js";
|
|
25
27
|
import { analyzeRun, formatReflection } from "../lib/workflow/run-reflect.js";
|
|
28
|
+
/** @internal Log a non-fatal warning: one-line summary always, detail in verbose. */
|
|
29
|
+
export function logNonFatalWarning(message, error, verbose) {
|
|
30
|
+
console.log(chalk.yellow(message));
|
|
31
|
+
if (verbose) {
|
|
32
|
+
console.log(chalk.gray(` ${error}`));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
26
35
|
// Extracted modules
|
|
27
36
|
import { detectDefaultBranch, ensureWorktrees, ensureWorktreesChain, getWorktreeDiffStats, } from "../lib/workflow/worktree-manager.js";
|
|
28
37
|
import { formatDuration } from "../lib/workflow/phase-executor.js";
|
|
@@ -32,6 +41,64 @@ export { parseQaVerdict, formatDuration, executePhaseWithRetry, } from "../lib/w
|
|
|
32
41
|
export { detectDefaultBranch, checkWorktreeFreshness, removeStaleWorktree, listWorktrees, getWorktreeChangedFiles, getWorktreeDiffStats, readCacheMetrics, filterResumedPhases, ensureWorktree, createCheckpointCommit, reinstallIfLockfileChanged, rebaseBeforePR, createPR, } from "../lib/workflow/worktree-manager.js";
|
|
33
42
|
export { detectPhasesFromLabels, parseRecommendedWorkflow, determinePhasesForIssue, } from "../lib/workflow/phase-mapper.js";
|
|
34
43
|
export { getIssueInfo, sortByDependencies, parseBatches, getEnvConfig, executeBatch, runIssueWithLogging, } from "../lib/workflow/batch-executor.js";
|
|
44
|
+
/**
|
|
45
|
+
* Normalize Commander.js --no-X flags into typed RunOptions fields.
|
|
46
|
+
* Replaces the `as any` cast (#402 AC-4).
|
|
47
|
+
*/
|
|
48
|
+
export function normalizeCommanderOptions(options) {
|
|
49
|
+
const raw = options;
|
|
50
|
+
return {
|
|
51
|
+
...options,
|
|
52
|
+
...(raw.log === false && { noLog: true }),
|
|
53
|
+
...(raw.smartTests === false && { noSmartTests: true }),
|
|
54
|
+
...(raw.mcp === false && { noMcp: true }),
|
|
55
|
+
...(raw.retry === false && { noRetry: true }),
|
|
56
|
+
...(raw.rebase === false && { noRebase: true }),
|
|
57
|
+
...(raw.pr === false && { noPr: true }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Execute a single issue with log bookkeeping (start/complete/PR info).
|
|
62
|
+
* Replaces the duplicated per-issue wrapper in sequential and parallel loops (#402 AC-1).
|
|
63
|
+
*/
|
|
64
|
+
async function executeOneIssue(args) {
|
|
65
|
+
const { issueNumber, batchCtx, chain, parallelIssueNumber } = args;
|
|
66
|
+
const { config, options, issueInfoMap, worktreeMap, logWriter, stateManager, shutdownManager, packageManager, baseBranch, onProgress, } = batchCtx;
|
|
67
|
+
const issueInfo = issueInfoMap.get(issueNumber) ?? {
|
|
68
|
+
title: `Issue #${issueNumber}`,
|
|
69
|
+
labels: [],
|
|
70
|
+
};
|
|
71
|
+
const worktreeInfo = worktreeMap.get(issueNumber);
|
|
72
|
+
// Start issue logging
|
|
73
|
+
if (logWriter) {
|
|
74
|
+
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
75
|
+
}
|
|
76
|
+
const ctx = {
|
|
77
|
+
issueNumber,
|
|
78
|
+
title: issueInfo.title,
|
|
79
|
+
labels: issueInfo.labels,
|
|
80
|
+
config,
|
|
81
|
+
options,
|
|
82
|
+
services: { logWriter, stateManager, shutdownManager },
|
|
83
|
+
worktree: worktreeInfo
|
|
84
|
+
? { path: worktreeInfo.path, branch: worktreeInfo.branch }
|
|
85
|
+
: undefined,
|
|
86
|
+
chain,
|
|
87
|
+
packageManager,
|
|
88
|
+
baseBranch,
|
|
89
|
+
onProgress,
|
|
90
|
+
};
|
|
91
|
+
const result = await runIssueWithLogging(ctx);
|
|
92
|
+
// Record PR info in log before completing issue
|
|
93
|
+
if (logWriter && result.prNumber && result.prUrl) {
|
|
94
|
+
logWriter.setPRInfo(result.prNumber, result.prUrl, parallelIssueNumber);
|
|
95
|
+
}
|
|
96
|
+
// Complete issue logging
|
|
97
|
+
if (logWriter) {
|
|
98
|
+
logWriter.completeIssue(parallelIssueNumber);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
35
102
|
/**
|
|
36
103
|
* Main run command
|
|
37
104
|
*/
|
|
@@ -61,22 +128,11 @@ export async function runCommand(issues, options) {
|
|
|
61
128
|
const envConfig = getEnvConfig();
|
|
62
129
|
// Settings provide defaults, env overrides settings, CLI overrides all
|
|
63
130
|
// Note: phases are auto-detected per-issue unless --phases is explicitly set
|
|
64
|
-
|
|
65
|
-
// Normalize these so RunOptions fields (noLog, noMcp, etc.) work correctly.
|
|
66
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
-
const cliOpts = options;
|
|
68
|
-
const normalizedOptions = {
|
|
69
|
-
...options,
|
|
70
|
-
...(cliOpts.log === false && { noLog: true }),
|
|
71
|
-
...(cliOpts.smartTests === false && { noSmartTests: true }),
|
|
72
|
-
...(cliOpts.mcp === false && { noMcp: true }),
|
|
73
|
-
...(cliOpts.retry === false && { noRetry: true }),
|
|
74
|
-
...(cliOpts.rebase === false && { noRebase: true }),
|
|
75
|
-
...(cliOpts.pr === false && { noPr: true }),
|
|
76
|
-
};
|
|
131
|
+
const normalizedOptions = normalizeCommanderOptions(options);
|
|
77
132
|
const mergedOptions = {
|
|
78
133
|
// Settings defaults (phases removed - now auto-detected)
|
|
79
134
|
sequential: normalizedOptions.sequential ?? settings.run.sequential,
|
|
135
|
+
concurrency: normalizedOptions.concurrency ?? settings.run.concurrency,
|
|
80
136
|
timeout: normalizedOptions.timeout ?? settings.run.timeout,
|
|
81
137
|
logPath: normalizedOptions.logPath ?? settings.run.logPath,
|
|
82
138
|
qualityLoop: normalizedOptions.qualityLoop ?? settings.run.qualityLoop,
|
|
@@ -132,6 +188,13 @@ export async function runCommand(issues, options) {
|
|
|
132
188
|
console.log("");
|
|
133
189
|
}
|
|
134
190
|
}
|
|
191
|
+
// Validate concurrency value
|
|
192
|
+
if (mergedOptions.concurrency !== undefined &&
|
|
193
|
+
(mergedOptions.concurrency < 1 ||
|
|
194
|
+
!Number.isInteger(mergedOptions.concurrency))) {
|
|
195
|
+
console.log(chalk.red(`❌ Invalid --concurrency value: ${mergedOptions.concurrency}. Must be a positive integer.`));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
135
198
|
// Validate QA gate requirements
|
|
136
199
|
if (mergedOptions.qaGate && !mergedOptions.chain) {
|
|
137
200
|
console.log(chalk.red("❌ --qa-gate requires --chain flag"));
|
|
@@ -161,10 +224,14 @@ export async function runCommand(issues, options) {
|
|
|
161
224
|
const retryEnabled = mergedOptions.noRetry
|
|
162
225
|
? false
|
|
163
226
|
: (settings.run.retry ?? true);
|
|
227
|
+
const isSequential = mergedOptions.sequential ?? false;
|
|
228
|
+
const isParallel = !isSequential && issueNumbers.length > 1;
|
|
164
229
|
const config = {
|
|
165
230
|
...DEFAULT_CONFIG,
|
|
166
231
|
phases: explicitPhases ?? DEFAULT_PHASES,
|
|
167
|
-
sequential:
|
|
232
|
+
sequential: isSequential,
|
|
233
|
+
concurrency: mergedOptions.concurrency ?? DEFAULT_CONFIG.concurrency,
|
|
234
|
+
parallel: isParallel,
|
|
168
235
|
dryRun: mergedOptions.dryRun ?? false,
|
|
169
236
|
verbose: mergedOptions.verbose ?? false,
|
|
170
237
|
phaseTimeout: mergedOptions.timeout ?? DEFAULT_CONFIG.phaseTimeout,
|
|
@@ -173,6 +240,8 @@ export async function runCommand(issues, options) {
|
|
|
173
240
|
noSmartTests: mergedOptions.noSmartTests ?? false,
|
|
174
241
|
mcp: mcpEnabled,
|
|
175
242
|
retry: retryEnabled,
|
|
243
|
+
agent: mergedOptions.agent ?? settings.run.agent,
|
|
244
|
+
aiderSettings: settings.run.aider,
|
|
176
245
|
};
|
|
177
246
|
// Propagate verbose mode to UI config so spinners use text-only mode.
|
|
178
247
|
// This prevents animated spinner control characters from colliding with
|
|
@@ -234,7 +303,7 @@ export async function runCommand(issues, options) {
|
|
|
234
303
|
else {
|
|
235
304
|
console.log(chalk.gray(` Phases: ${config.phases.join(" → ")}`));
|
|
236
305
|
}
|
|
237
|
-
console.log(chalk.gray(` Mode: ${config.sequential ? "stop-on-failure" :
|
|
306
|
+
console.log(chalk.gray(` Mode: ${config.sequential ? "sequential (stop-on-failure)" : `parallel (concurrency: ${config.concurrency})`}`));
|
|
238
307
|
if (config.qualityLoop) {
|
|
239
308
|
console.log(chalk.gray(` Quality loop: enabled (max ${config.maxIterations} iterations)`));
|
|
240
309
|
}
|
|
@@ -270,11 +339,9 @@ export async function runCommand(issues, options) {
|
|
|
270
339
|
console.log(chalk.gray(` State reconciled: ${reconcileResult.advanced.map((n) => `#${n}`).join(", ")} → merged`));
|
|
271
340
|
}
|
|
272
341
|
}
|
|
273
|
-
catch {
|
|
342
|
+
catch (error) {
|
|
274
343
|
// AC-8: Graceful degradation - don't block execution on reconciliation failure
|
|
275
|
-
|
|
276
|
-
console.log(chalk.yellow(` ⚠️ State reconciliation failed, continuing...`));
|
|
277
|
-
}
|
|
344
|
+
logNonFatalWarning(` ⚠️ State reconciliation failed, continuing...`, error, config.verbose);
|
|
278
345
|
}
|
|
279
346
|
}
|
|
280
347
|
// AC-1 & AC-2: Pre-flight state guard - skip completed issues unless --force
|
|
@@ -294,8 +361,9 @@ export async function runCommand(issues, options) {
|
|
|
294
361
|
activeIssues.push(issueNumber);
|
|
295
362
|
}
|
|
296
363
|
}
|
|
297
|
-
catch {
|
|
364
|
+
catch (error) {
|
|
298
365
|
// AC-8: Graceful degradation - if state check fails, include the issue
|
|
366
|
+
logNonFatalWarning(` ⚠️ State lookup failed for #${issueNumber}, including anyway...`, error, config.verbose);
|
|
299
367
|
activeIssues.push(issueNumber);
|
|
300
368
|
}
|
|
301
369
|
}
|
|
@@ -357,6 +425,18 @@ export async function runCommand(issues, options) {
|
|
|
357
425
|
}
|
|
358
426
|
}
|
|
359
427
|
}
|
|
428
|
+
// Shared context for all execution paths
|
|
429
|
+
const batchCtx = {
|
|
430
|
+
config,
|
|
431
|
+
options: mergedOptions,
|
|
432
|
+
issueInfoMap,
|
|
433
|
+
worktreeMap,
|
|
434
|
+
logWriter,
|
|
435
|
+
stateManager,
|
|
436
|
+
shutdownManager: shutdown,
|
|
437
|
+
packageManager: manifest.packageManager,
|
|
438
|
+
baseBranch: resolvedBaseBranch,
|
|
439
|
+
};
|
|
360
440
|
// Execute with graceful shutdown handling
|
|
361
441
|
const results = [];
|
|
362
442
|
let exitCode = 0;
|
|
@@ -366,7 +446,7 @@ export async function runCommand(issues, options) {
|
|
|
366
446
|
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
|
367
447
|
const batch = batches[batchIdx];
|
|
368
448
|
console.log(chalk.blue(`\n Batch ${batchIdx + 1}/${batches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
|
|
369
|
-
const batchResults = await executeBatch(batch,
|
|
449
|
+
const batchResults = await executeBatch(batch, batchCtx);
|
|
370
450
|
results.push(...batchResults);
|
|
371
451
|
// Check if batch failed and we should stop
|
|
372
452
|
const batchFailed = batchResults.some((r) => !r.success);
|
|
@@ -380,28 +460,14 @@ export async function runCommand(issues, options) {
|
|
|
380
460
|
// Sequential execution
|
|
381
461
|
for (let i = 0; i < issueNumbers.length; i++) {
|
|
382
462
|
const issueNumber = issueNumbers[i];
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
391
|
-
}
|
|
392
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, mergedOptions, worktreeInfo?.path, worktreeInfo?.branch, shutdown, mergedOptions.chain, // Enable checkpoint commits in chain mode
|
|
393
|
-
manifest.packageManager,
|
|
394
|
-
// In chain mode, only the last issue should trigger pre-PR rebase
|
|
395
|
-
mergedOptions.chain ? i === issueNumbers.length - 1 : undefined, resolvedBaseBranch);
|
|
463
|
+
const result = await executeOneIssue({
|
|
464
|
+
issueNumber,
|
|
465
|
+
batchCtx,
|
|
466
|
+
chain: mergedOptions.chain
|
|
467
|
+
? { enabled: true, isLast: i === issueNumbers.length - 1 }
|
|
468
|
+
: undefined,
|
|
469
|
+
});
|
|
396
470
|
results.push(result);
|
|
397
|
-
// Record PR info in log before completing issue
|
|
398
|
-
if (logWriter && result.prNumber && result.prUrl) {
|
|
399
|
-
logWriter.setPRInfo(result.prNumber, result.prUrl);
|
|
400
|
-
}
|
|
401
|
-
// Complete issue logging
|
|
402
|
-
if (logWriter) {
|
|
403
|
-
logWriter.completeIssue();
|
|
404
|
-
}
|
|
405
471
|
// Check if shutdown was triggered
|
|
406
472
|
if (shutdown.shuttingDown) {
|
|
407
473
|
break;
|
|
@@ -435,32 +501,135 @@ export async function runCommand(issues, options) {
|
|
|
435
501
|
}
|
|
436
502
|
}
|
|
437
503
|
else {
|
|
438
|
-
// Default mode: run issues
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
504
|
+
// Default mode: run issues concurrently with configurable concurrency limit
|
|
505
|
+
const limit = pLimit(config.concurrency);
|
|
506
|
+
// Track progress for concurrent issues
|
|
507
|
+
const issueStatus = new Map();
|
|
508
|
+
for (const num of issueNumbers) {
|
|
509
|
+
issueStatus.set(num, { state: "running" });
|
|
510
|
+
}
|
|
511
|
+
const renderProgressLine = () => {
|
|
512
|
+
const parts = issueNumbers.map((num) => {
|
|
513
|
+
const info = issueStatus.get(num);
|
|
514
|
+
if (info.state === "done")
|
|
515
|
+
return colors.success(`#${num} ✓`);
|
|
516
|
+
if (info.state === "failed")
|
|
517
|
+
return colors.error(`#${num} ✗`);
|
|
518
|
+
return colors.warning(`#${num} ⏳`);
|
|
519
|
+
});
|
|
520
|
+
return ` Progress: ${parts.join(" ")}`;
|
|
521
|
+
};
|
|
522
|
+
const updateProgress = (completedIssue) => {
|
|
523
|
+
if (mergedOptions.quiet)
|
|
524
|
+
return;
|
|
525
|
+
if (process.stdout.isTTY) {
|
|
526
|
+
// TTY: overwrite the progress line in place
|
|
527
|
+
process.stdout.write(`\r${renderProgressLine()}`);
|
|
444
528
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
529
|
+
// Print a per-issue completion summary (works on both TTY and non-TTY)
|
|
530
|
+
if (completedIssue != null) {
|
|
531
|
+
const info = issueStatus.get(completedIssue);
|
|
532
|
+
const duration = info.durationSeconds != null
|
|
533
|
+
? ` (${formatElapsedTime(info.durationSeconds)})`
|
|
534
|
+
: "";
|
|
535
|
+
if (info.state === "done") {
|
|
536
|
+
const line = ` ${colors.success("✓")} Issue #${completedIssue} completed${duration}`;
|
|
537
|
+
if (process.stdout.isTTY) {
|
|
538
|
+
// Move to a new line before printing the summary, then re-render progress
|
|
539
|
+
process.stdout.write(`\n${line}\n${renderProgressLine()}`);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
console.log(line);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const errorSuffix = info.error ? `: ${info.error}` : "";
|
|
547
|
+
const line = ` ${colors.error("✗")} Issue #${completedIssue} failed${duration}${errorSuffix}`;
|
|
548
|
+
if (process.stdout.isTTY) {
|
|
549
|
+
process.stdout.write(`\n${line}\n${renderProgressLine()}`);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
console.log(line);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
453
555
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
556
|
+
};
|
|
557
|
+
// Per-phase progress callback for parallel mode (AC-1, AC-3)
|
|
558
|
+
const parallelStartTime = Date.now();
|
|
559
|
+
const onPhaseProgress = (issue, phase, event, extra) => {
|
|
560
|
+
if (mergedOptions.quiet)
|
|
561
|
+
return;
|
|
562
|
+
let line;
|
|
563
|
+
if (event === "start") {
|
|
564
|
+
line = ` ${colors.warning("●")} #${issue}: ${phase} started`;
|
|
460
565
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
566
|
+
else if (event === "complete") {
|
|
567
|
+
const dur = extra?.durationSeconds != null
|
|
568
|
+
? ` (${formatElapsedTime(extra.durationSeconds)})`
|
|
569
|
+
: "";
|
|
570
|
+
line = ` ${colors.success("●")} #${issue}: ${phase} ✓${dur}`;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
line = ` ${colors.error("●")} #${issue}: ${phase} ✗`;
|
|
574
|
+
}
|
|
575
|
+
console.log(line);
|
|
576
|
+
};
|
|
577
|
+
// 60-second heartbeat timer so the terminal never appears frozen (AC-2)
|
|
578
|
+
const HEARTBEAT_INTERVAL_MS = 60_000;
|
|
579
|
+
const heartbeatTimer = setInterval(() => {
|
|
580
|
+
if (mergedOptions.quiet)
|
|
581
|
+
return;
|
|
582
|
+
const elapsedSec = Math.round((Date.now() - parallelStartTime) / 1000);
|
|
583
|
+
console.log(` ${colors.warning("⏳")} Still running... (${formatElapsedTime(elapsedSec)} elapsed)`);
|
|
584
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
585
|
+
updateProgress();
|
|
586
|
+
const settledResults = await Promise.allSettled(issueNumbers.map((issueNumber) => limit(async () => {
|
|
587
|
+
// Check if shutdown was triggered before starting
|
|
588
|
+
if (shutdown.shuttingDown) {
|
|
589
|
+
return {
|
|
590
|
+
issueNumber,
|
|
591
|
+
success: false,
|
|
592
|
+
phaseResults: [],
|
|
593
|
+
durationSeconds: 0,
|
|
594
|
+
loopTriggered: false,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
const result = await executeOneIssue({
|
|
598
|
+
issueNumber,
|
|
599
|
+
batchCtx: { ...batchCtx, onProgress: onPhaseProgress },
|
|
600
|
+
parallelIssueNumber: issueNumber,
|
|
601
|
+
});
|
|
602
|
+
// Update progress with completion details
|
|
603
|
+
issueStatus.set(issueNumber, {
|
|
604
|
+
state: result.success ? "done" : "failed",
|
|
605
|
+
durationSeconds: result.durationSeconds,
|
|
606
|
+
error: result.phaseResults.find((p) => !p.success)?.error,
|
|
607
|
+
});
|
|
608
|
+
updateProgress(issueNumber);
|
|
609
|
+
return result;
|
|
610
|
+
})));
|
|
611
|
+
// Clean up heartbeat timer
|
|
612
|
+
clearInterval(heartbeatTimer);
|
|
613
|
+
// Clear the progress line
|
|
614
|
+
if (process.stdout.isTTY && !mergedOptions.quiet) {
|
|
615
|
+
process.stdout.write("\n");
|
|
616
|
+
}
|
|
617
|
+
// Collect results from settled promises
|
|
618
|
+
for (let i = 0; i < settledResults.length; i++) {
|
|
619
|
+
const settled = settledResults[i];
|
|
620
|
+
if (settled.status === "fulfilled") {
|
|
621
|
+
results.push(settled.value);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
// Defensive fallback — runIssueWithLogging catches errors internally,
|
|
625
|
+
// so this path is unreachable in normal operation.
|
|
626
|
+
results.push({
|
|
627
|
+
issueNumber: issueNumbers[i],
|
|
628
|
+
success: false,
|
|
629
|
+
phaseResults: [],
|
|
630
|
+
durationSeconds: 0,
|
|
631
|
+
loopTriggered: false,
|
|
632
|
+
});
|
|
464
633
|
}
|
|
465
634
|
}
|
|
466
635
|
}
|
|
@@ -555,9 +724,7 @@ export async function runCommand(issues, options) {
|
|
|
555
724
|
}
|
|
556
725
|
catch (metricsError) {
|
|
557
726
|
// Metrics recording errors shouldn't stop execution
|
|
558
|
-
|
|
559
|
-
console.log(chalk.yellow(` ⚠️ Metrics recording error: ${metricsError}`));
|
|
560
|
-
}
|
|
727
|
+
logNonFatalWarning(` ⚠️ Metrics recording failed, continuing...`, metricsError, config.verbose);
|
|
561
728
|
}
|
|
562
729
|
}
|
|
563
730
|
// Summary
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequant serve - Start MCP server
|
|
3
|
+
*
|
|
4
|
+
* Exposes Sequant workflow orchestration as tools and resources
|
|
5
|
+
* over the Model Context Protocol (MCP).
|
|
6
|
+
*
|
|
7
|
+
* Supports stdio (default) and SSE transports.
|
|
8
|
+
*/
|
|
9
|
+
export interface ServeOptions {
|
|
10
|
+
transport?: "stdio" | "sse";
|
|
11
|
+
port?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function serveCommand(options: ServeOptions): Promise<void>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequant serve - Start MCP server
|
|
3
|
+
*
|
|
4
|
+
* Exposes Sequant workflow orchestration as tools and resources
|
|
5
|
+
* over the Model Context Protocol (MCP).
|
|
6
|
+
*
|
|
7
|
+
* Supports stdio (default) and SSE transports.
|
|
8
|
+
*/
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
11
|
+
import { createServer as createHttpServer, } from "http";
|
|
12
|
+
import { createServer } from "../mcp/server.js";
|
|
13
|
+
import { getVersion } from "../lib/version.js";
|
|
14
|
+
export async function serveCommand(options) {
|
|
15
|
+
const version = getVersion();
|
|
16
|
+
const server = createServer(version);
|
|
17
|
+
const transportType = options.transport || "stdio";
|
|
18
|
+
if (transportType === "sse") {
|
|
19
|
+
await startSSE(server, options.port || 3100);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
await startStdio(server);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function startStdio(server) {
|
|
26
|
+
const transport = new StdioServerTransport();
|
|
27
|
+
// Handle graceful shutdown
|
|
28
|
+
let shuttingDown = false;
|
|
29
|
+
const shutdown = async () => {
|
|
30
|
+
if (shuttingDown)
|
|
31
|
+
return;
|
|
32
|
+
shuttingDown = true;
|
|
33
|
+
try {
|
|
34
|
+
await server.close();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* best-effort */
|
|
38
|
+
}
|
|
39
|
+
process.exit(0);
|
|
40
|
+
};
|
|
41
|
+
process.on("SIGINT", shutdown);
|
|
42
|
+
process.on("SIGTERM", shutdown);
|
|
43
|
+
await server.connect(transport);
|
|
44
|
+
// Write startup info to stderr (stdout is for MCP protocol)
|
|
45
|
+
process.stderr.write(`Sequant MCP server started (stdio)\n`);
|
|
46
|
+
}
|
|
47
|
+
async function startSSE(server, port) {
|
|
48
|
+
let sseTransport = null;
|
|
49
|
+
let clientConnected = false;
|
|
50
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
51
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
52
|
+
if (url.pathname === "/sse" && req.method === "GET") {
|
|
53
|
+
// Reject if a client is already connected
|
|
54
|
+
if (clientConnected) {
|
|
55
|
+
res.writeHead(409, { "Content-Type": "application/json" });
|
|
56
|
+
res.end(JSON.stringify({
|
|
57
|
+
error: "conflict",
|
|
58
|
+
message: "Another SSE client is already connected",
|
|
59
|
+
}));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// SSE endpoint - create transport and connect
|
|
63
|
+
sseTransport = new SSEServerTransport("/messages", res);
|
|
64
|
+
clientConnected = true;
|
|
65
|
+
// Clean up on client disconnect
|
|
66
|
+
res.on("close", () => {
|
|
67
|
+
clientConnected = false;
|
|
68
|
+
sseTransport = null;
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
await server.connect(sseTransport);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
clientConnected = false;
|
|
75
|
+
sseTransport = null;
|
|
76
|
+
if (!res.headersSent) {
|
|
77
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
78
|
+
res.end(JSON.stringify({
|
|
79
|
+
error: "connection_failed",
|
|
80
|
+
message: "Failed to establish MCP transport connection",
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (url.pathname === "/messages" &&
|
|
86
|
+
req.method === "POST" &&
|
|
87
|
+
sseTransport) {
|
|
88
|
+
// Message endpoint for client-to-server communication
|
|
89
|
+
await sseTransport.handlePostMessage(req, res);
|
|
90
|
+
}
|
|
91
|
+
else if (url.pathname === "/health" && req.method === "GET") {
|
|
92
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
93
|
+
res.end(JSON.stringify({
|
|
94
|
+
status: "ok",
|
|
95
|
+
transport: "sse",
|
|
96
|
+
connected: clientConnected,
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
res.writeHead(404);
|
|
101
|
+
res.end("Not found");
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// Handle graceful shutdown
|
|
105
|
+
let shuttingDown = false;
|
|
106
|
+
const shutdown = async () => {
|
|
107
|
+
if (shuttingDown)
|
|
108
|
+
return;
|
|
109
|
+
shuttingDown = true;
|
|
110
|
+
try {
|
|
111
|
+
await server.close();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* best-effort */
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* best-effort */
|
|
121
|
+
}
|
|
122
|
+
process.exit(0);
|
|
123
|
+
};
|
|
124
|
+
process.on("SIGINT", shutdown);
|
|
125
|
+
process.on("SIGTERM", shutdown);
|
|
126
|
+
httpServer.listen(port, "127.0.0.1", () => {
|
|
127
|
+
console.log(`Sequant MCP server started (SSE) on 127.0.0.1:${port}`);
|
|
128
|
+
console.log(` SSE endpoint: http://127.0.0.1:${port}/sse`);
|
|
129
|
+
console.log(` Health check: http://127.0.0.1:${port}/health`);
|
|
130
|
+
});
|
|
131
|
+
}
|