sequant 1.20.3 → 2.0.1

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.
Files changed (137) hide show
  1. package/.claude-plugin/marketplace.json +2 -4
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +36 -15
  4. package/dist/bin/cli.js +25 -2
  5. package/dist/src/commands/doctor.js +42 -9
  6. package/dist/src/commands/init.d.ts +1 -0
  7. package/dist/src/commands/init.js +52 -0
  8. package/dist/src/commands/logs.d.ts +1 -0
  9. package/dist/src/commands/logs.js +18 -2
  10. package/dist/src/commands/run.d.ts +7 -0
  11. package/dist/src/commands/run.js +235 -68
  12. package/dist/src/commands/serve.d.ts +13 -0
  13. package/dist/src/commands/serve.js +131 -0
  14. package/dist/src/commands/stats.d.ts +1 -0
  15. package/dist/src/commands/stats.js +185 -26
  16. package/dist/src/commands/status.d.ts +2 -0
  17. package/dist/src/commands/status.js +99 -50
  18. package/dist/src/index.d.ts +2 -2
  19. package/dist/src/index.js +4 -1
  20. package/dist/src/lib/ac-parser.d.ts +2 -0
  21. package/dist/src/lib/ac-parser.js +12 -2
  22. package/dist/src/lib/assess-comment-parser.d.ts +137 -0
  23. package/dist/src/lib/assess-comment-parser.js +344 -0
  24. package/dist/src/lib/ci/config.d.ts +22 -0
  25. package/dist/src/lib/ci/config.js +134 -0
  26. package/dist/src/lib/ci/index.d.ts +12 -0
  27. package/dist/src/lib/ci/index.js +10 -0
  28. package/dist/src/lib/ci/inputs.d.ts +29 -0
  29. package/dist/src/lib/ci/inputs.js +103 -0
  30. package/dist/src/lib/ci/labels.d.ts +34 -0
  31. package/dist/src/lib/ci/labels.js +101 -0
  32. package/dist/src/lib/ci/outputs.d.ts +25 -0
  33. package/dist/src/lib/ci/outputs.js +84 -0
  34. package/dist/src/lib/ci/triggers.d.ts +9 -0
  35. package/dist/src/lib/ci/triggers.js +86 -0
  36. package/dist/src/lib/ci/types.d.ts +131 -0
  37. package/dist/src/lib/ci/types.js +47 -0
  38. package/dist/src/lib/mcp-config.d.ts +54 -0
  39. package/dist/src/lib/mcp-config.js +172 -0
  40. package/dist/src/lib/merge-check/index.js +6 -12
  41. package/dist/src/lib/merge-check/types.d.ts +20 -7
  42. package/dist/src/lib/merge-check/types.js +11 -0
  43. package/dist/src/lib/phase-signal.d.ts +3 -3
  44. package/dist/src/lib/phase-signal.js +5 -3
  45. package/dist/src/lib/settings.d.ts +52 -0
  46. package/dist/src/lib/settings.js +41 -0
  47. package/dist/src/lib/shutdown.d.ts +16 -5
  48. package/dist/src/lib/shutdown.js +32 -12
  49. package/dist/src/lib/solve-comment-parser.d.ts +9 -102
  50. package/dist/src/lib/solve-comment-parser.js +13 -248
  51. package/dist/src/lib/stacks.d.ts +8 -0
  52. package/dist/src/lib/stacks.js +34 -0
  53. package/dist/src/lib/system.js +3 -7
  54. package/dist/src/lib/test-tautology-detector.d.ts +10 -0
  55. package/dist/src/lib/test-tautology-detector.js +43 -4
  56. package/dist/src/lib/upstream/assessment.js +9 -59
  57. package/dist/src/lib/upstream/issues.js +12 -75
  58. package/dist/src/lib/version-check.d.ts +2 -2
  59. package/dist/src/lib/version-check.js +6 -3
  60. package/dist/src/lib/version.d.ts +4 -0
  61. package/dist/src/lib/version.js +25 -0
  62. package/dist/src/lib/workflow/batch-executor.d.ts +26 -86
  63. package/dist/src/lib/workflow/batch-executor.js +269 -55
  64. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +56 -0
  65. package/dist/src/lib/workflow/drivers/agent-driver.js +8 -0
  66. package/dist/src/lib/workflow/drivers/aider.d.ts +18 -0
  67. package/dist/src/lib/workflow/drivers/aider.js +160 -0
  68. package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -0
  69. package/dist/src/lib/workflow/drivers/claude-code.js +165 -0
  70. package/dist/src/lib/workflow/drivers/index.d.ts +20 -0
  71. package/dist/src/lib/workflow/drivers/index.js +27 -0
  72. package/dist/src/lib/workflow/error-classifier.d.ts +16 -0
  73. package/dist/src/lib/workflow/error-classifier.js +90 -0
  74. package/dist/src/lib/workflow/log-writer.d.ts +6 -3
  75. package/dist/src/lib/workflow/log-writer.js +57 -27
  76. package/dist/src/lib/workflow/metrics-schema.d.ts +9 -9
  77. package/dist/src/lib/workflow/phase-detection.d.ts +23 -0
  78. package/dist/src/lib/workflow/phase-detection.js +45 -29
  79. package/dist/src/lib/workflow/phase-executor.d.ts +42 -3
  80. package/dist/src/lib/workflow/phase-executor.js +375 -229
  81. package/dist/src/lib/workflow/phase-mapper.d.ts +1 -1
  82. package/dist/src/lib/workflow/phase-mapper.js +7 -7
  83. package/dist/src/lib/workflow/platforms/github.d.ts +157 -0
  84. package/dist/src/lib/workflow/platforms/github.js +466 -0
  85. package/dist/src/lib/workflow/platforms/index.d.ts +17 -0
  86. package/dist/src/lib/workflow/platforms/index.js +25 -0
  87. package/dist/src/lib/workflow/platforms/platform-provider.d.ts +67 -0
  88. package/dist/src/lib/workflow/platforms/platform-provider.js +8 -0
  89. package/dist/src/lib/workflow/pr-status.d.ts +2 -4
  90. package/dist/src/lib/workflow/pr-status.js +3 -16
  91. package/dist/src/lib/workflow/qa-cache.d.ts +58 -0
  92. package/dist/src/lib/workflow/qa-cache.js +88 -0
  93. package/dist/src/lib/workflow/reconcile.d.ts +69 -0
  94. package/dist/src/lib/workflow/reconcile.js +290 -0
  95. package/dist/src/lib/workflow/ring-buffer.d.ts +17 -0
  96. package/dist/src/lib/workflow/ring-buffer.js +37 -0
  97. package/dist/src/lib/workflow/run-log-schema.d.ts +115 -24
  98. package/dist/src/lib/workflow/run-log-schema.js +47 -12
  99. package/dist/src/lib/workflow/run-reflect.js +1 -1
  100. package/dist/src/lib/workflow/state-cleanup.js +21 -0
  101. package/dist/src/lib/workflow/state-manager.d.ts +34 -3
  102. package/dist/src/lib/workflow/state-manager.js +278 -126
  103. package/dist/src/lib/workflow/state-schema.d.ts +34 -30
  104. package/dist/src/lib/workflow/state-schema.js +35 -25
  105. package/dist/src/lib/workflow/state-utils.d.ts +3 -1
  106. package/dist/src/lib/workflow/state-utils.js +1 -0
  107. package/dist/src/lib/workflow/types.d.ts +224 -6
  108. package/dist/src/lib/workflow/types.js +20 -1
  109. package/dist/src/lib/workflow/worktree-discovery.d.ts +1 -1
  110. package/dist/src/lib/workflow/worktree-discovery.js +6 -14
  111. package/dist/src/lib/workflow/worktree-manager.js +33 -51
  112. package/dist/src/mcp/index.d.ts +4 -0
  113. package/dist/src/mcp/index.js +4 -0
  114. package/dist/src/mcp/resources.d.ts +7 -0
  115. package/dist/src/mcp/resources.js +111 -0
  116. package/dist/src/mcp/run-registry.d.ts +34 -0
  117. package/dist/src/mcp/run-registry.js +42 -0
  118. package/dist/src/mcp/server.d.ts +12 -0
  119. package/dist/src/mcp/server.js +50 -0
  120. package/dist/src/mcp/tools/logs.d.ts +7 -0
  121. package/dist/src/mcp/tools/logs.js +149 -0
  122. package/dist/src/mcp/tools/run.d.ts +121 -0
  123. package/dist/src/mcp/tools/run.js +591 -0
  124. package/dist/src/mcp/tools/status.d.ts +7 -0
  125. package/dist/src/mcp/tools/status.js +127 -0
  126. package/package.json +26 -7
  127. package/templates/hooks/post-tool.sh +19 -8
  128. package/templates/hooks/pre-tool.sh +36 -49
  129. package/templates/mcp.json +6 -0
  130. package/templates/skills/assess/SKILL.md +354 -352
  131. package/templates/skills/exec/SKILL.md +64 -1
  132. package/templates/skills/fullsolve/SKILL.md +35 -4
  133. package/templates/skills/qa/SKILL.md +486 -9
  134. package/templates/skills/qa/scripts/quality-checks.sh +1 -1
  135. package/templates/skills/setup/SKILL.md +386 -0
  136. package/templates/skills/solve/SKILL.md +38 -664
  137. 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
- * Available workflow phases
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
- * Available workflow phases
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