sequant 1.16.1 → 1.18.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 +1 -1
- package/.claude-plugin/plugin.json +14 -2
- package/README.md +2 -0
- package/dist/bin/cli.js +2 -1
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +21 -0
- package/dist/marketplace/external_plugins/sequant/README.md +38 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +292 -0
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +463 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/prompt-templates.md +350 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +131 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +474 -0
- package/dist/marketplace/external_plugins/sequant/skills/clean/SKILL.md +211 -0
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +337 -0
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +807 -0
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +678 -0
- package/dist/marketplace/external_plugins/sequant/skills/improve/SKILL.md +668 -0
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +374 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +570 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-quality-exemplars.md +107 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +65 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +179 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/semgrep-rules.md +207 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +109 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +622 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +175 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +70 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +95 -0
- package/dist/marketplace/external_plugins/sequant/skills/security-review/SKILL.md +358 -0
- package/dist/marketplace/external_plugins/sequant/skills/security-review/references/security-checklists.md +432 -0
- package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +697 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +754 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/parallel-groups.md +72 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +92 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +104 -0
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +600 -0
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +576 -0
- package/dist/marketplace/external_plugins/sequant/skills/verify/SKILL.md +281 -0
- package/dist/src/commands/run.d.ts +13 -274
- package/dist/src/commands/run.js +43 -1958
- package/dist/src/commands/sync.js +3 -0
- package/dist/src/commands/update.js +3 -0
- package/dist/src/lib/plugin-version-sync.d.ts +2 -1
- package/dist/src/lib/plugin-version-sync.js +28 -7
- package/dist/src/lib/solve-comment-parser.d.ts +26 -0
- package/dist/src/lib/solve-comment-parser.js +63 -7
- package/dist/src/lib/upstream/assessment.js +6 -3
- package/dist/src/lib/upstream/relevance.d.ts +5 -0
- package/dist/src/lib/upstream/relevance.js +24 -0
- package/dist/src/lib/upstream/report.js +18 -46
- package/dist/src/lib/upstream/types.d.ts +2 -0
- package/dist/src/lib/workflow/batch-executor.d.ts +117 -0
- package/dist/src/lib/workflow/batch-executor.js +574 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +40 -0
- package/dist/src/lib/workflow/phase-executor.js +381 -0
- package/dist/src/lib/workflow/phase-mapper.d.ts +65 -0
- package/dist/src/lib/workflow/phase-mapper.js +147 -0
- package/dist/src/lib/workflow/pr-operations.d.ts +86 -0
- package/dist/src/lib/workflow/pr-operations.js +326 -0
- package/dist/src/lib/workflow/pr-status.d.ts +49 -0
- package/dist/src/lib/workflow/pr-status.js +131 -0
- package/dist/src/lib/workflow/run-reflect.d.ts +32 -0
- package/dist/src/lib/workflow/run-reflect.js +191 -0
- package/dist/src/lib/workflow/run-summary.d.ts +36 -0
- package/dist/src/lib/workflow/run-summary.js +142 -0
- package/dist/src/lib/workflow/state-cleanup.d.ts +79 -0
- package/dist/src/lib/workflow/state-cleanup.js +250 -0
- package/dist/src/lib/workflow/state-rebuild.d.ts +38 -0
- package/dist/src/lib/workflow/state-rebuild.js +140 -0
- package/dist/src/lib/workflow/state-utils.d.ts +14 -162
- package/dist/src/lib/workflow/state-utils.js +10 -677
- package/dist/src/lib/workflow/worktree-discovery.d.ts +61 -0
- package/dist/src/lib/workflow/worktree-discovery.js +229 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +205 -0
- package/dist/src/lib/workflow/worktree-manager.js +918 -0
- package/package.json +4 -2
- package/templates/skills/exec/SKILL.md +2 -2
- package/templates/skills/fullsolve/SKILL.md +15 -5
- package/templates/skills/loop/SKILL.md +1 -1
- package/templates/skills/qa/SKILL.md +47 -7
- package/templates/skills/solve/SKILL.md +92 -6
- package/templates/skills/spec/SKILL.md +57 -4
- package/templates/skills/test/SKILL.md +10 -0
- package/templates/skills/testgen/SKILL.md +1 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State cleanup and reconciliation utilities
|
|
3
|
+
*
|
|
4
|
+
* @module state-cleanup
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { cleanupStaleEntries, reconcileStateAtStartup } from './state-cleanup';
|
|
8
|
+
*
|
|
9
|
+
* // Clean up orphaned entries
|
|
10
|
+
* const result = await cleanupStaleEntries({ dryRun: true });
|
|
11
|
+
* console.log(`Would remove ${result.removed.length} entries`);
|
|
12
|
+
*
|
|
13
|
+
* // Reconcile state at startup
|
|
14
|
+
* const reconcileResult = await reconcileStateAtStartup();
|
|
15
|
+
* console.log(`Advanced ${reconcileResult.advanced.length} issues to merged`);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { spawnSync } from "child_process";
|
|
19
|
+
import { StateManager } from "./state-manager.js";
|
|
20
|
+
import { checkPRMergeStatus, isIssueMergedIntoMain } from "./pr-status.js";
|
|
21
|
+
/**
|
|
22
|
+
* Get list of active worktree paths
|
|
23
|
+
*/
|
|
24
|
+
function getActiveWorktrees() {
|
|
25
|
+
const result = spawnSync("git", ["worktree", "list", "--porcelain"], {
|
|
26
|
+
stdio: "pipe",
|
|
27
|
+
});
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const output = result.stdout.toString();
|
|
32
|
+
const paths = [];
|
|
33
|
+
for (const line of output.split("\n")) {
|
|
34
|
+
if (line.startsWith("worktree ")) {
|
|
35
|
+
paths.push(line.substring(9));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return paths;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Clean up stale and orphaned entries from workflow state
|
|
42
|
+
*
|
|
43
|
+
* - Checks GitHub to detect if associated PR was merged
|
|
44
|
+
* - Orphaned entries with merged PRs get status "merged" and are removed automatically
|
|
45
|
+
* - Orphaned entries without merged PRs get status "abandoned" (kept for review)
|
|
46
|
+
* - Use removeAll to remove both merged and abandoned orphaned entries in one step
|
|
47
|
+
* - Use maxAgeDays to remove old merged/abandoned issues
|
|
48
|
+
*/
|
|
49
|
+
export async function cleanupStaleEntries(options = {}) {
|
|
50
|
+
const manager = new StateManager({
|
|
51
|
+
statePath: options.statePath,
|
|
52
|
+
verbose: options.verbose,
|
|
53
|
+
});
|
|
54
|
+
if (!manager.stateExists()) {
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
removed: [],
|
|
58
|
+
orphaned: [],
|
|
59
|
+
merged: [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const state = await manager.getState();
|
|
64
|
+
const removed = [];
|
|
65
|
+
const orphaned = [];
|
|
66
|
+
const merged = [];
|
|
67
|
+
// Get list of active worktrees
|
|
68
|
+
const activeWorktrees = getActiveWorktrees();
|
|
69
|
+
for (const [issueNumStr, issueState] of Object.entries(state.issues)) {
|
|
70
|
+
const issueNum = parseInt(issueNumStr, 10);
|
|
71
|
+
// Check if worktree exists (if issue has one)
|
|
72
|
+
if (issueState.worktree &&
|
|
73
|
+
!activeWorktrees.includes(issueState.worktree)) {
|
|
74
|
+
if (options.verbose) {
|
|
75
|
+
console.log(`🔍 Orphaned: #${issueNum} (worktree not found: ${issueState.worktree})`);
|
|
76
|
+
}
|
|
77
|
+
// Check if this issue has a PR and if it's merged
|
|
78
|
+
let prMerged = false;
|
|
79
|
+
if (issueState.pr?.number) {
|
|
80
|
+
if (options.verbose) {
|
|
81
|
+
console.log(` Checking PR #${issueState.pr.number} status...`);
|
|
82
|
+
}
|
|
83
|
+
const prStatus = checkPRMergeStatus(issueState.pr.number);
|
|
84
|
+
prMerged = prStatus === "MERGED";
|
|
85
|
+
if (options.verbose) {
|
|
86
|
+
console.log(` PR status: ${prStatus ?? "unknown"}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!options.dryRun) {
|
|
90
|
+
if (prMerged || issueState.status === "merged") {
|
|
91
|
+
// Merged PRs are auto-removed
|
|
92
|
+
merged.push(issueNum);
|
|
93
|
+
removed.push(issueNum);
|
|
94
|
+
if (options.verbose) {
|
|
95
|
+
console.log(` ✓ Merged PR detected, removing entry`);
|
|
96
|
+
}
|
|
97
|
+
delete state.issues[issueNumStr];
|
|
98
|
+
}
|
|
99
|
+
else if (issueState.status === "abandoned" || options.removeAll) {
|
|
100
|
+
// Already abandoned or removeAll flag - remove it
|
|
101
|
+
orphaned.push(issueNum);
|
|
102
|
+
removed.push(issueNum);
|
|
103
|
+
if (options.verbose) {
|
|
104
|
+
console.log(` ✓ Removing abandoned entry`);
|
|
105
|
+
}
|
|
106
|
+
delete state.issues[issueNumStr];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Mark as abandoned (kept for review)
|
|
110
|
+
orphaned.push(issueNum);
|
|
111
|
+
issueState.status = "abandoned";
|
|
112
|
+
if (options.verbose) {
|
|
113
|
+
console.log(` → Marked as abandoned (kept for review)`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Dry run - report what would happen
|
|
119
|
+
if (prMerged || issueState.status === "merged") {
|
|
120
|
+
merged.push(issueNum);
|
|
121
|
+
removed.push(issueNum);
|
|
122
|
+
}
|
|
123
|
+
else if (issueState.status === "abandoned" || options.removeAll) {
|
|
124
|
+
orphaned.push(issueNum);
|
|
125
|
+
removed.push(issueNum);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
orphaned.push(issueNum);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// Check age for merged/abandoned issues
|
|
134
|
+
if (options.maxAgeDays &&
|
|
135
|
+
(issueState.status === "merged" || issueState.status === "abandoned")) {
|
|
136
|
+
const lastActivity = new Date(issueState.lastActivity);
|
|
137
|
+
const ageDays = (Date.now() - lastActivity.getTime()) / (1000 * 60 * 60 * 24);
|
|
138
|
+
if (ageDays > options.maxAgeDays) {
|
|
139
|
+
removed.push(issueNum);
|
|
140
|
+
if (options.verbose) {
|
|
141
|
+
console.log(`🗑️ Stale: #${issueNum} (${Math.floor(ageDays)} days old)`);
|
|
142
|
+
}
|
|
143
|
+
if (!options.dryRun) {
|
|
144
|
+
delete state.issues[issueNumStr];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Save updated state
|
|
150
|
+
if (!options.dryRun && (removed.length > 0 || orphaned.length > 0)) {
|
|
151
|
+
await manager.saveState(state);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
removed,
|
|
156
|
+
orphaned,
|
|
157
|
+
merged,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
removed: [],
|
|
164
|
+
orphaned: [],
|
|
165
|
+
merged: [],
|
|
166
|
+
error: String(error),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Lightweight state reconciliation at run start
|
|
172
|
+
*
|
|
173
|
+
* Checks issues in `ready_for_merge` state and advances them to `merged`
|
|
174
|
+
* if their PRs are merged or their branches are in main.
|
|
175
|
+
*
|
|
176
|
+
* This prevents re-running already completed issues.
|
|
177
|
+
*
|
|
178
|
+
* @param options - Reconciliation options
|
|
179
|
+
* @returns Result with lists of advanced and still-pending issues
|
|
180
|
+
*/
|
|
181
|
+
export async function reconcileStateAtStartup(options = {}) {
|
|
182
|
+
const manager = new StateManager({
|
|
183
|
+
statePath: options.statePath,
|
|
184
|
+
verbose: options.verbose,
|
|
185
|
+
});
|
|
186
|
+
// Graceful degradation: if state file doesn't exist, skip
|
|
187
|
+
if (!manager.stateExists()) {
|
|
188
|
+
return {
|
|
189
|
+
success: true,
|
|
190
|
+
advanced: [],
|
|
191
|
+
stillPending: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const state = await manager.getState();
|
|
196
|
+
const advanced = [];
|
|
197
|
+
const stillPending = [];
|
|
198
|
+
// Find issues in ready_for_merge state
|
|
199
|
+
for (const [issueNumStr, issueState] of Object.entries(state.issues)) {
|
|
200
|
+
if (issueState.status !== "ready_for_merge") {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const issueNum = parseInt(issueNumStr, 10);
|
|
204
|
+
let isMerged = false;
|
|
205
|
+
// Check 1: If we have PR info, check PR status via gh
|
|
206
|
+
if (issueState.pr?.number) {
|
|
207
|
+
const prStatus = checkPRMergeStatus(issueState.pr.number);
|
|
208
|
+
if (prStatus === "MERGED") {
|
|
209
|
+
isMerged = true;
|
|
210
|
+
if (options.verbose) {
|
|
211
|
+
console.log(` #${issueNum}: PR #${issueState.pr.number} is merged`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Check 2: If no PR or PR check failed, check git for merged branch
|
|
216
|
+
if (!isMerged) {
|
|
217
|
+
isMerged = isIssueMergedIntoMain(issueNum);
|
|
218
|
+
if (isMerged && options.verbose) {
|
|
219
|
+
console.log(` #${issueNum}: Branch merged into main (git check)`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (isMerged) {
|
|
223
|
+
// Advance state to merged
|
|
224
|
+
issueState.status = "merged";
|
|
225
|
+
issueState.lastActivity = new Date().toISOString();
|
|
226
|
+
advanced.push(issueNum);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
stillPending.push(issueNum);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Save state if any issues were advanced
|
|
233
|
+
if (advanced.length > 0) {
|
|
234
|
+
await manager.saveState(state);
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
advanced,
|
|
239
|
+
stillPending,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
advanced: [],
|
|
246
|
+
stillPending: [],
|
|
247
|
+
error: String(error),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State reconstruction from run logs
|
|
3
|
+
*
|
|
4
|
+
* @module state-rebuild
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { rebuildStateFromLogs } from './state-rebuild';
|
|
8
|
+
*
|
|
9
|
+
* // Rebuild state from run logs
|
|
10
|
+
* const result = await rebuildStateFromLogs();
|
|
11
|
+
* console.log(`Processed ${result.logsProcessed} logs, found ${result.issuesFound} issues`);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export interface RebuildOptions {
|
|
15
|
+
/** Log directory path (default: .sequant/logs) */
|
|
16
|
+
logPath?: string;
|
|
17
|
+
/** State file path (default: .sequant/state.json) */
|
|
18
|
+
statePath?: string;
|
|
19
|
+
/** Enable verbose logging */
|
|
20
|
+
verbose?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface RebuildResult {
|
|
23
|
+
/** Whether rebuild was successful */
|
|
24
|
+
success: boolean;
|
|
25
|
+
/** Number of log files processed */
|
|
26
|
+
logsProcessed: number;
|
|
27
|
+
/** Number of issues found */
|
|
28
|
+
issuesFound: number;
|
|
29
|
+
/** Error message if failed */
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Rebuild workflow state from run logs
|
|
34
|
+
*
|
|
35
|
+
* Scans all run logs in .sequant/logs/ and reconstructs state
|
|
36
|
+
* based on the most recent activity for each issue.
|
|
37
|
+
*/
|
|
38
|
+
export declare function rebuildStateFromLogs(options?: RebuildOptions): Promise<RebuildResult>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State reconstruction from run logs
|
|
3
|
+
*
|
|
4
|
+
* @module state-rebuild
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { rebuildStateFromLogs } from './state-rebuild';
|
|
8
|
+
*
|
|
9
|
+
* // Rebuild state from run logs
|
|
10
|
+
* const result = await rebuildStateFromLogs();
|
|
11
|
+
* console.log(`Processed ${result.logsProcessed} logs, found ${result.issuesFound} issues`);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
import { StateManager } from "./state-manager.js";
|
|
17
|
+
import { createEmptyState, createIssueState, createPhaseState, } from "./state-schema.js";
|
|
18
|
+
import { RunLogSchema, LOG_PATHS } from "./run-log-schema.js";
|
|
19
|
+
/**
|
|
20
|
+
* Rebuild workflow state from run logs
|
|
21
|
+
*
|
|
22
|
+
* Scans all run logs in .sequant/logs/ and reconstructs state
|
|
23
|
+
* based on the most recent activity for each issue.
|
|
24
|
+
*/
|
|
25
|
+
export async function rebuildStateFromLogs(options = {}) {
|
|
26
|
+
const logPath = options.logPath ?? LOG_PATHS.project;
|
|
27
|
+
if (!fs.existsSync(logPath)) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
logsProcessed: 0,
|
|
31
|
+
issuesFound: 0,
|
|
32
|
+
error: `Log directory not found: ${logPath}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// Find all log files
|
|
37
|
+
const files = fs.readdirSync(logPath).filter((f) => f.endsWith(".json"));
|
|
38
|
+
if (files.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
logsProcessed: 0,
|
|
42
|
+
issuesFound: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Sort by timestamp (newest first)
|
|
46
|
+
files.sort().reverse();
|
|
47
|
+
// Build state from logs
|
|
48
|
+
const state = createEmptyState();
|
|
49
|
+
const issueMap = new Map();
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const filePath = path.join(logPath, file);
|
|
52
|
+
try {
|
|
53
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
54
|
+
const logData = JSON.parse(content);
|
|
55
|
+
const log = RunLogSchema.safeParse(logData);
|
|
56
|
+
if (!log.success) {
|
|
57
|
+
if (options.verbose) {
|
|
58
|
+
console.log(`⚠️ Invalid log format: ${file}`);
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const runLog = log.data;
|
|
63
|
+
// Process each issue in the log
|
|
64
|
+
for (const issueLog of runLog.issues) {
|
|
65
|
+
// Skip if we already have newer data for this issue
|
|
66
|
+
if (issueMap.has(issueLog.issueNumber)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Create issue state from log
|
|
70
|
+
const issueState = createIssueState(issueLog.issueNumber, issueLog.title);
|
|
71
|
+
// Determine status from log
|
|
72
|
+
if (issueLog.status === "success") {
|
|
73
|
+
issueState.status = "ready_for_merge";
|
|
74
|
+
}
|
|
75
|
+
else if (issueLog.status === "failure") {
|
|
76
|
+
issueState.status = "in_progress";
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
issueState.status = "in_progress";
|
|
80
|
+
}
|
|
81
|
+
// Add phase states from log
|
|
82
|
+
for (const phaseLog of issueLog.phases) {
|
|
83
|
+
const phaseState = createPhaseState(phaseLog.status === "success"
|
|
84
|
+
? "completed"
|
|
85
|
+
: phaseLog.status === "failure"
|
|
86
|
+
? "failed"
|
|
87
|
+
: phaseLog.status === "skipped"
|
|
88
|
+
? "skipped"
|
|
89
|
+
: "completed");
|
|
90
|
+
phaseState.startedAt = phaseLog.startTime;
|
|
91
|
+
phaseState.completedAt = phaseLog.endTime;
|
|
92
|
+
if (phaseLog.error) {
|
|
93
|
+
phaseState.error = phaseLog.error;
|
|
94
|
+
}
|
|
95
|
+
issueState.phases[phaseLog.phase] = phaseState;
|
|
96
|
+
// Update current phase to last executed
|
|
97
|
+
issueState.currentPhase = phaseLog.phase;
|
|
98
|
+
}
|
|
99
|
+
// Set last activity from most recent phase
|
|
100
|
+
const lastPhase = issueLog.phases[issueLog.phases.length - 1];
|
|
101
|
+
if (lastPhase) {
|
|
102
|
+
issueState.lastActivity = lastPhase.endTime;
|
|
103
|
+
}
|
|
104
|
+
issueMap.set(issueLog.issueNumber, issueState);
|
|
105
|
+
}
|
|
106
|
+
if (options.verbose) {
|
|
107
|
+
console.log(`✓ Processed: ${file}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (options.verbose) {
|
|
112
|
+
console.log(`⚠️ Error reading ${file}: ${err}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Copy issues to state
|
|
117
|
+
for (const [num, issueState] of issueMap) {
|
|
118
|
+
state.issues[String(num)] = issueState;
|
|
119
|
+
}
|
|
120
|
+
// Save rebuilt state
|
|
121
|
+
const manager = new StateManager({
|
|
122
|
+
statePath: options.statePath,
|
|
123
|
+
verbose: options.verbose,
|
|
124
|
+
});
|
|
125
|
+
await manager.saveState(state);
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
logsProcessed: files.length,
|
|
129
|
+
issuesFound: issueMap.size,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
logsProcessed: 0,
|
|
136
|
+
issuesFound: 0,
|
|
137
|
+
error: String(error),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* State utilities for rebuilding and cleaning up workflow state
|
|
3
3
|
*
|
|
4
|
+
* This module re-exports focused utilities from dedicated modules:
|
|
5
|
+
* - pr-status: PR merge detection and branch status
|
|
6
|
+
* - state-rebuild: State reconstruction from run logs
|
|
7
|
+
* - worktree-discovery: Worktree discovery for state bootstrapping
|
|
8
|
+
* - state-cleanup: Cleanup of stale entries and startup reconciliation
|
|
9
|
+
*
|
|
4
10
|
* @example
|
|
5
11
|
* ```typescript
|
|
6
12
|
* import { rebuildStateFromLogs, cleanupStaleEntries } from './state-utils';
|
|
@@ -12,165 +18,11 @@
|
|
|
12
18
|
* const result = await cleanupStaleEntries({ dryRun: true });
|
|
13
19
|
* ```
|
|
14
20
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* @param prNumber - The PR number to check
|
|
24
|
-
* @returns "MERGED" | "CLOSED" | "OPEN" | null (null if PR not found or gh unavailable)
|
|
25
|
-
*/
|
|
26
|
-
export declare function checkPRMergeStatus(prNumber: number): PRMergeStatus;
|
|
27
|
-
export interface RebuildOptions {
|
|
28
|
-
/** Log directory path (default: .sequant/logs) */
|
|
29
|
-
logPath?: string;
|
|
30
|
-
/** State file path (default: .sequant/state.json) */
|
|
31
|
-
statePath?: string;
|
|
32
|
-
/** Enable verbose logging */
|
|
33
|
-
verbose?: boolean;
|
|
34
|
-
}
|
|
35
|
-
export interface RebuildResult {
|
|
36
|
-
/** Whether rebuild was successful */
|
|
37
|
-
success: boolean;
|
|
38
|
-
/** Number of log files processed */
|
|
39
|
-
logsProcessed: number;
|
|
40
|
-
/** Number of issues found */
|
|
41
|
-
issuesFound: number;
|
|
42
|
-
/** Error message if failed */
|
|
43
|
-
error?: string;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Rebuild workflow state from run logs
|
|
47
|
-
*
|
|
48
|
-
* Scans all run logs in .sequant/logs/ and reconstructs state
|
|
49
|
-
* based on the most recent activity for each issue.
|
|
50
|
-
*/
|
|
51
|
-
export declare function rebuildStateFromLogs(options?: RebuildOptions): Promise<RebuildResult>;
|
|
52
|
-
export interface CleanupOptions {
|
|
53
|
-
/** State file path (default: .sequant/state.json) */
|
|
54
|
-
statePath?: string;
|
|
55
|
-
/** Only report what would be cleaned (don't modify) */
|
|
56
|
-
dryRun?: boolean;
|
|
57
|
-
/** Enable verbose logging */
|
|
58
|
-
verbose?: boolean;
|
|
59
|
-
/** Remove issues older than this many days */
|
|
60
|
-
maxAgeDays?: number;
|
|
61
|
-
/** Remove all orphaned entries (both merged and abandoned) in one step */
|
|
62
|
-
removeAll?: boolean;
|
|
63
|
-
}
|
|
64
|
-
export interface CleanupResult {
|
|
65
|
-
/** Whether cleanup was successful */
|
|
66
|
-
success: boolean;
|
|
67
|
-
/** Issues that were removed or would be removed */
|
|
68
|
-
removed: number[];
|
|
69
|
-
/** Issues that were marked as orphaned (abandoned) */
|
|
70
|
-
orphaned: number[];
|
|
71
|
-
/** Issues detected as merged PRs */
|
|
72
|
-
merged: number[];
|
|
73
|
-
/** Error message if failed */
|
|
74
|
-
error?: string;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Clean up stale and orphaned entries from workflow state
|
|
78
|
-
*
|
|
79
|
-
* - Checks GitHub to detect if associated PR was merged
|
|
80
|
-
* - Orphaned entries with merged PRs get status "merged" and are removed automatically
|
|
81
|
-
* - Orphaned entries without merged PRs get status "abandoned" (kept for review)
|
|
82
|
-
* - Use removeAll to remove both merged and abandoned orphaned entries in one step
|
|
83
|
-
* - Use maxAgeDays to remove old merged/abandoned issues
|
|
84
|
-
*/
|
|
85
|
-
export declare function cleanupStaleEntries(options?: CleanupOptions): Promise<CleanupResult>;
|
|
86
|
-
export interface DiscoverOptions {
|
|
87
|
-
/** State file path (default: .sequant/state.json) */
|
|
88
|
-
statePath?: string;
|
|
89
|
-
/** Enable verbose logging */
|
|
90
|
-
verbose?: boolean;
|
|
91
|
-
}
|
|
92
|
-
export interface DiscoveredWorktree {
|
|
93
|
-
/** Issue number extracted from branch name */
|
|
94
|
-
issueNumber: number;
|
|
95
|
-
/** Issue title (fetched from GitHub or placeholder) */
|
|
96
|
-
title: string;
|
|
97
|
-
/** Full path to the worktree */
|
|
98
|
-
worktreePath: string;
|
|
99
|
-
/** Branch name */
|
|
100
|
-
branch: string;
|
|
101
|
-
/** Inferred current phase from logs (if available) */
|
|
102
|
-
inferredPhase?: Phase;
|
|
103
|
-
}
|
|
104
|
-
export interface SkippedWorktree {
|
|
105
|
-
/** Path to the worktree */
|
|
106
|
-
path: string;
|
|
107
|
-
/** Reason it was skipped */
|
|
108
|
-
reason: string;
|
|
109
|
-
}
|
|
110
|
-
export interface DiscoverResult {
|
|
111
|
-
/** Whether discovery was successful */
|
|
112
|
-
success: boolean;
|
|
113
|
-
/** Number of worktrees scanned */
|
|
114
|
-
worktreesScanned: number;
|
|
115
|
-
/** Number of worktrees already tracked */
|
|
116
|
-
alreadyTracked: number;
|
|
117
|
-
/** Discovered worktrees not yet in state */
|
|
118
|
-
discovered: DiscoveredWorktree[];
|
|
119
|
-
/** Worktrees that were skipped (not matching pattern, etc.) */
|
|
120
|
-
skipped: SkippedWorktree[];
|
|
121
|
-
/** Error message if failed */
|
|
122
|
-
error?: string;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Discover worktrees that are not yet tracked in state
|
|
126
|
-
*
|
|
127
|
-
* Scans all git worktrees, identifies those with issue-related branch names,
|
|
128
|
-
* and returns information about worktrees not yet in the state file.
|
|
129
|
-
*/
|
|
130
|
-
export declare function discoverUntrackedWorktrees(options?: DiscoverOptions): Promise<DiscoverResult>;
|
|
131
|
-
export interface ReconcileOptions {
|
|
132
|
-
/** State file path (default: .sequant/state.json) */
|
|
133
|
-
statePath?: string;
|
|
134
|
-
/** Enable verbose logging */
|
|
135
|
-
verbose?: boolean;
|
|
136
|
-
}
|
|
137
|
-
export interface ReconcileResult {
|
|
138
|
-
/** Whether reconciliation was successful */
|
|
139
|
-
success: boolean;
|
|
140
|
-
/** Issues that were advanced from ready_for_merge to merged */
|
|
141
|
-
advanced: number[];
|
|
142
|
-
/** Issues checked but still ready_for_merge */
|
|
143
|
-
stillPending: number[];
|
|
144
|
-
/** Error message if failed */
|
|
145
|
-
error?: string;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Check if a branch has been merged into main using git
|
|
149
|
-
*
|
|
150
|
-
* @param branchName - The branch name to check (e.g., "feature/33-some-title")
|
|
151
|
-
* @returns true if the branch is merged into main, false otherwise
|
|
152
|
-
*/
|
|
153
|
-
export declare function isBranchMergedIntoMain(branchName: string): boolean;
|
|
154
|
-
/**
|
|
155
|
-
* Check if a feature branch for an issue is merged into main
|
|
156
|
-
*
|
|
157
|
-
* Tries multiple detection methods:
|
|
158
|
-
* 1. Check if branch exists and is merged via `git branch --merged main`
|
|
159
|
-
* 2. Check for merge commits mentioning the issue
|
|
160
|
-
*
|
|
161
|
-
* @param issueNumber - The issue number to check
|
|
162
|
-
* @returns true if the issue's work is merged into main
|
|
163
|
-
*/
|
|
164
|
-
export declare function isIssueMergedIntoMain(issueNumber: number): boolean;
|
|
165
|
-
/**
|
|
166
|
-
* Lightweight state reconciliation at run start
|
|
167
|
-
*
|
|
168
|
-
* Checks issues in `ready_for_merge` state and advances them to `merged`
|
|
169
|
-
* if their PRs are merged or their branches are in main.
|
|
170
|
-
*
|
|
171
|
-
* This prevents re-running already completed issues.
|
|
172
|
-
*
|
|
173
|
-
* @param options - Reconciliation options
|
|
174
|
-
* @returns Result with lists of advanced and still-pending issues
|
|
175
|
-
*/
|
|
176
|
-
export declare function reconcileStateAtStartup(options?: ReconcileOptions): Promise<ReconcileResult>;
|
|
21
|
+
export type { PRMergeStatus } from "./pr-status.js";
|
|
22
|
+
export { checkPRMergeStatus, isBranchMergedIntoMain, isIssueMergedIntoMain, } from "./pr-status.js";
|
|
23
|
+
export type { RebuildOptions, RebuildResult } from "./state-rebuild.js";
|
|
24
|
+
export { rebuildStateFromLogs } from "./state-rebuild.js";
|
|
25
|
+
export type { DiscoverOptions, DiscoveredWorktree, SkippedWorktree, DiscoverResult, } from "./worktree-discovery.js";
|
|
26
|
+
export { discoverUntrackedWorktrees } from "./worktree-discovery.js";
|
|
27
|
+
export type { CleanupOptions, CleanupResult, ReconcileOptions, ReconcileResult, } from "./state-cleanup.js";
|
|
28
|
+
export { cleanupStaleEntries, reconcileStateAtStartup, } from "./state-cleanup.js";
|