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
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync } from "fs";
|
|
|
10
10
|
import path from "path";
|
|
11
11
|
import { PM_CONFIG } from "../stacks.js";
|
|
12
12
|
import { getResumablePhasesForIssue } from "./phase-detection.js";
|
|
13
|
+
import { GitHubProvider } from "./platforms/github.js";
|
|
13
14
|
/**
|
|
14
15
|
* Lockfile names for different package managers
|
|
15
16
|
*/
|
|
@@ -797,26 +798,19 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
|
|
|
797
798
|
* @internal Exported for testing
|
|
798
799
|
*/
|
|
799
800
|
export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels) {
|
|
801
|
+
const github = new GitHubProvider();
|
|
800
802
|
// Step 1: Check for existing PR on this branch
|
|
801
|
-
const
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
if (prInfo.number && prInfo.url) {
|
|
806
|
-
if (verbose) {
|
|
807
|
-
console.log(chalk.gray(` ℹ️ PR #${prInfo.number} already exists for branch ${branch}`));
|
|
808
|
-
}
|
|
809
|
-
return {
|
|
810
|
-
attempted: true,
|
|
811
|
-
success: true,
|
|
812
|
-
prNumber: prInfo.number,
|
|
813
|
-
prUrl: prInfo.url,
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
catch {
|
|
818
|
-
// JSON parse failed — no existing PR, continue to create
|
|
803
|
+
const existingPRInfo = github.viewPRByBranchSync(branch, worktreePath);
|
|
804
|
+
if (existingPRInfo) {
|
|
805
|
+
if (verbose) {
|
|
806
|
+
console.log(chalk.gray(` ℹ️ PR #${existingPRInfo.number} already exists for branch ${branch}`));
|
|
819
807
|
}
|
|
808
|
+
return {
|
|
809
|
+
attempted: true,
|
|
810
|
+
success: true,
|
|
811
|
+
prNumber: existingPRInfo.number,
|
|
812
|
+
prUrl: existingPRInfo.url,
|
|
813
|
+
};
|
|
820
814
|
}
|
|
821
815
|
// Step 2: Push branch to remote
|
|
822
816
|
if (verbose) {
|
|
@@ -849,25 +843,19 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
|
|
|
849
843
|
`---`,
|
|
850
844
|
`🤖 Generated by \`sequant run\``,
|
|
851
845
|
].join("\n");
|
|
852
|
-
const prResult =
|
|
853
|
-
if (prResult.
|
|
854
|
-
const prError = prResult.stderr
|
|
846
|
+
const prResult = github.createPRCliSync(prTitle, prBody, branch, worktreePath);
|
|
847
|
+
if (prResult.exitCode !== 0) {
|
|
848
|
+
const prError = prResult.stderr.trim() || "Unknown error";
|
|
855
849
|
// Check if PR already exists (race condition or push-before-PR scenarios)
|
|
856
850
|
if (prError.includes("already exists")) {
|
|
857
|
-
const
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
prUrl: prInfo.url,
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
catch {
|
|
869
|
-
// Fall through to error
|
|
870
|
-
}
|
|
851
|
+
const retryInfo = github.viewPRByBranchSync(branch, worktreePath);
|
|
852
|
+
if (retryInfo) {
|
|
853
|
+
return {
|
|
854
|
+
attempted: true,
|
|
855
|
+
success: true,
|
|
856
|
+
prNumber: retryInfo.number,
|
|
857
|
+
prUrl: retryInfo.url,
|
|
858
|
+
};
|
|
871
859
|
}
|
|
872
860
|
}
|
|
873
861
|
console.log(chalk.yellow(` ⚠️ PR creation failed: ${prError}`));
|
|
@@ -878,7 +866,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
|
|
|
878
866
|
};
|
|
879
867
|
}
|
|
880
868
|
// Step 4: Extract PR URL from output and get PR details
|
|
881
|
-
const prOutput = prResult.stdout
|
|
869
|
+
const prOutput = prResult.stdout.trim();
|
|
882
870
|
const prUrlMatch = prOutput.match(/https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/);
|
|
883
871
|
if (prUrlMatch) {
|
|
884
872
|
const prNumber = parseInt(prUrlMatch[1], 10);
|
|
@@ -892,21 +880,15 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
|
|
|
892
880
|
};
|
|
893
881
|
}
|
|
894
882
|
// Fallback: try gh pr view to get details
|
|
895
|
-
const
|
|
896
|
-
if (
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
prUrl: prInfo.url,
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
catch {
|
|
908
|
-
// Fall through
|
|
909
|
-
}
|
|
883
|
+
const viewInfo = github.viewPRByBranchSync(branch, worktreePath);
|
|
884
|
+
if (viewInfo) {
|
|
885
|
+
console.log(chalk.green(` ✅ PR #${viewInfo.number} created: ${viewInfo.url}`));
|
|
886
|
+
return {
|
|
887
|
+
attempted: true,
|
|
888
|
+
success: true,
|
|
889
|
+
prNumber: viewInfo.number,
|
|
890
|
+
prUrl: viewInfo.url,
|
|
891
|
+
};
|
|
910
892
|
}
|
|
911
893
|
// PR was created but we couldn't parse the URL
|
|
912
894
|
console.log(chalk.yellow(` ⚠️ PR created but could not extract URL from output: ${prOutput}`));
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources for Sequant
|
|
3
|
+
*
|
|
4
|
+
* Exposes workflow state and configuration as readable resources.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import { StateManager } from "../lib/workflow/state-manager.js";
|
|
8
|
+
import { SETTINGS_PATH } from "../lib/settings.js";
|
|
9
|
+
export function registerResources(server) {
|
|
10
|
+
// sequant://state — current workflow state
|
|
11
|
+
server.registerResource("state", "sequant://state", {
|
|
12
|
+
description: "Dashboard view of all tracked GitHub issues and their workflow progress. " +
|
|
13
|
+
"Contains per-issue phase status (spec/exec/qa), worktree paths, PR links, and QA verdicts. " +
|
|
14
|
+
"Read this to understand which issues are in-flight before starting new work.",
|
|
15
|
+
mimeType: "application/json",
|
|
16
|
+
}, async () => {
|
|
17
|
+
try {
|
|
18
|
+
const stateManager = new StateManager();
|
|
19
|
+
if (!stateManager.stateExists()) {
|
|
20
|
+
return {
|
|
21
|
+
contents: [
|
|
22
|
+
{
|
|
23
|
+
uri: "sequant://state",
|
|
24
|
+
mimeType: "application/json",
|
|
25
|
+
text: JSON.stringify({ issues: {} }),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Use getAllIssueStates() which applies TTL filtering
|
|
31
|
+
const filteredIssues = await stateManager.getAllIssueStates();
|
|
32
|
+
const state = await stateManager.getState();
|
|
33
|
+
const output = {
|
|
34
|
+
version: state.version,
|
|
35
|
+
lastUpdated: state.lastUpdated,
|
|
36
|
+
lastSynced: state.lastSynced,
|
|
37
|
+
issues: Object.fromEntries(Object.values(filteredIssues).map((v) => [String(v.number), v])),
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
contents: [
|
|
41
|
+
{
|
|
42
|
+
uri: "sequant://state",
|
|
43
|
+
mimeType: "application/json",
|
|
44
|
+
text: JSON.stringify(output, null, 2),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
contents: [
|
|
52
|
+
{
|
|
53
|
+
uri: "sequant://state",
|
|
54
|
+
mimeType: "application/json",
|
|
55
|
+
text: JSON.stringify({
|
|
56
|
+
error: "Failed to read state",
|
|
57
|
+
message: error instanceof Error ? error.message : String(error),
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// sequant://config — current Sequant configuration
|
|
65
|
+
server.registerResource("config", "sequant://config", {
|
|
66
|
+
description: "Current Sequant workflow settings including default phases, timeout limits, " +
|
|
67
|
+
"quality loop configuration, and agent preferences. " +
|
|
68
|
+
"Read this to understand how sequant_run will behave before invoking it.",
|
|
69
|
+
mimeType: "application/json",
|
|
70
|
+
}, async () => {
|
|
71
|
+
try {
|
|
72
|
+
if (!fs.existsSync(SETTINGS_PATH)) {
|
|
73
|
+
return {
|
|
74
|
+
contents: [
|
|
75
|
+
{
|
|
76
|
+
uri: "sequant://config",
|
|
77
|
+
mimeType: "application/json",
|
|
78
|
+
text: JSON.stringify({
|
|
79
|
+
message: "No settings file found. Run `sequant init` to create one.",
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const content = fs.readFileSync(SETTINGS_PATH, "utf-8");
|
|
86
|
+
return {
|
|
87
|
+
contents: [
|
|
88
|
+
{
|
|
89
|
+
uri: "sequant://config",
|
|
90
|
+
mimeType: "application/json",
|
|
91
|
+
text: content,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
contents: [
|
|
99
|
+
{
|
|
100
|
+
uri: "sequant://config",
|
|
101
|
+
mimeType: "application/json",
|
|
102
|
+
text: JSON.stringify({
|
|
103
|
+
error: "Failed to read config",
|
|
104
|
+
message: error instanceof Error ? error.message : String(error),
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active run registry for MCP server
|
|
3
|
+
*
|
|
4
|
+
* Tracks which workflow runs are currently active so that
|
|
5
|
+
* sequant_status can report isRunning state in real time.
|
|
6
|
+
*
|
|
7
|
+
* Module-level singleton — all tools in the same process share one registry.
|
|
8
|
+
*/
|
|
9
|
+
export interface ActiveRun {
|
|
10
|
+
startedAt: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Register an active run for an issue.
|
|
14
|
+
* If a run is already registered for this issue, it is replaced.
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerRun(issue: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Unregister a run for an issue (called on process exit/error/abort).
|
|
19
|
+
*/
|
|
20
|
+
export declare function unregisterRun(issue: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a run is currently active for the given issue.
|
|
23
|
+
* Returns false for untracked issues.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isRunning(issue: number): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Get info about all currently active runs.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getActiveRuns(): ReadonlyMap<number, ActiveRun>;
|
|
30
|
+
/**
|
|
31
|
+
* Clear all active runs. Intended for testing only.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export declare function clearRegistry(): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active run registry for MCP server
|
|
3
|
+
*
|
|
4
|
+
* Tracks which workflow runs are currently active so that
|
|
5
|
+
* sequant_status can report isRunning state in real time.
|
|
6
|
+
*
|
|
7
|
+
* Module-level singleton — all tools in the same process share one registry.
|
|
8
|
+
*/
|
|
9
|
+
const activeRuns = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Register an active run for an issue.
|
|
12
|
+
* If a run is already registered for this issue, it is replaced.
|
|
13
|
+
*/
|
|
14
|
+
export function registerRun(issue) {
|
|
15
|
+
activeRuns.set(issue, { startedAt: new Date().toISOString() });
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Unregister a run for an issue (called on process exit/error/abort).
|
|
19
|
+
*/
|
|
20
|
+
export function unregisterRun(issue) {
|
|
21
|
+
activeRuns.delete(issue);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a run is currently active for the given issue.
|
|
25
|
+
* Returns false for untracked issues.
|
|
26
|
+
*/
|
|
27
|
+
export function isRunning(issue) {
|
|
28
|
+
return activeRuns.has(issue);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get info about all currently active runs.
|
|
32
|
+
*/
|
|
33
|
+
export function getActiveRuns() {
|
|
34
|
+
return activeRuns;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clear all active runs. Intended for testing only.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export function clearRegistry() {
|
|
41
|
+
activeRuns.clear();
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequant MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes Sequant workflow orchestration as an MCP server.
|
|
5
|
+
* Any MCP client (Claude Desktop, Cursor, VS Code, etc.) can invoke
|
|
6
|
+
* Sequant tools to drive structured AI workflows.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create and configure the Sequant MCP server instance.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createServer(version: string): McpServer;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequant MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes Sequant workflow orchestration as an MCP server.
|
|
5
|
+
* Any MCP client (Claude Desktop, Cursor, VS Code, etc.) can invoke
|
|
6
|
+
* Sequant tools to drive structured AI workflows.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { registerRunTool } from "./tools/run.js";
|
|
10
|
+
import { registerStatusTool } from "./tools/status.js";
|
|
11
|
+
import { registerLogsTool } from "./tools/logs.js";
|
|
12
|
+
import { registerResources } from "./resources.js";
|
|
13
|
+
/**
|
|
14
|
+
* Create and configure the Sequant MCP server instance.
|
|
15
|
+
*/
|
|
16
|
+
export function createServer(version) {
|
|
17
|
+
const options = {
|
|
18
|
+
instructions: [
|
|
19
|
+
"Sequant orchestrates AI-driven development workflows for GitHub issues.",
|
|
20
|
+
"Each issue progresses through phases: spec (plan) → exec (implement) → qa (review).",
|
|
21
|
+
"",
|
|
22
|
+
"Tools:",
|
|
23
|
+
"- sequant_status: Check issue progress. Poll every 5-10s during active runs. Always check before calling sequant_run.",
|
|
24
|
+
"- sequant_run: Execute workflow phases. Long-running (up to 30 min). Returns structured JSON on completion.",
|
|
25
|
+
"- sequant_logs: Review past run results and debug failures.",
|
|
26
|
+
"",
|
|
27
|
+
"Resources:",
|
|
28
|
+
"- sequant://state: Dashboard view of all tracked issues and their phases.",
|
|
29
|
+
"- sequant://config: Current workflow settings (timeout, phases, quality loop).",
|
|
30
|
+
"",
|
|
31
|
+
"Workflow: Check sequant_status first → sequant_run if needed → poll sequant_status → review sequant_logs on failure.",
|
|
32
|
+
"Do NOT call sequant_run for issues that are already merged or completed.",
|
|
33
|
+
].join("\n"),
|
|
34
|
+
capabilities: {
|
|
35
|
+
tools: {},
|
|
36
|
+
resources: {},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const server = new McpServer({
|
|
40
|
+
name: "sequant",
|
|
41
|
+
version,
|
|
42
|
+
}, options);
|
|
43
|
+
// Register tools
|
|
44
|
+
registerRunTool(server);
|
|
45
|
+
registerStatusTool(server);
|
|
46
|
+
registerLogsTool(server);
|
|
47
|
+
// Register resources
|
|
48
|
+
registerResources(server);
|
|
49
|
+
return server;
|
|
50
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequant_logs MCP tool
|
|
3
|
+
*
|
|
4
|
+
* Get run logs and metrics.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { LOG_PATHS, RunLogSchema } from "../../lib/workflow/run-log-schema.js";
|
|
11
|
+
function resolveLogPath() {
|
|
12
|
+
const projectPath = LOG_PATHS.project;
|
|
13
|
+
if (fs.existsSync(projectPath)) {
|
|
14
|
+
return projectPath;
|
|
15
|
+
}
|
|
16
|
+
const userPath = LOG_PATHS.user.replace("~", os.homedir());
|
|
17
|
+
if (fs.existsSync(userPath)) {
|
|
18
|
+
return userPath;
|
|
19
|
+
}
|
|
20
|
+
return projectPath;
|
|
21
|
+
}
|
|
22
|
+
function parseLogFile(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
25
|
+
const data = JSON.parse(content);
|
|
26
|
+
return RunLogSchema.parse(data);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function registerLogsTool(server) {
|
|
33
|
+
server.registerTool("sequant_logs", {
|
|
34
|
+
title: "Sequant Logs",
|
|
35
|
+
description: "Get structured run logs for recent workflow executions. " +
|
|
36
|
+
"Each log contains per-issue phase results (duration, status, errors) and QA verdicts. " +
|
|
37
|
+
"Use after a sequant_run completes or fails to understand what happened. " +
|
|
38
|
+
"Logs are stored as run-<ISO-timestamp>-<uuid>.json files.",
|
|
39
|
+
annotations: {
|
|
40
|
+
readOnlyHint: true,
|
|
41
|
+
idempotentHint: true,
|
|
42
|
+
},
|
|
43
|
+
inputSchema: {
|
|
44
|
+
runId: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Specific run ID prefix to filter by (e.g. 'run-2026-03-24'). " +
|
|
48
|
+
"Omit to return the most recent runs."),
|
|
49
|
+
limit: z
|
|
50
|
+
.number()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Number of recent runs to return (default: 5, max: all available)"),
|
|
53
|
+
},
|
|
54
|
+
}, async ({ runId, limit }) => {
|
|
55
|
+
try {
|
|
56
|
+
const logDir = resolveLogPath();
|
|
57
|
+
if (!fs.existsSync(logDir)) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: JSON.stringify({
|
|
63
|
+
logs: [],
|
|
64
|
+
message: "No log directory found. Run `sequant run` to generate logs.",
|
|
65
|
+
}),
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const logFiles = fs
|
|
71
|
+
.readdirSync(logDir)
|
|
72
|
+
.filter((f) => f.startsWith("run-") && f.endsWith(".json"))
|
|
73
|
+
.sort()
|
|
74
|
+
.reverse();
|
|
75
|
+
if (logFiles.length === 0) {
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: JSON.stringify({
|
|
81
|
+
logs: [],
|
|
82
|
+
message: "No logs found.",
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
let logs = [];
|
|
89
|
+
for (const filename of logFiles) {
|
|
90
|
+
const filePath = path.join(logDir, filename);
|
|
91
|
+
const log = parseLogFile(filePath);
|
|
92
|
+
if (log) {
|
|
93
|
+
// Filter by runId if specified
|
|
94
|
+
if (runId && !log.runId.startsWith(runId)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
logs.push(log);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Apply limit
|
|
101
|
+
const maxResults = limit && limit > 0 ? limit : 5;
|
|
102
|
+
logs = logs.slice(0, maxResults);
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: JSON.stringify({
|
|
108
|
+
count: logs.length,
|
|
109
|
+
logs: logs.map((log) => ({
|
|
110
|
+
runId: log.runId,
|
|
111
|
+
startTime: log.startTime,
|
|
112
|
+
config: log.config,
|
|
113
|
+
summary: log.summary,
|
|
114
|
+
issues: log.issues.map((issue) => ({
|
|
115
|
+
issueNumber: issue.issueNumber,
|
|
116
|
+
title: issue.title,
|
|
117
|
+
status: issue.status,
|
|
118
|
+
totalDurationSeconds: issue.totalDurationSeconds,
|
|
119
|
+
phases: issue.phases.map((p) => ({
|
|
120
|
+
phase: p.phase,
|
|
121
|
+
status: p.status,
|
|
122
|
+
durationSeconds: p.durationSeconds,
|
|
123
|
+
error: p.error,
|
|
124
|
+
...(p.verdict && { verdict: p.verdict }),
|
|
125
|
+
...(p.summary && { summary: p.summary }),
|
|
126
|
+
})),
|
|
127
|
+
})),
|
|
128
|
+
})),
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: JSON.stringify({
|
|
140
|
+
error: "LOGS_ERROR",
|
|
141
|
+
message: error instanceof Error ? error.message : String(error),
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequant_run MCP tool
|
|
3
|
+
*
|
|
4
|
+
* Execute workflow phases for GitHub issues.
|
|
5
|
+
* Returns structured JSON with per-issue summaries parsed from run logs.
|
|
6
|
+
* Uses async spawn to keep the MCP server responsive during execution.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import type { RunLog } from "../../lib/workflow/run-log-schema.js";
|
|
10
|
+
/**
|
|
11
|
+
* Per-issue summary in the structured response
|
|
12
|
+
*/
|
|
13
|
+
interface RunToolIssueSummary {
|
|
14
|
+
issueNumber: number;
|
|
15
|
+
status: "success" | "failure" | "partial";
|
|
16
|
+
phases: Array<{
|
|
17
|
+
phase: string;
|
|
18
|
+
status: string;
|
|
19
|
+
durationSeconds: number;
|
|
20
|
+
}>;
|
|
21
|
+
verdict?: string;
|
|
22
|
+
durationSeconds: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Structured response from sequant_run
|
|
26
|
+
*/
|
|
27
|
+
interface RunToolResponse {
|
|
28
|
+
status: "success" | "failure";
|
|
29
|
+
exitCode?: number;
|
|
30
|
+
issues: RunToolIssueSummary[];
|
|
31
|
+
summary: {
|
|
32
|
+
total: number;
|
|
33
|
+
passed: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
durationSeconds: number;
|
|
36
|
+
};
|
|
37
|
+
phases: string;
|
|
38
|
+
rawOutput?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the CLI binary path to avoid nested npx version mismatches (#389).
|
|
43
|
+
*
|
|
44
|
+
* Priority:
|
|
45
|
+
* 1. process.argv[1] — the script currently running (works for npx, global, local node)
|
|
46
|
+
* 2. __dirname-relative resolution — fallback for bundled/compiled entry points
|
|
47
|
+
* 3. "npx" + "sequant" — last resort if nothing else resolves
|
|
48
|
+
*
|
|
49
|
+
* Returns [command, prefixArgs] where the full invocation is:
|
|
50
|
+
* spawnAsync(command, [...prefixArgs, "run", ...userArgs])
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveCliBinary(): [string, string[]];
|
|
53
|
+
/**
|
|
54
|
+
* Find and parse the most recent run log file.
|
|
55
|
+
*
|
|
56
|
+
* When runStartTime is provided, only log files created within
|
|
57
|
+
* MAX_LOG_AGE_MS of that timestamp are considered, preventing
|
|
58
|
+
* stale logs from a previous run being returned.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readLatestRunLog(runStartTime?: Date): Promise<RunLog | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Build a structured response from a parsed RunLog
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildStructuredResponse(runLog: RunLog, rawOutput: string, overallStatus: "success" | "failure", exitCode?: number | null, errorOutput?: string): RunToolResponse;
|
|
65
|
+
/** Parsed progress event from a SEQUANT_PROGRESS line. */
|
|
66
|
+
export interface ProgressEvent {
|
|
67
|
+
issue: number;
|
|
68
|
+
phase: string;
|
|
69
|
+
event: "start" | "complete" | "failed";
|
|
70
|
+
durationSeconds?: number;
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse a SEQUANT_PROGRESS line emitted by the batch executor.
|
|
75
|
+
* Returns the parsed event or null if the line isn't a progress line.
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseProgressLine(line: string): ProgressEvent | null;
|
|
78
|
+
/**
|
|
79
|
+
* Build a human-readable message for a progress notification (AC-3).
|
|
80
|
+
* @internal Exported for testing only.
|
|
81
|
+
*/
|
|
82
|
+
export declare function formatProgressMessage(event: ProgressEvent): string;
|
|
83
|
+
/**
|
|
84
|
+
* Create a line buffer that accumulates stream chunks and yields complete lines.
|
|
85
|
+
* Handles the case where a single `data` event spans partial lines.
|
|
86
|
+
*/
|
|
87
|
+
export declare function createLineBuffer(onLine: (line: string) => void): (chunk: string) => void;
|
|
88
|
+
export declare function registerRunTool(server: McpServer): void;
|
|
89
|
+
export interface SpawnResult {
|
|
90
|
+
exitCode: number | null;
|
|
91
|
+
stdout: string;
|
|
92
|
+
stderr: string;
|
|
93
|
+
}
|
|
94
|
+
/** Per-phase timeout ceiling (30 minutes) */
|
|
95
|
+
export declare const PHASE_TIMEOUT = 1800000;
|
|
96
|
+
/** Absolute maximum run duration (2 hours), even with progress resets */
|
|
97
|
+
export declare const MAX_TOTAL_TIMEOUT = 7200000;
|
|
98
|
+
export interface SpawnOptions {
|
|
99
|
+
timeout: number;
|
|
100
|
+
env?: NodeJS.ProcessEnv;
|
|
101
|
+
signal?: AbortSignal;
|
|
102
|
+
/** Called with each stderr chunk (real-time, before process exits) */
|
|
103
|
+
onStderr?: (chunk: string) => void;
|
|
104
|
+
/** Called with each stdout chunk (real-time, before process exits) */
|
|
105
|
+
onStdout?: (chunk: string) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Called when a progress event is detected from stderr.
|
|
108
|
+
* When set, enables resettable timeout: the per-phase timeout resets on
|
|
109
|
+
* each SEQUANT_PROGRESS line, but the absolute ceiling still applies.
|
|
110
|
+
*/
|
|
111
|
+
onProgress?: () => void;
|
|
112
|
+
/**
|
|
113
|
+
* Override the absolute timeout ceiling (defaults to MAX_TOTAL_TIMEOUT).
|
|
114
|
+
* Only applies when onProgress is set. Useful for testing.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
maxTotalTimeout?: number;
|
|
118
|
+
}
|
|
119
|
+
/** @internal Exported for testing only */
|
|
120
|
+
export declare function spawnAsync(command: string, args: string[], options: SpawnOptions): Promise<SpawnResult>;
|
|
121
|
+
export {};
|