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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciliation engine for sequant status
|
|
3
|
+
*
|
|
4
|
+
* Reconciles local state.json with GitHub API and filesystem
|
|
5
|
+
* to provide accurate, up-to-date status information.
|
|
6
|
+
*
|
|
7
|
+
* @module reconcile
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import { StateManager } from "./state-manager.js";
|
|
11
|
+
import { GitHubProvider, } from "./platforms/github.js";
|
|
12
|
+
/**
|
|
13
|
+
* Classify drift for a single issue based on GitHub and filesystem state.
|
|
14
|
+
*/
|
|
15
|
+
export function classifyDrift(issue, githubIssue, githubPR, worktreeExists) {
|
|
16
|
+
const num = issue.number;
|
|
17
|
+
// Check PR merge → unambiguous
|
|
18
|
+
if (githubPR?.state === "MERGED" && issue.status !== "merged") {
|
|
19
|
+
return {
|
|
20
|
+
issueNumber: num,
|
|
21
|
+
type: "unambiguous",
|
|
22
|
+
action: "update_to_merged",
|
|
23
|
+
description: `PR #${githubPR.number} merged on GitHub`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// Check issue closed + no merged PR → unambiguous abandoned
|
|
27
|
+
if (githubIssue?.state === "CLOSED" &&
|
|
28
|
+
issue.status !== "merged" &&
|
|
29
|
+
issue.status !== "abandoned" &&
|
|
30
|
+
githubPR?.state !== "MERGED") {
|
|
31
|
+
return {
|
|
32
|
+
issueNumber: num,
|
|
33
|
+
type: "unambiguous",
|
|
34
|
+
action: "update_to_abandoned",
|
|
35
|
+
description: `Issue #${num} closed on GitHub without merged PR`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Check worktree missing
|
|
39
|
+
if (issue.worktree && worktreeExists === false) {
|
|
40
|
+
if (!issue.pr?.number) {
|
|
41
|
+
if (githubIssue?.state === "OPEN") {
|
|
42
|
+
// Confirmed open on GitHub, no PR → ambiguous
|
|
43
|
+
return {
|
|
44
|
+
issueNumber: num,
|
|
45
|
+
type: "ambiguous",
|
|
46
|
+
action: "flag_missing_worktree",
|
|
47
|
+
description: `Worktree deleted but issue #${num} still open on GitHub with no PR`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!githubIssue) {
|
|
51
|
+
// GitHub data unavailable → ambiguous (can't determine if safe to clear)
|
|
52
|
+
return {
|
|
53
|
+
issueNumber: num,
|
|
54
|
+
type: "ambiguous",
|
|
55
|
+
action: "flag_missing_worktree",
|
|
56
|
+
description: `Worktree deleted for issue #${num} but GitHub state unknown (API unreachable)`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Otherwise just clear worktree (unambiguous)
|
|
61
|
+
return {
|
|
62
|
+
issueNumber: num,
|
|
63
|
+
type: "unambiguous",
|
|
64
|
+
action: "clear_worktree",
|
|
65
|
+
description: `Worktree path no longer exists for issue #${num}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Check local abandoned but GitHub open → ambiguous
|
|
69
|
+
if (issue.status === "abandoned" && githubIssue?.state === "OPEN") {
|
|
70
|
+
return {
|
|
71
|
+
issueNumber: num,
|
|
72
|
+
type: "ambiguous",
|
|
73
|
+
action: "flag_status_mismatch",
|
|
74
|
+
description: `Issue #${num} marked abandoned locally but still open on GitHub`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a next-action hint for an issue based on its current state.
|
|
81
|
+
*/
|
|
82
|
+
export function getNextActionHint(issue) {
|
|
83
|
+
switch (issue.status) {
|
|
84
|
+
case "not_started":
|
|
85
|
+
return `sequant run ${issue.number}`;
|
|
86
|
+
case "in_progress": {
|
|
87
|
+
// Suggest resuming at current phase or next phase
|
|
88
|
+
if (issue.currentPhase) {
|
|
89
|
+
const failedPhase = Object.entries(issue.phases).find(([, ps]) => ps.status === "failed");
|
|
90
|
+
if (failedPhase) {
|
|
91
|
+
return `sequant run ${issue.number} --phase ${failedPhase[0]}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return `sequant run ${issue.number}`;
|
|
95
|
+
}
|
|
96
|
+
case "waiting_for_qa_gate":
|
|
97
|
+
return `sequant run ${issue.number} --phase qa`;
|
|
98
|
+
case "ready_for_merge":
|
|
99
|
+
if (issue.pr?.number) {
|
|
100
|
+
return `gh pr merge ${issue.pr.number}`;
|
|
101
|
+
}
|
|
102
|
+
return `gh pr merge`;
|
|
103
|
+
case "blocked":
|
|
104
|
+
return `resolve blockers, then sequant run ${issue.number}`;
|
|
105
|
+
case "merged":
|
|
106
|
+
return `sequant status --cleanup`;
|
|
107
|
+
case "abandoned":
|
|
108
|
+
return `reopen issue or sequant status --cleanup`;
|
|
109
|
+
default:
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Format a relative time string from an ISO timestamp.
|
|
115
|
+
*/
|
|
116
|
+
export function formatRelativeTime(isoTimestamp) {
|
|
117
|
+
if (!isoTimestamp)
|
|
118
|
+
return "unknown";
|
|
119
|
+
const date = new Date(isoTimestamp);
|
|
120
|
+
if (isNaN(date.getTime()))
|
|
121
|
+
return "unknown";
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const diffMs = now.getTime() - date.getTime();
|
|
124
|
+
// Handle future timestamps (clock skew)
|
|
125
|
+
if (diffMs < 0)
|
|
126
|
+
return "just now";
|
|
127
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
128
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
129
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
130
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
131
|
+
if (diffSec < 60)
|
|
132
|
+
return "just now";
|
|
133
|
+
if (diffMin < 60)
|
|
134
|
+
return `${diffMin} minute${diffMin > 1 ? "s" : ""} ago`;
|
|
135
|
+
if (diffHour < 24)
|
|
136
|
+
return `${diffHour} hour${diffHour > 1 ? "s" : ""} ago`;
|
|
137
|
+
if (diffDay < 7)
|
|
138
|
+
return `${diffDay} day${diffDay > 1 ? "s" : ""} ago`;
|
|
139
|
+
return date.toLocaleDateString();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reconcile local workflow state with GitHub and filesystem.
|
|
143
|
+
*
|
|
144
|
+
* This is the main entry point for reconciliation. Called by:
|
|
145
|
+
* - `sequant status` (CLI)
|
|
146
|
+
* - `sequant_status` (MCP tool)
|
|
147
|
+
*/
|
|
148
|
+
export async function reconcileState(options = {}) {
|
|
149
|
+
const stateManager = options.stateManager ??
|
|
150
|
+
new StateManager({
|
|
151
|
+
// Increase lock timeout for reconcile — GitHub API calls may be slow
|
|
152
|
+
lockTimeout: 30_000,
|
|
153
|
+
});
|
|
154
|
+
const now = new Date().toISOString();
|
|
155
|
+
if (!stateManager.stateExists()) {
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
healed: [],
|
|
159
|
+
warnings: [],
|
|
160
|
+
lastSynced: now,
|
|
161
|
+
githubReachable: !options.offline,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
// Wrap the entire read-modify-write cycle (including GitHub API calls)
|
|
166
|
+
// in a single lock to prevent concurrent updatePhaseStatus from
|
|
167
|
+
// interleaving and causing state regression. (#458 AC-4)
|
|
168
|
+
return await stateManager.withLock(async () => {
|
|
169
|
+
const state = await stateManager.getState();
|
|
170
|
+
const issues = Object.values(state.issues);
|
|
171
|
+
if (issues.length === 0) {
|
|
172
|
+
state.lastSynced = now;
|
|
173
|
+
await stateManager.saveState(state);
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
healed: [],
|
|
177
|
+
warnings: [],
|
|
178
|
+
lastSynced: now,
|
|
179
|
+
githubReachable: !options.offline,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Collect issue and PR numbers to query
|
|
183
|
+
const issueNumbers = issues.map((i) => i.number);
|
|
184
|
+
const prNumbers = issues
|
|
185
|
+
.filter((i) => i.pr?.number)
|
|
186
|
+
.map((i) => i.pr.number);
|
|
187
|
+
// Batch fetch from GitHub (unless offline)
|
|
188
|
+
let githubIssues = {};
|
|
189
|
+
let githubPRs = {};
|
|
190
|
+
let githubReachable = false;
|
|
191
|
+
if (!options.offline) {
|
|
192
|
+
const github = new GitHubProvider();
|
|
193
|
+
const batchResult = github.batchFetchIssueAndPRStatus(issueNumbers, prNumbers);
|
|
194
|
+
if (!batchResult.error) {
|
|
195
|
+
githubIssues = batchResult.issues;
|
|
196
|
+
githubPRs = batchResult.pullRequests;
|
|
197
|
+
githubReachable = true;
|
|
198
|
+
}
|
|
199
|
+
// On error: graceful degradation — proceed with cached data
|
|
200
|
+
}
|
|
201
|
+
// Check filesystem for worktrees
|
|
202
|
+
const worktreeExists = {};
|
|
203
|
+
for (const issue of issues) {
|
|
204
|
+
if (issue.worktree) {
|
|
205
|
+
worktreeExists[issue.number] = fs.existsSync(issue.worktree);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Classify and apply drift
|
|
209
|
+
const healed = [];
|
|
210
|
+
const warnings = [];
|
|
211
|
+
let stateModified = false;
|
|
212
|
+
for (const issue of issues) {
|
|
213
|
+
const issueKey = String(issue.number);
|
|
214
|
+
const ghIssue = githubIssues[issue.number];
|
|
215
|
+
const ghPR = issue.pr?.number ? githubPRs[issue.pr.number] : undefined;
|
|
216
|
+
const wtExists = issue.worktree
|
|
217
|
+
? worktreeExists[issue.number]
|
|
218
|
+
: undefined;
|
|
219
|
+
// Update title from GitHub if available
|
|
220
|
+
if (ghIssue?.title && ghIssue.title !== issue.title) {
|
|
221
|
+
state.issues[issueKey].title = ghIssue.title;
|
|
222
|
+
stateModified = true;
|
|
223
|
+
}
|
|
224
|
+
const drift = classifyDrift(issue, ghIssue, ghPR, wtExists);
|
|
225
|
+
// Always clear missing worktrees independently of other drift
|
|
226
|
+
if (issue.worktree &&
|
|
227
|
+
wtExists === false &&
|
|
228
|
+
drift?.type !== "ambiguous") {
|
|
229
|
+
state.issues[issueKey].worktree = undefined;
|
|
230
|
+
stateModified = true;
|
|
231
|
+
}
|
|
232
|
+
if (!drift)
|
|
233
|
+
continue;
|
|
234
|
+
if (drift.type === "unambiguous") {
|
|
235
|
+
// Auto-heal
|
|
236
|
+
switch (drift.action) {
|
|
237
|
+
case "update_to_merged":
|
|
238
|
+
state.issues[issueKey].status = "merged";
|
|
239
|
+
state.issues[issueKey].lastActivity = now;
|
|
240
|
+
if (!state.issues[issueKey].resolvedAt) {
|
|
241
|
+
state.issues[issueKey].resolvedAt = now;
|
|
242
|
+
}
|
|
243
|
+
stateModified = true;
|
|
244
|
+
break;
|
|
245
|
+
case "update_to_abandoned":
|
|
246
|
+
state.issues[issueKey].status = "abandoned";
|
|
247
|
+
state.issues[issueKey].lastActivity = now;
|
|
248
|
+
if (!state.issues[issueKey].resolvedAt) {
|
|
249
|
+
state.issues[issueKey].resolvedAt = now;
|
|
250
|
+
}
|
|
251
|
+
stateModified = true;
|
|
252
|
+
break;
|
|
253
|
+
case "clear_worktree":
|
|
254
|
+
state.issues[issueKey].worktree = undefined;
|
|
255
|
+
state.issues[issueKey].lastActivity = now;
|
|
256
|
+
stateModified = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
healed.push(drift);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
// Flag to user
|
|
263
|
+
warnings.push(drift);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Persist if changed
|
|
267
|
+
if (stateModified || githubReachable) {
|
|
268
|
+
state.lastSynced = now;
|
|
269
|
+
await stateManager.saveState(state);
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
healed,
|
|
274
|
+
warnings,
|
|
275
|
+
lastSynced: now,
|
|
276
|
+
githubReachable,
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
healed: [],
|
|
284
|
+
warnings: [],
|
|
285
|
+
lastSynced: now,
|
|
286
|
+
githubReachable: false,
|
|
287
|
+
error: error instanceof Error ? error.message : String(error),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RingBuffer — fixed-capacity circular buffer for retaining the last N lines.
|
|
3
|
+
*
|
|
4
|
+
* Used by agent drivers to capture the tail of stderr/stdout without
|
|
5
|
+
* unbounded memory growth.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RingBuffer {
|
|
8
|
+
private buffer;
|
|
9
|
+
private head;
|
|
10
|
+
private count;
|
|
11
|
+
private readonly capacity;
|
|
12
|
+
constructor(capacity: number);
|
|
13
|
+
/** Push a line into the buffer, evicting the oldest if at capacity. */
|
|
14
|
+
push(line: string): void;
|
|
15
|
+
/** Return the stored lines in insertion order (oldest first). */
|
|
16
|
+
getLines(): string[];
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RingBuffer — fixed-capacity circular buffer for retaining the last N lines.
|
|
3
|
+
*
|
|
4
|
+
* Used by agent drivers to capture the tail of stderr/stdout without
|
|
5
|
+
* unbounded memory growth.
|
|
6
|
+
*/
|
|
7
|
+
export class RingBuffer {
|
|
8
|
+
buffer;
|
|
9
|
+
head = 0;
|
|
10
|
+
count = 0;
|
|
11
|
+
capacity;
|
|
12
|
+
constructor(capacity) {
|
|
13
|
+
this.capacity = capacity;
|
|
14
|
+
this.buffer = new Array(capacity);
|
|
15
|
+
}
|
|
16
|
+
/** Push a line into the buffer, evicting the oldest if at capacity. */
|
|
17
|
+
push(line) {
|
|
18
|
+
this.buffer[this.head] = line;
|
|
19
|
+
this.head = (this.head + 1) % this.capacity;
|
|
20
|
+
if (this.count < this.capacity) {
|
|
21
|
+
this.count++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Return the stored lines in insertion order (oldest first). */
|
|
25
|
+
getLines() {
|
|
26
|
+
if (this.count === 0)
|
|
27
|
+
return [];
|
|
28
|
+
if (this.count < this.capacity) {
|
|
29
|
+
return this.buffer.slice(0, this.count);
|
|
30
|
+
}
|
|
31
|
+
// Buffer is full — head points to the oldest entry
|
|
32
|
+
return [
|
|
33
|
+
...this.buffer.slice(this.head),
|
|
34
|
+
...this.buffer.slice(0, this.head),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -16,19 +16,9 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
import { z } from "zod";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export declare const PhaseSchema: z.ZodEnum<{
|
|
23
|
-
loop: "loop";
|
|
24
|
-
spec: "spec";
|
|
25
|
-
exec: "exec";
|
|
26
|
-
qa: "qa";
|
|
27
|
-
"security-review": "security-review";
|
|
28
|
-
testgen: "testgen";
|
|
29
|
-
test: "test";
|
|
30
|
-
}>;
|
|
31
|
-
export type Phase = z.infer<typeof PhaseSchema>;
|
|
19
|
+
import { PhaseSchema, type Phase } from "./types.js";
|
|
20
|
+
export { PhaseSchema };
|
|
21
|
+
export type { Phase } from "./types.js";
|
|
32
22
|
/**
|
|
33
23
|
* Phase execution status
|
|
34
24
|
*/
|
|
@@ -82,18 +72,54 @@ export declare const CacheMetricsSchema: z.ZodObject<{
|
|
|
82
72
|
skipped: z.ZodNumber;
|
|
83
73
|
}, z.core.$strip>;
|
|
84
74
|
export type CacheMetrics = z.infer<typeof CacheMetricsSchema>;
|
|
75
|
+
/**
|
|
76
|
+
* Structured error context captured from phase failures (#447).
|
|
77
|
+
*
|
|
78
|
+
* Provides stderr/stdout tails and a categorized error type
|
|
79
|
+
* for better failure diagnostics and analytics.
|
|
80
|
+
*/
|
|
81
|
+
export declare const ErrorContextSchema: z.ZodObject<{
|
|
82
|
+
stderrTail: z.ZodArray<z.ZodString>;
|
|
83
|
+
stdoutTail: z.ZodArray<z.ZodString>;
|
|
84
|
+
exitCode: z.ZodOptional<z.ZodNumber>;
|
|
85
|
+
category: z.ZodEnum<{
|
|
86
|
+
unknown: "unknown";
|
|
87
|
+
timeout: "timeout";
|
|
88
|
+
context_overflow: "context_overflow";
|
|
89
|
+
api_error: "api_error";
|
|
90
|
+
hook_failure: "hook_failure";
|
|
91
|
+
build_error: "build_error";
|
|
92
|
+
}>;
|
|
93
|
+
}, z.core.$strip>;
|
|
94
|
+
export type ErrorContext = z.infer<typeof ErrorContextSchema>;
|
|
95
|
+
/**
|
|
96
|
+
* Condensed QA verdict summary for structured log output (#434).
|
|
97
|
+
*
|
|
98
|
+
* Provides AC coverage counts, gaps, and suggestions so that
|
|
99
|
+
* `sequant_logs` consumers can review QA results without
|
|
100
|
+
* fetching issue comments separately.
|
|
101
|
+
*/
|
|
102
|
+
export declare const QaSummarySchema: z.ZodObject<{
|
|
103
|
+
acMet: z.ZodNumber;
|
|
104
|
+
acTotal: z.ZodNumber;
|
|
105
|
+
gaps: z.ZodArray<z.ZodString>;
|
|
106
|
+
suggestions: z.ZodArray<z.ZodString>;
|
|
107
|
+
}, z.core.$strip>;
|
|
108
|
+
export type QaSummary = z.infer<typeof QaSummarySchema>;
|
|
85
109
|
/**
|
|
86
110
|
* Log entry for a single phase execution
|
|
87
111
|
*/
|
|
88
112
|
export declare const PhaseLogSchema: z.ZodObject<{
|
|
89
113
|
phase: z.ZodEnum<{
|
|
114
|
+
qa: "qa";
|
|
90
115
|
loop: "loop";
|
|
116
|
+
verify: "verify";
|
|
91
117
|
spec: "spec";
|
|
92
|
-
exec: "exec";
|
|
93
|
-
qa: "qa";
|
|
94
118
|
"security-review": "security-review";
|
|
119
|
+
exec: "exec";
|
|
95
120
|
testgen: "testgen";
|
|
96
121
|
test: "test";
|
|
122
|
+
merger: "merger";
|
|
97
123
|
}>;
|
|
98
124
|
issueNumber: z.ZodNumber;
|
|
99
125
|
startTime: z.ZodString;
|
|
@@ -116,6 +142,12 @@ export declare const PhaseLogSchema: z.ZodObject<{
|
|
|
116
142
|
AC_NOT_MET: "AC_NOT_MET";
|
|
117
143
|
NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
|
|
118
144
|
}>>;
|
|
145
|
+
summary: z.ZodOptional<z.ZodObject<{
|
|
146
|
+
acMet: z.ZodNumber;
|
|
147
|
+
acTotal: z.ZodNumber;
|
|
148
|
+
gaps: z.ZodArray<z.ZodString>;
|
|
149
|
+
suggestions: z.ZodArray<z.ZodString>;
|
|
150
|
+
}, z.core.$strip>>;
|
|
119
151
|
commitHash: z.ZodOptional<z.ZodString>;
|
|
120
152
|
fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
121
153
|
path: z.ZodString;
|
|
@@ -133,6 +165,19 @@ export declare const PhaseLogSchema: z.ZodObject<{
|
|
|
133
165
|
misses: z.ZodNumber;
|
|
134
166
|
skipped: z.ZodNumber;
|
|
135
167
|
}, z.core.$strip>>;
|
|
168
|
+
errorContext: z.ZodOptional<z.ZodObject<{
|
|
169
|
+
stderrTail: z.ZodArray<z.ZodString>;
|
|
170
|
+
stdoutTail: z.ZodArray<z.ZodString>;
|
|
171
|
+
exitCode: z.ZodOptional<z.ZodNumber>;
|
|
172
|
+
category: z.ZodEnum<{
|
|
173
|
+
unknown: "unknown";
|
|
174
|
+
timeout: "timeout";
|
|
175
|
+
context_overflow: "context_overflow";
|
|
176
|
+
api_error: "api_error";
|
|
177
|
+
hook_failure: "hook_failure";
|
|
178
|
+
build_error: "build_error";
|
|
179
|
+
}>;
|
|
180
|
+
}, z.core.$strip>>;
|
|
136
181
|
}, z.core.$strip>;
|
|
137
182
|
export type PhaseLog = z.infer<typeof PhaseLogSchema>;
|
|
138
183
|
/**
|
|
@@ -149,13 +194,15 @@ export declare const IssueLogSchema: z.ZodObject<{
|
|
|
149
194
|
}>;
|
|
150
195
|
phases: z.ZodArray<z.ZodObject<{
|
|
151
196
|
phase: z.ZodEnum<{
|
|
197
|
+
qa: "qa";
|
|
152
198
|
loop: "loop";
|
|
199
|
+
verify: "verify";
|
|
153
200
|
spec: "spec";
|
|
154
|
-
exec: "exec";
|
|
155
|
-
qa: "qa";
|
|
156
201
|
"security-review": "security-review";
|
|
202
|
+
exec: "exec";
|
|
157
203
|
testgen: "testgen";
|
|
158
204
|
test: "test";
|
|
205
|
+
merger: "merger";
|
|
159
206
|
}>;
|
|
160
207
|
issueNumber: z.ZodNumber;
|
|
161
208
|
startTime: z.ZodString;
|
|
@@ -178,6 +225,12 @@ export declare const IssueLogSchema: z.ZodObject<{
|
|
|
178
225
|
AC_NOT_MET: "AC_NOT_MET";
|
|
179
226
|
NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
|
|
180
227
|
}>>;
|
|
228
|
+
summary: z.ZodOptional<z.ZodObject<{
|
|
229
|
+
acMet: z.ZodNumber;
|
|
230
|
+
acTotal: z.ZodNumber;
|
|
231
|
+
gaps: z.ZodArray<z.ZodString>;
|
|
232
|
+
suggestions: z.ZodArray<z.ZodString>;
|
|
233
|
+
}, z.core.$strip>>;
|
|
181
234
|
commitHash: z.ZodOptional<z.ZodString>;
|
|
182
235
|
fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
183
236
|
path: z.ZodString;
|
|
@@ -195,6 +248,19 @@ export declare const IssueLogSchema: z.ZodObject<{
|
|
|
195
248
|
misses: z.ZodNumber;
|
|
196
249
|
skipped: z.ZodNumber;
|
|
197
250
|
}, z.core.$strip>>;
|
|
251
|
+
errorContext: z.ZodOptional<z.ZodObject<{
|
|
252
|
+
stderrTail: z.ZodArray<z.ZodString>;
|
|
253
|
+
stdoutTail: z.ZodArray<z.ZodString>;
|
|
254
|
+
exitCode: z.ZodOptional<z.ZodNumber>;
|
|
255
|
+
category: z.ZodEnum<{
|
|
256
|
+
unknown: "unknown";
|
|
257
|
+
timeout: "timeout";
|
|
258
|
+
context_overflow: "context_overflow";
|
|
259
|
+
api_error: "api_error";
|
|
260
|
+
hook_failure: "hook_failure";
|
|
261
|
+
build_error: "build_error";
|
|
262
|
+
}>;
|
|
263
|
+
}, z.core.$strip>>;
|
|
198
264
|
}, z.core.$strip>>;
|
|
199
265
|
totalDurationSeconds: z.ZodNumber;
|
|
200
266
|
prNumber: z.ZodOptional<z.ZodNumber>;
|
|
@@ -206,13 +272,15 @@ export type IssueLog = z.infer<typeof IssueLogSchema>;
|
|
|
206
272
|
*/
|
|
207
273
|
export declare const RunConfigSchema: z.ZodObject<{
|
|
208
274
|
phases: z.ZodArray<z.ZodEnum<{
|
|
275
|
+
qa: "qa";
|
|
209
276
|
loop: "loop";
|
|
277
|
+
verify: "verify";
|
|
210
278
|
spec: "spec";
|
|
211
|
-
exec: "exec";
|
|
212
|
-
qa: "qa";
|
|
213
279
|
"security-review": "security-review";
|
|
280
|
+
exec: "exec";
|
|
214
281
|
testgen: "testgen";
|
|
215
282
|
test: "test";
|
|
283
|
+
merger: "merger";
|
|
216
284
|
}>>;
|
|
217
285
|
sequential: z.ZodBoolean;
|
|
218
286
|
qualityLoop: z.ZodBoolean;
|
|
@@ -243,13 +311,15 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
243
311
|
endTime: z.ZodString;
|
|
244
312
|
config: z.ZodObject<{
|
|
245
313
|
phases: z.ZodArray<z.ZodEnum<{
|
|
314
|
+
qa: "qa";
|
|
246
315
|
loop: "loop";
|
|
316
|
+
verify: "verify";
|
|
247
317
|
spec: "spec";
|
|
248
|
-
exec: "exec";
|
|
249
|
-
qa: "qa";
|
|
250
318
|
"security-review": "security-review";
|
|
319
|
+
exec: "exec";
|
|
251
320
|
testgen: "testgen";
|
|
252
321
|
test: "test";
|
|
322
|
+
merger: "merger";
|
|
253
323
|
}>>;
|
|
254
324
|
sequential: z.ZodBoolean;
|
|
255
325
|
qualityLoop: z.ZodBoolean;
|
|
@@ -268,13 +338,15 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
268
338
|
}>;
|
|
269
339
|
phases: z.ZodArray<z.ZodObject<{
|
|
270
340
|
phase: z.ZodEnum<{
|
|
341
|
+
qa: "qa";
|
|
271
342
|
loop: "loop";
|
|
343
|
+
verify: "verify";
|
|
272
344
|
spec: "spec";
|
|
273
|
-
exec: "exec";
|
|
274
|
-
qa: "qa";
|
|
275
345
|
"security-review": "security-review";
|
|
346
|
+
exec: "exec";
|
|
276
347
|
testgen: "testgen";
|
|
277
348
|
test: "test";
|
|
349
|
+
merger: "merger";
|
|
278
350
|
}>;
|
|
279
351
|
issueNumber: z.ZodNumber;
|
|
280
352
|
startTime: z.ZodString;
|
|
@@ -297,6 +369,12 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
297
369
|
AC_NOT_MET: "AC_NOT_MET";
|
|
298
370
|
NEEDS_VERIFICATION: "NEEDS_VERIFICATION";
|
|
299
371
|
}>>;
|
|
372
|
+
summary: z.ZodOptional<z.ZodObject<{
|
|
373
|
+
acMet: z.ZodNumber;
|
|
374
|
+
acTotal: z.ZodNumber;
|
|
375
|
+
gaps: z.ZodArray<z.ZodString>;
|
|
376
|
+
suggestions: z.ZodArray<z.ZodString>;
|
|
377
|
+
}, z.core.$strip>>;
|
|
300
378
|
commitHash: z.ZodOptional<z.ZodString>;
|
|
301
379
|
fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
302
380
|
path: z.ZodString;
|
|
@@ -314,6 +392,19 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
314
392
|
misses: z.ZodNumber;
|
|
315
393
|
skipped: z.ZodNumber;
|
|
316
394
|
}, z.core.$strip>>;
|
|
395
|
+
errorContext: z.ZodOptional<z.ZodObject<{
|
|
396
|
+
stderrTail: z.ZodArray<z.ZodString>;
|
|
397
|
+
stdoutTail: z.ZodArray<z.ZodString>;
|
|
398
|
+
exitCode: z.ZodOptional<z.ZodNumber>;
|
|
399
|
+
category: z.ZodEnum<{
|
|
400
|
+
unknown: "unknown";
|
|
401
|
+
timeout: "timeout";
|
|
402
|
+
context_overflow: "context_overflow";
|
|
403
|
+
api_error: "api_error";
|
|
404
|
+
hook_failure: "hook_failure";
|
|
405
|
+
build_error: "build_error";
|
|
406
|
+
}>;
|
|
407
|
+
}, z.core.$strip>>;
|
|
317
408
|
}, z.core.$strip>>;
|
|
318
409
|
totalDurationSeconds: z.ZodNumber;
|
|
319
410
|
prNumber: z.ZodOptional<z.ZodNumber>;
|
|
@@ -372,7 +463,7 @@ export declare function createPhaseLog(phase: Phase, issueNumber: number): Omit<
|
|
|
372
463
|
* @param options - Additional fields (error, filesModified, verdict, etc.)
|
|
373
464
|
* @returns Complete PhaseLog
|
|
374
465
|
*/
|
|
375
|
-
export declare function completePhaseLog(phaseLog: Omit<PhaseLog, "endTime" | "durationSeconds" | "status">, status: PhaseStatus, options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "commitHash" | "fileDiffStats" | "cacheMetrics">>): PhaseLog;
|
|
466
|
+
export declare function completePhaseLog(phaseLog: Omit<PhaseLog, "endTime" | "durationSeconds" | "status">, status: PhaseStatus, options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "summary" | "commitHash" | "fileDiffStats" | "cacheMetrics" | "errorContext">>): PhaseLog;
|
|
376
467
|
/**
|
|
377
468
|
* Finalize a run log with summary statistics
|
|
378
469
|
*
|
|
@@ -17,18 +17,9 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import { z } from "zod";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export const PhaseSchema = z.enum([
|
|
24
|
-
"spec",
|
|
25
|
-
"security-review",
|
|
26
|
-
"testgen",
|
|
27
|
-
"exec",
|
|
28
|
-
"test",
|
|
29
|
-
"qa",
|
|
30
|
-
"loop",
|
|
31
|
-
]);
|
|
20
|
+
// Import canonical Phase types from types.ts (single source of truth)
|
|
21
|
+
import { PhaseSchema } from "./types.js";
|
|
22
|
+
export { PhaseSchema };
|
|
32
23
|
/**
|
|
33
24
|
* Phase execution status
|
|
34
25
|
*/
|
|
@@ -75,6 +66,46 @@ export const CacheMetricsSchema = z.object({
|
|
|
75
66
|
/** Number of skipped checks */
|
|
76
67
|
skipped: z.number().int().nonnegative(),
|
|
77
68
|
});
|
|
69
|
+
/**
|
|
70
|
+
* Structured error context captured from phase failures (#447).
|
|
71
|
+
*
|
|
72
|
+
* Provides stderr/stdout tails and a categorized error type
|
|
73
|
+
* for better failure diagnostics and analytics.
|
|
74
|
+
*/
|
|
75
|
+
export const ErrorContextSchema = z.object({
|
|
76
|
+
/** Last N lines of stderr before process exit */
|
|
77
|
+
stderrTail: z.array(z.string()),
|
|
78
|
+
/** Last N lines of stdout before process exit */
|
|
79
|
+
stdoutTail: z.array(z.string()),
|
|
80
|
+
/** Process exit code */
|
|
81
|
+
exitCode: z.number().int().optional(),
|
|
82
|
+
/** Classified error category */
|
|
83
|
+
category: z.enum([
|
|
84
|
+
"context_overflow",
|
|
85
|
+
"api_error",
|
|
86
|
+
"hook_failure",
|
|
87
|
+
"build_error",
|
|
88
|
+
"timeout",
|
|
89
|
+
"unknown",
|
|
90
|
+
]),
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* Condensed QA verdict summary for structured log output (#434).
|
|
94
|
+
*
|
|
95
|
+
* Provides AC coverage counts, gaps, and suggestions so that
|
|
96
|
+
* `sequant_logs` consumers can review QA results without
|
|
97
|
+
* fetching issue comments separately.
|
|
98
|
+
*/
|
|
99
|
+
export const QaSummarySchema = z.object({
|
|
100
|
+
/** Number of acceptance criteria marked MET */
|
|
101
|
+
acMet: z.number().int().nonnegative(),
|
|
102
|
+
/** Total number of acceptance criteria evaluated */
|
|
103
|
+
acTotal: z.number().int().nonnegative(),
|
|
104
|
+
/** List of gaps identified during QA */
|
|
105
|
+
gaps: z.array(z.string()),
|
|
106
|
+
/** List of improvement suggestions from QA */
|
|
107
|
+
suggestions: z.array(z.string()),
|
|
108
|
+
});
|
|
78
109
|
/**
|
|
79
110
|
* Log entry for a single phase execution
|
|
80
111
|
*/
|
|
@@ -103,12 +134,16 @@ export const PhaseLogSchema = z.object({
|
|
|
103
134
|
testsPassed: z.number().int().nonnegative().optional(),
|
|
104
135
|
/** Parsed QA verdict (only for qa phase) */
|
|
105
136
|
verdict: QaVerdictSchema.optional(),
|
|
137
|
+
/** Condensed QA summary with AC coverage (#434) */
|
|
138
|
+
summary: QaSummarySchema.optional(),
|
|
106
139
|
/** Git commit SHA after phase completes (AC-2) */
|
|
107
140
|
commitHash: z.string().optional(),
|
|
108
141
|
/** Per-file diff statistics (AC-3) */
|
|
109
142
|
fileDiffStats: z.array(FileDiffStatSchema).optional(),
|
|
110
143
|
/** Cache metrics for QA phase (AC-7) */
|
|
111
144
|
cacheMetrics: CacheMetricsSchema.optional(),
|
|
145
|
+
/** Structured error context for failed phases (#447) */
|
|
146
|
+
errorContext: ErrorContextSchema.optional(),
|
|
112
147
|
});
|
|
113
148
|
/**
|
|
114
149
|
* Complete execution record for a single issue
|