sequant 1.14.0 → 1.14.2
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/plugin.json +1 -1
- package/README.md +1 -0
- package/dist/bin/cli.js +1 -0
- package/dist/src/commands/run.d.ts +21 -0
- package/dist/src/commands/run.js +35 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -0
- package/dist/src/lib/workflow/phase-detection.d.ts +114 -0
- package/dist/src/lib/workflow/phase-detection.js +215 -0
- package/dist/src/lib/workflow/state-schema.d.ts +29 -0
- package/dist/src/lib/workflow/state-schema.js +17 -0
- package/package.json +1 -1
- package/templates/skills/exec/SKILL.md +50 -1
- package/templates/skills/fullsolve/SKILL.md +47 -1
- package/templates/skills/qa/SKILL.md +47 -1
- package/templates/skills/security-review/SKILL.md +0 -1
- package/templates/skills/spec/SKILL.md +39 -1
- package/templates/skills/testgen/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -223,6 +223,7 @@ See [Customization Guide](docs/guides/customization.md) for all options.
|
|
|
223
223
|
- [Complete Workflow](docs/guides/workflow.md) — Full workflow including post-QA patterns
|
|
224
224
|
- [Getting Started](docs/getting-started/installation.md)
|
|
225
225
|
- [What We've Built](docs/internal/what-weve-built.md) — Comprehensive project overview
|
|
226
|
+
- [What Is Sequant](docs/concepts/what-is-sequant.md) — Elevator pitch, pipeline diagram, architecture
|
|
226
227
|
- [Workflow Concepts](docs/concepts/workflow-phases.md)
|
|
227
228
|
- [Run Command](docs/reference/run-command.md)
|
|
228
229
|
- [Git Workflows](docs/guides/git-workflows.md)
|
package/dist/bin/cli.js
CHANGED
|
@@ -138,6 +138,7 @@ program
|
|
|
138
138
|
.option("--qa-gate", "Wait for QA pass before starting next issue in chain (requires --chain)")
|
|
139
139
|
.option("--base <branch>", "Base branch for worktree creation (default: main or settings.run.defaultBase)")
|
|
140
140
|
.option("--no-mcp", "Disable MCP server injection in headless mode")
|
|
141
|
+
.option("--resume", "Resume from last completed phase (reads phase markers from GitHub)")
|
|
141
142
|
.action(runCommand);
|
|
142
143
|
program
|
|
143
144
|
.command("logs")
|
|
@@ -37,6 +37,22 @@ export declare function getWorktreeDiffStats(worktreePath: string): {
|
|
|
37
37
|
filesChanged: number;
|
|
38
38
|
linesAdded: number;
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Filter phases based on resume status.
|
|
42
|
+
*
|
|
43
|
+
* When `resume` is true, calls `getResumablePhasesForIssue` to determine
|
|
44
|
+
* which phases have already completed (via GitHub issue comment markers)
|
|
45
|
+
* and removes them from the execution list.
|
|
46
|
+
*
|
|
47
|
+
* @param issueNumber - GitHub issue number
|
|
48
|
+
* @param phases - The phases to potentially filter
|
|
49
|
+
* @param resume - Whether the --resume flag is set
|
|
50
|
+
* @returns Object with filtered phases and any skipped phases
|
|
51
|
+
*/
|
|
52
|
+
export declare function filterResumedPhases(issueNumber: number, phases: Phase[], resume: boolean): {
|
|
53
|
+
phases: Phase[];
|
|
54
|
+
skipped: Phase[];
|
|
55
|
+
};
|
|
40
56
|
/**
|
|
41
57
|
* Create a checkpoint commit in the worktree after QA passes
|
|
42
58
|
* This allows recovery in case later issues in the chain fail
|
|
@@ -103,6 +119,11 @@ interface RunOptions {
|
|
|
103
119
|
* Resolution priority: this CLI flag → settings.run.mcp → default (true)
|
|
104
120
|
*/
|
|
105
121
|
noMcp?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Resume from last completed phase.
|
|
124
|
+
* Reads phase markers from GitHub issue comments and skips completed phases.
|
|
125
|
+
*/
|
|
126
|
+
resume?: boolean;
|
|
106
127
|
}
|
|
107
128
|
/**
|
|
108
129
|
* Main run command
|
package/dist/src/commands/run.js
CHANGED
|
@@ -20,6 +20,7 @@ import { getMcpServersConfig } from "../lib/system.js";
|
|
|
20
20
|
import { checkVersionCached, getVersionWarning } from "../lib/version-check.js";
|
|
21
21
|
import { MetricsWriter } from "../lib/workflow/metrics-writer.js";
|
|
22
22
|
import { determineOutcome, } from "../lib/workflow/metrics-schema.js";
|
|
23
|
+
import { getResumablePhasesForIssue } from "../lib/workflow/phase-detection.js";
|
|
23
24
|
import { ui, colors } from "../lib/cli-ui.js";
|
|
24
25
|
import { PhaseSpinner } from "../lib/phase-spinner.js";
|
|
25
26
|
/**
|
|
@@ -159,6 +160,26 @@ export function getWorktreeDiffStats(worktreePath) {
|
|
|
159
160
|
linesAdded: insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0,
|
|
160
161
|
};
|
|
161
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Filter phases based on resume status.
|
|
165
|
+
*
|
|
166
|
+
* When `resume` is true, calls `getResumablePhasesForIssue` to determine
|
|
167
|
+
* which phases have already completed (via GitHub issue comment markers)
|
|
168
|
+
* and removes them from the execution list.
|
|
169
|
+
*
|
|
170
|
+
* @param issueNumber - GitHub issue number
|
|
171
|
+
* @param phases - The phases to potentially filter
|
|
172
|
+
* @param resume - Whether the --resume flag is set
|
|
173
|
+
* @returns Object with filtered phases and any skipped phases
|
|
174
|
+
*/
|
|
175
|
+
export function filterResumedPhases(issueNumber, phases, resume) {
|
|
176
|
+
if (!resume) {
|
|
177
|
+
return { phases: [...phases], skipped: [] };
|
|
178
|
+
}
|
|
179
|
+
const resumable = getResumablePhasesForIssue(issueNumber, phases);
|
|
180
|
+
const skipped = phases.filter((p) => !resumable.includes(p));
|
|
181
|
+
return { phases: resumable, skipped };
|
|
182
|
+
}
|
|
162
183
|
/**
|
|
163
184
|
* Create or reuse a worktree for an issue
|
|
164
185
|
* @param baseBranch - Optional branch to use as base instead of origin/main (for chain mode)
|
|
@@ -1631,6 +1652,20 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1631
1652
|
console.log(chalk.gray(` Phases adjusted: ${phases.join(" → ")}`));
|
|
1632
1653
|
}
|
|
1633
1654
|
}
|
|
1655
|
+
// Resume: filter out completed phases if --resume flag is set
|
|
1656
|
+
if (options.resume) {
|
|
1657
|
+
const resumeResult = filterResumedPhases(issueNumber, phases, true);
|
|
1658
|
+
if (resumeResult.skipped.length > 0) {
|
|
1659
|
+
console.log(chalk.gray(` Resume: skipping completed phases: ${resumeResult.skipped.join(", ")}`));
|
|
1660
|
+
phases = resumeResult.phases;
|
|
1661
|
+
}
|
|
1662
|
+
// Also skip spec if it was auto-detected as completed
|
|
1663
|
+
if (specAlreadyRan &&
|
|
1664
|
+
resumeResult.skipped.length === 0 &&
|
|
1665
|
+
resumeResult.phases.length === 0) {
|
|
1666
|
+
console.log(chalk.gray(` Resume: all phases already completed`));
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1634
1669
|
// Add testgen phase if requested (and spec was in the phases)
|
|
1635
1670
|
if (options.testgen &&
|
|
1636
1671
|
(phases.includes("spec") || specAlreadyRan) &&
|
package/dist/src/index.d.ts
CHANGED
|
@@ -18,7 +18,8 @@ export type { SequantConfig } from "./lib/config.js";
|
|
|
18
18
|
export { StateManager, getStateManager } from "./lib/workflow/state-manager.js";
|
|
19
19
|
export type { StateManagerOptions } from "./lib/workflow/state-manager.js";
|
|
20
20
|
export { createEmptyState, createIssueState, createPhaseState, STATE_FILE_PATH, WORKFLOW_PHASES, } from "./lib/workflow/state-schema.js";
|
|
21
|
-
export type { WorkflowState, IssueState, PhaseState, Phase, PhaseStatus, IssueStatus, PRInfo, LoopState, } from "./lib/workflow/state-schema.js";
|
|
21
|
+
export type { WorkflowState, IssueState, PhaseState, Phase, PhaseStatus, IssueStatus, PRInfo, LoopState, PhaseMarker, } from "./lib/workflow/state-schema.js";
|
|
22
|
+
export { formatPhaseMarker, parsePhaseMarkers, detectPhaseFromComments, getPhaseMap, getCompletedPhasesFromComments, getResumablePhases, isPhaseCompletedOrPast, getIssuePhase, getCompletedPhases, getResumablePhasesForIssue, } from "./lib/workflow/phase-detection.js";
|
|
22
23
|
export { createStateHook, isOrchestrated, getOrchestrationContext, } from "./lib/workflow/state-hook.js";
|
|
23
24
|
export type { StateHook, StateHookOptions } from "./lib/workflow/state-hook.js";
|
|
24
25
|
export { rebuildStateFromLogs, cleanupStaleEntries, } from "./lib/workflow/state-utils.js";
|
package/dist/src/index.js
CHANGED
|
@@ -14,6 +14,8 @@ export { copyTemplates, listTemplateFiles, getTemplateContent, processTemplate,
|
|
|
14
14
|
// Workflow state exports
|
|
15
15
|
export { StateManager, getStateManager } from "./lib/workflow/state-manager.js";
|
|
16
16
|
export { createEmptyState, createIssueState, createPhaseState, STATE_FILE_PATH, WORKFLOW_PHASES, } from "./lib/workflow/state-schema.js";
|
|
17
|
+
// Phase detection exports
|
|
18
|
+
export { formatPhaseMarker, parsePhaseMarkers, detectPhaseFromComments, getPhaseMap, getCompletedPhasesFromComments, getResumablePhases, isPhaseCompletedOrPast, getIssuePhase, getCompletedPhases, getResumablePhasesForIssue, } from "./lib/workflow/phase-detection.js";
|
|
17
19
|
export { createStateHook, isOrchestrated, getOrchestrationContext, } from "./lib/workflow/state-hook.js";
|
|
18
20
|
export { rebuildStateFromLogs, cleanupStaleEntries, } from "./lib/workflow/state-utils.js";
|
|
19
21
|
// Content analysis exports
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub-based workflow phase detection for smart resumption.
|
|
3
|
+
*
|
|
4
|
+
* Reads phase markers from GitHub issue comments to detect workflow state
|
|
5
|
+
* across machines, sessions, and users. Enables skills and `sequant run`
|
|
6
|
+
* to resume from where they left off.
|
|
7
|
+
*
|
|
8
|
+
* Phase markers are embedded as HTML comments in issue comment bodies:
|
|
9
|
+
* ```
|
|
10
|
+
* <!-- SEQUANT_PHASE: {"phase":"exec","status":"completed","timestamp":"..."} -->
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { type Phase, type PhaseMarker } from "./state-schema.js";
|
|
14
|
+
/**
|
|
15
|
+
* Format a phase marker as an HTML comment string for embedding in GitHub comments.
|
|
16
|
+
*
|
|
17
|
+
* @param marker - The phase marker data
|
|
18
|
+
* @returns HTML comment string like `<!-- SEQUANT_PHASE: {...} -->`
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatPhaseMarker(marker: PhaseMarker): string;
|
|
21
|
+
/**
|
|
22
|
+
* Parse all phase markers from a single comment body.
|
|
23
|
+
*
|
|
24
|
+
* @param commentBody - The full body text of a GitHub comment
|
|
25
|
+
* @returns Array of parsed phase markers (empty if none found)
|
|
26
|
+
*/
|
|
27
|
+
export declare function parsePhaseMarkers(commentBody: string): PhaseMarker[];
|
|
28
|
+
/**
|
|
29
|
+
* Detect the latest phase from an array of comment bodies.
|
|
30
|
+
*
|
|
31
|
+
* Scans all comments for phase markers and returns the most recent one
|
|
32
|
+
* based on the timestamp field.
|
|
33
|
+
*
|
|
34
|
+
* @param comments - Array of objects with a `body` string field
|
|
35
|
+
* @returns The latest phase marker, or null if no markers found
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectPhaseFromComments(comments: {
|
|
38
|
+
body: string;
|
|
39
|
+
}[]): PhaseMarker | null;
|
|
40
|
+
/**
|
|
41
|
+
* Get all phase markers from issue comments, grouped by phase.
|
|
42
|
+
*
|
|
43
|
+
* Returns the latest marker for each phase that has been recorded.
|
|
44
|
+
*
|
|
45
|
+
* @param comments - Array of comment bodies
|
|
46
|
+
* @returns Map of phase → latest marker for that phase
|
|
47
|
+
*/
|
|
48
|
+
export declare function getPhaseMap(comments: {
|
|
49
|
+
body: string;
|
|
50
|
+
}[]): Map<Phase, PhaseMarker>;
|
|
51
|
+
/**
|
|
52
|
+
* Get list of phases that have been completed for an issue.
|
|
53
|
+
*
|
|
54
|
+
* @param comments - Array of comment bodies
|
|
55
|
+
* @returns Array of phase names that have status "completed"
|
|
56
|
+
*/
|
|
57
|
+
export declare function getCompletedPhasesFromComments(comments: {
|
|
58
|
+
body: string;
|
|
59
|
+
}[]): Phase[];
|
|
60
|
+
/**
|
|
61
|
+
* Determine which phases to run based on completed phases and requested phases.
|
|
62
|
+
*
|
|
63
|
+
* Filters out phases that are already completed. If a phase failed,
|
|
64
|
+
* it is kept in the list (for retry).
|
|
65
|
+
*
|
|
66
|
+
* @param requestedPhases - The phases the user wants to run
|
|
67
|
+
* @param comments - Array of comment bodies from the issue
|
|
68
|
+
* @returns Filtered array of phases that still need to run
|
|
69
|
+
*/
|
|
70
|
+
export declare function getResumablePhases(requestedPhases: readonly string[], comments: {
|
|
71
|
+
body: string;
|
|
72
|
+
}[]): string[];
|
|
73
|
+
/**
|
|
74
|
+
* Check if a specific phase has been reached or passed.
|
|
75
|
+
*
|
|
76
|
+
* Uses WORKFLOW_PHASES ordering to determine if the target phase
|
|
77
|
+
* is at or before the latest completed phase.
|
|
78
|
+
*
|
|
79
|
+
* @param targetPhase - The phase to check
|
|
80
|
+
* @param comments - Array of comment bodies
|
|
81
|
+
* @returns true if targetPhase has been completed or a later phase has been completed
|
|
82
|
+
*/
|
|
83
|
+
export declare function isPhaseCompletedOrPast(targetPhase: Phase, comments: {
|
|
84
|
+
body: string;
|
|
85
|
+
}[]): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Get the current phase status for an issue from GitHub comments.
|
|
88
|
+
*
|
|
89
|
+
* Calls `gh` CLI to fetch comments and parse phase markers.
|
|
90
|
+
*
|
|
91
|
+
* @param issueNumber - GitHub issue number
|
|
92
|
+
* @returns Latest phase marker, or null if no markers found or on error
|
|
93
|
+
*/
|
|
94
|
+
export declare function getIssuePhase(issueNumber: number): PhaseMarker | null;
|
|
95
|
+
/**
|
|
96
|
+
* Get completed phases for an issue from GitHub comments.
|
|
97
|
+
*
|
|
98
|
+
* Calls `gh` CLI to fetch comments and extract completed phases.
|
|
99
|
+
*
|
|
100
|
+
* @param issueNumber - GitHub issue number
|
|
101
|
+
* @returns Array of completed phase names, or empty array on error
|
|
102
|
+
*/
|
|
103
|
+
export declare function getCompletedPhases(issueNumber: number): Phase[];
|
|
104
|
+
/**
|
|
105
|
+
* Get resumable phases for an issue from GitHub comments.
|
|
106
|
+
*
|
|
107
|
+
* Convenience wrapper that fetches comments via `gh` CLI and
|
|
108
|
+
* filters requested phases by completed status.
|
|
109
|
+
*
|
|
110
|
+
* @param issueNumber - GitHub issue number
|
|
111
|
+
* @param requestedPhases - The phases the user wants to run
|
|
112
|
+
* @returns Filtered phases that still need to run
|
|
113
|
+
*/
|
|
114
|
+
export declare function getResumablePhasesForIssue(issueNumber: number, requestedPhases: readonly string[]): string[];
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub-based workflow phase detection for smart resumption.
|
|
3
|
+
*
|
|
4
|
+
* Reads phase markers from GitHub issue comments to detect workflow state
|
|
5
|
+
* across machines, sessions, and users. Enables skills and `sequant run`
|
|
6
|
+
* to resume from where they left off.
|
|
7
|
+
*
|
|
8
|
+
* Phase markers are embedded as HTML comments in issue comment bodies:
|
|
9
|
+
* ```
|
|
10
|
+
* <!-- SEQUANT_PHASE: {"phase":"exec","status":"completed","timestamp":"..."} -->
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
import { PhaseMarkerSchema, WORKFLOW_PHASES, } from "./state-schema.js";
|
|
15
|
+
/** Regex to extract phase marker JSON from HTML comments */
|
|
16
|
+
const PHASE_MARKER_REGEX = /<!-- SEQUANT_PHASE: (\{[^}]+\}) -->/g;
|
|
17
|
+
/**
|
|
18
|
+
* Format a phase marker as an HTML comment string for embedding in GitHub comments.
|
|
19
|
+
*
|
|
20
|
+
* @param marker - The phase marker data
|
|
21
|
+
* @returns HTML comment string like `<!-- SEQUANT_PHASE: {...} -->`
|
|
22
|
+
*/
|
|
23
|
+
export function formatPhaseMarker(marker) {
|
|
24
|
+
return `<!-- SEQUANT_PHASE: ${JSON.stringify(marker)} -->`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse all phase markers from a single comment body.
|
|
28
|
+
*
|
|
29
|
+
* @param commentBody - The full body text of a GitHub comment
|
|
30
|
+
* @returns Array of parsed phase markers (empty if none found)
|
|
31
|
+
*/
|
|
32
|
+
export function parsePhaseMarkers(commentBody) {
|
|
33
|
+
const markers = [];
|
|
34
|
+
// Reset regex state for reuse
|
|
35
|
+
PHASE_MARKER_REGEX.lastIndex = 0;
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = PHASE_MARKER_REGEX.exec(commentBody)) !== null) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(match[1]);
|
|
40
|
+
const result = PhaseMarkerSchema.safeParse(parsed);
|
|
41
|
+
if (result.success) {
|
|
42
|
+
markers.push(result.data);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Malformed JSON — skip silently
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return markers;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Detect the latest phase from an array of comment bodies.
|
|
53
|
+
*
|
|
54
|
+
* Scans all comments for phase markers and returns the most recent one
|
|
55
|
+
* based on the timestamp field.
|
|
56
|
+
*
|
|
57
|
+
* @param comments - Array of objects with a `body` string field
|
|
58
|
+
* @returns The latest phase marker, or null if no markers found
|
|
59
|
+
*/
|
|
60
|
+
export function detectPhaseFromComments(comments) {
|
|
61
|
+
const allMarkers = [];
|
|
62
|
+
for (const comment of comments) {
|
|
63
|
+
const markers = parsePhaseMarkers(comment.body);
|
|
64
|
+
allMarkers.push(...markers);
|
|
65
|
+
}
|
|
66
|
+
if (allMarkers.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
// Sort by timestamp descending, return latest
|
|
70
|
+
allMarkers.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
71
|
+
return allMarkers[0];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get all phase markers from issue comments, grouped by phase.
|
|
75
|
+
*
|
|
76
|
+
* Returns the latest marker for each phase that has been recorded.
|
|
77
|
+
*
|
|
78
|
+
* @param comments - Array of comment bodies
|
|
79
|
+
* @returns Map of phase → latest marker for that phase
|
|
80
|
+
*/
|
|
81
|
+
export function getPhaseMap(comments) {
|
|
82
|
+
const phaseMap = new Map();
|
|
83
|
+
for (const comment of comments) {
|
|
84
|
+
const markers = parsePhaseMarkers(comment.body);
|
|
85
|
+
for (const marker of markers) {
|
|
86
|
+
const existing = phaseMap.get(marker.phase);
|
|
87
|
+
if (!existing ||
|
|
88
|
+
new Date(marker.timestamp).getTime() >
|
|
89
|
+
new Date(existing.timestamp).getTime()) {
|
|
90
|
+
phaseMap.set(marker.phase, marker);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return phaseMap;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get list of phases that have been completed for an issue.
|
|
98
|
+
*
|
|
99
|
+
* @param comments - Array of comment bodies
|
|
100
|
+
* @returns Array of phase names that have status "completed"
|
|
101
|
+
*/
|
|
102
|
+
export function getCompletedPhasesFromComments(comments) {
|
|
103
|
+
const phaseMap = getPhaseMap(comments);
|
|
104
|
+
const completed = [];
|
|
105
|
+
for (const phase of WORKFLOW_PHASES) {
|
|
106
|
+
const marker = phaseMap.get(phase);
|
|
107
|
+
if (marker && marker.status === "completed") {
|
|
108
|
+
completed.push(phase);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return completed;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Determine which phases to run based on completed phases and requested phases.
|
|
115
|
+
*
|
|
116
|
+
* Filters out phases that are already completed. If a phase failed,
|
|
117
|
+
* it is kept in the list (for retry).
|
|
118
|
+
*
|
|
119
|
+
* @param requestedPhases - The phases the user wants to run
|
|
120
|
+
* @param comments - Array of comment bodies from the issue
|
|
121
|
+
* @returns Filtered array of phases that still need to run
|
|
122
|
+
*/
|
|
123
|
+
export function getResumablePhases(requestedPhases, comments) {
|
|
124
|
+
const completedPhases = new Set(getCompletedPhasesFromComments(comments));
|
|
125
|
+
return requestedPhases.filter((phase) => !completedPhases.has(phase));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if a specific phase has been reached or passed.
|
|
129
|
+
*
|
|
130
|
+
* Uses WORKFLOW_PHASES ordering to determine if the target phase
|
|
131
|
+
* is at or before the latest completed phase.
|
|
132
|
+
*
|
|
133
|
+
* @param targetPhase - The phase to check
|
|
134
|
+
* @param comments - Array of comment bodies
|
|
135
|
+
* @returns true if targetPhase has been completed or a later phase has been completed
|
|
136
|
+
*/
|
|
137
|
+
export function isPhaseCompletedOrPast(targetPhase, comments) {
|
|
138
|
+
const phaseMap = getPhaseMap(comments);
|
|
139
|
+
const targetIndex = WORKFLOW_PHASES.indexOf(targetPhase);
|
|
140
|
+
// Check if target phase itself is completed
|
|
141
|
+
const targetMarker = phaseMap.get(targetPhase);
|
|
142
|
+
if (targetMarker && targetMarker.status === "completed") {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// Check if any later phase is completed (implies target was completed)
|
|
146
|
+
for (let i = targetIndex + 1; i < WORKFLOW_PHASES.length; i++) {
|
|
147
|
+
const laterPhase = WORKFLOW_PHASES[i];
|
|
148
|
+
const laterMarker = phaseMap.get(laterPhase);
|
|
149
|
+
if (laterMarker && laterMarker.status === "completed") {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the current phase status for an issue from GitHub comments.
|
|
157
|
+
*
|
|
158
|
+
* Calls `gh` CLI to fetch comments and parse phase markers.
|
|
159
|
+
*
|
|
160
|
+
* @param issueNumber - GitHub issue number
|
|
161
|
+
* @returns Latest phase marker, or null if no markers found or on error
|
|
162
|
+
*/
|
|
163
|
+
export function getIssuePhase(issueNumber) {
|
|
164
|
+
try {
|
|
165
|
+
const output = execSync(`gh issue view ${issueNumber} --json comments --jq '[.comments[].body]'`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
166
|
+
const bodies = JSON.parse(output);
|
|
167
|
+
const comments = bodies.map((body) => ({ body }));
|
|
168
|
+
return detectPhaseFromComments(comments);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// GitHub CLI failure — fall through to normal execution
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get completed phases for an issue from GitHub comments.
|
|
177
|
+
*
|
|
178
|
+
* Calls `gh` CLI to fetch comments and extract completed phases.
|
|
179
|
+
*
|
|
180
|
+
* @param issueNumber - GitHub issue number
|
|
181
|
+
* @returns Array of completed phase names, or empty array on error
|
|
182
|
+
*/
|
|
183
|
+
export function getCompletedPhases(issueNumber) {
|
|
184
|
+
try {
|
|
185
|
+
const output = execSync(`gh issue view ${issueNumber} --json comments --jq '[.comments[].body]'`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
186
|
+
const bodies = JSON.parse(output);
|
|
187
|
+
const comments = bodies.map((body) => ({ body }));
|
|
188
|
+
return getCompletedPhasesFromComments(comments);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get resumable phases for an issue from GitHub comments.
|
|
196
|
+
*
|
|
197
|
+
* Convenience wrapper that fetches comments via `gh` CLI and
|
|
198
|
+
* filters requested phases by completed status.
|
|
199
|
+
*
|
|
200
|
+
* @param issueNumber - GitHub issue number
|
|
201
|
+
* @param requestedPhases - The phases the user wants to run
|
|
202
|
+
* @returns Filtered phases that still need to run
|
|
203
|
+
*/
|
|
204
|
+
export function getResumablePhasesForIssue(issueNumber, requestedPhases) {
|
|
205
|
+
try {
|
|
206
|
+
const output = execSync(`gh issue view ${issueNumber} --json comments --jq '[.comments[].body]'`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
207
|
+
const bodies = JSON.parse(output);
|
|
208
|
+
const comments = bodies.map((body) => ({ body }));
|
|
209
|
+
return getResumablePhases(requestedPhases, comments);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// On error, return all phases (no filtering)
|
|
213
|
+
return [...requestedPhases];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -60,6 +60,35 @@ export declare const PhaseSchema: z.ZodEnum<{
|
|
|
60
60
|
merger: "merger";
|
|
61
61
|
}>;
|
|
62
62
|
export type Phase = z.infer<typeof PhaseSchema>;
|
|
63
|
+
/**
|
|
64
|
+
* Phase marker stored in GitHub issue comments for cross-session detection.
|
|
65
|
+
*
|
|
66
|
+
* Embedded as HTML comments: `<!-- SEQUANT_PHASE: {...} -->`
|
|
67
|
+
*/
|
|
68
|
+
export declare const PhaseMarkerSchema: z.ZodObject<{
|
|
69
|
+
phase: z.ZodEnum<{
|
|
70
|
+
loop: "loop";
|
|
71
|
+
verify: "verify";
|
|
72
|
+
spec: "spec";
|
|
73
|
+
exec: "exec";
|
|
74
|
+
qa: "qa";
|
|
75
|
+
"security-review": "security-review";
|
|
76
|
+
testgen: "testgen";
|
|
77
|
+
test: "test";
|
|
78
|
+
merger: "merger";
|
|
79
|
+
}>;
|
|
80
|
+
status: z.ZodEnum<{
|
|
81
|
+
pending: "pending";
|
|
82
|
+
skipped: "skipped";
|
|
83
|
+
completed: "completed";
|
|
84
|
+
in_progress: "in_progress";
|
|
85
|
+
failed: "failed";
|
|
86
|
+
}>;
|
|
87
|
+
timestamp: z.ZodString;
|
|
88
|
+
pr: z.ZodOptional<z.ZodNumber>;
|
|
89
|
+
error: z.ZodOptional<z.ZodString>;
|
|
90
|
+
}, z.core.$strip>;
|
|
91
|
+
export type PhaseMarker = z.infer<typeof PhaseMarkerSchema>;
|
|
63
92
|
/**
|
|
64
93
|
* Individual phase state within an issue
|
|
65
94
|
*/
|
|
@@ -68,6 +68,23 @@ export const PhaseSchema = z.enum([
|
|
|
68
68
|
"loop",
|
|
69
69
|
"merger",
|
|
70
70
|
]);
|
|
71
|
+
/**
|
|
72
|
+
* Phase marker stored in GitHub issue comments for cross-session detection.
|
|
73
|
+
*
|
|
74
|
+
* Embedded as HTML comments: `<!-- SEQUANT_PHASE: {...} -->`
|
|
75
|
+
*/
|
|
76
|
+
export const PhaseMarkerSchema = z.object({
|
|
77
|
+
/** The workflow phase */
|
|
78
|
+
phase: PhaseSchema,
|
|
79
|
+
/** Phase completion status */
|
|
80
|
+
status: PhaseStatusSchema,
|
|
81
|
+
/** ISO 8601 timestamp */
|
|
82
|
+
timestamp: z.string().datetime(),
|
|
83
|
+
/** PR number if created during this phase */
|
|
84
|
+
pr: z.number().int().positive().optional(),
|
|
85
|
+
/** Error message if phase failed */
|
|
86
|
+
error: z.string().optional(),
|
|
87
|
+
});
|
|
71
88
|
/**
|
|
72
89
|
* Individual phase state within an issue
|
|
73
90
|
*/
|
package/package.json
CHANGED
|
@@ -37,7 +37,7 @@ allowed-tools:
|
|
|
37
37
|
- mcp__context7__* # Library documentation lookup - falls back to web search if unavailable
|
|
38
38
|
- mcp__sequential-thinking__* # Complex reasoning - falls back to standard analysis if unavailable
|
|
39
39
|
# Task management
|
|
40
|
-
- Task
|
|
40
|
+
- Task(general-purpose)
|
|
41
41
|
- TodoWrite
|
|
42
42
|
---
|
|
43
43
|
|
|
@@ -56,6 +56,55 @@ When invoked as `/exec`, your job is to:
|
|
|
56
56
|
5. Iterate until the AC appear satisfied or clear blockers are reached.
|
|
57
57
|
6. Draft a progress update for the GitHub issue.
|
|
58
58
|
|
|
59
|
+
## Phase Detection (Smart Resumption)
|
|
60
|
+
|
|
61
|
+
**Before executing**, check if this phase has already been completed or if prerequisites are met:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Check for existing phase markers
|
|
65
|
+
phase_data=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]' | \
|
|
66
|
+
grep -o '{[^}]*}' | grep '"phase"' | tail -1)
|
|
67
|
+
|
|
68
|
+
if [[ -n "$phase_data" ]]; then
|
|
69
|
+
phase=$(echo "$phase_data" | jq -r '.phase')
|
|
70
|
+
status=$(echo "$phase_data" | jq -r '.status')
|
|
71
|
+
|
|
72
|
+
# Skip if exec is already completed
|
|
73
|
+
if [[ "$phase" == "exec" && "$status" == "completed" ]]; then
|
|
74
|
+
echo "⏭️ Exec phase already completed. Skipping."
|
|
75
|
+
# Exit early — no work needed
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Resume if exec previously failed
|
|
79
|
+
if [[ "$phase" == "exec" && "$status" == "failed" ]]; then
|
|
80
|
+
echo "🔄 Exec phase previously failed. Resuming from failure point."
|
|
81
|
+
# Continue execution — will retry the implementation
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Behavior:**
|
|
87
|
+
- If `exec:completed` → Skip with message
|
|
88
|
+
- If `exec:failed` → Resume (retry implementation)
|
|
89
|
+
- If `spec:completed` (no exec marker) → Normal execution
|
|
90
|
+
- If no markers found → Normal execution (fresh start)
|
|
91
|
+
- If detection fails (API error) → Fall through to normal execution
|
|
92
|
+
|
|
93
|
+
**Phase Marker Emission:**
|
|
94
|
+
|
|
95
|
+
When posting the progress update comment to GitHub, append a phase marker at the end:
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
<!-- SEQUANT_PHASE: {"phase":"exec","status":"completed","timestamp":"<ISO-8601>","pr":<PR_NUMBER>} -->
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If exec fails, emit a failure marker:
|
|
102
|
+
```markdown
|
|
103
|
+
<!-- SEQUANT_PHASE: {"phase":"exec","status":"failed","timestamp":"<ISO-8601>","error":"<error message>"} -->
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Include this marker in every `gh issue comment` that represents phase completion or failure.
|
|
107
|
+
|
|
59
108
|
## Behavior
|
|
60
109
|
|
|
61
110
|
Invocation:
|
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
- Grep
|
|
14
14
|
- Bash
|
|
15
15
|
- TodoWrite
|
|
16
|
-
-
|
|
16
|
+
- Skill # For invoking child skills (/spec, /exec, /test, /qa)
|
|
17
17
|
# Optional MCP tools (enhanced functionality if available)
|
|
18
18
|
- mcp__chrome-devtools__* # Browser testing - falls back to manual checklist if unavailable
|
|
19
19
|
- mcp__sequential-thinking__* # Complex reasoning - falls back to standard analysis if unavailable
|
|
@@ -90,6 +90,52 @@ When invoked as `/fullsolve <issue-number>`, execute the complete issue resoluti
|
|
|
90
90
|
/fullsolve 218 --max-iterations 5 # Override max fix iterations
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
## Phase Detection (Smart Resumption)
|
|
94
|
+
|
|
95
|
+
**Before starting any phase**, detect the current workflow state from GitHub issue comments to enable smart resumption:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Get all phase markers from issue comments
|
|
99
|
+
comments_json=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]')
|
|
100
|
+
markers=$(echo "$comments_json" | grep -o '{[^}]*}' | grep '"phase"')
|
|
101
|
+
|
|
102
|
+
if [[ -n "$markers" ]]; then
|
|
103
|
+
echo "Phase markers detected:"
|
|
104
|
+
echo "$markers" | jq -r '" \(.phase): \(.status)"'
|
|
105
|
+
|
|
106
|
+
# Determine resume point
|
|
107
|
+
latest_completed=$(echo "$markers" | jq -r 'select(.status == "completed") | .phase' | tail -1)
|
|
108
|
+
latest_failed=$(echo "$markers" | jq -r 'select(.status == "failed") | .phase' | tail -1)
|
|
109
|
+
|
|
110
|
+
echo "Latest completed: ${latest_completed:-none}"
|
|
111
|
+
echo "Latest failed: ${latest_failed:-none}"
|
|
112
|
+
fi
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Resume Logic:**
|
|
116
|
+
|
|
117
|
+
| Detected State | Action |
|
|
118
|
+
|---------------|--------|
|
|
119
|
+
| No markers | Start from Phase 1 (spec) — fresh start |
|
|
120
|
+
| `spec:completed` | Skip to Phase 2 (exec) |
|
|
121
|
+
| `exec:completed` | Skip to Phase 3 (test) or Phase 4 (qa) |
|
|
122
|
+
| `exec:failed` | Resume at Phase 2 (exec) — retry |
|
|
123
|
+
| `test:completed` | Skip to Phase 4 (qa) |
|
|
124
|
+
| `qa:completed` | Skip to Phase 5 (PR) |
|
|
125
|
+
| `qa:failed` | Resume at Phase 4 (qa) — retry with /loop |
|
|
126
|
+
| All completed | Skip to PR creation (if no PR exists) |
|
|
127
|
+
|
|
128
|
+
**Backward Compatibility:**
|
|
129
|
+
- Issues without markers → treat as fresh start (no phase detection)
|
|
130
|
+
- If detection fails (API error) → fall through to standard Phase 0 checks
|
|
131
|
+
|
|
132
|
+
**Phase Marker Emission:**
|
|
133
|
+
|
|
134
|
+
When posting progress comments after each phase, append the appropriate marker:
|
|
135
|
+
```markdown
|
|
136
|
+
<!-- SEQUANT_PHASE: {"phase":"<phase>","status":"<completed|failed>","timestamp":"<ISO-8601>"} -->
|
|
137
|
+
```
|
|
138
|
+
|
|
93
139
|
## Phase 0: Pre-flight Checks
|
|
94
140
|
|
|
95
141
|
**CRITICAL after context restoration:** Before starting any work, verify the current git state to avoid duplicate work.
|
|
@@ -19,7 +19,7 @@ allowed-tools:
|
|
|
19
19
|
- Bash(semgrep:*)
|
|
20
20
|
- Bash(npx semgrep:*)
|
|
21
21
|
- Bash(npx tsx scripts/semgrep-scan.ts:*)
|
|
22
|
-
- Task
|
|
22
|
+
- Task(general-purpose)
|
|
23
23
|
- AgentOutputTool
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -37,6 +37,52 @@ When invoked as `/qa`, your job is to:
|
|
|
37
37
|
4. Assess whether the change is "A+ status" or needs more work.
|
|
38
38
|
5. Draft a GitHub review/QA comment summarizing findings and recommendations.
|
|
39
39
|
|
|
40
|
+
## Phase Detection (Smart Resumption)
|
|
41
|
+
|
|
42
|
+
**Before executing**, check if the exec phase has been completed (prerequisite for QA):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Check for existing phase markers
|
|
46
|
+
comments_json=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]')
|
|
47
|
+
exec_completed=$(echo "$comments_json" | \
|
|
48
|
+
grep -o '{[^}]*}' | grep '"phase"' | \
|
|
49
|
+
jq -r 'select(.phase == "exec" and .status == "completed")' 2>/dev/null)
|
|
50
|
+
|
|
51
|
+
if [[ -z "$exec_completed" ]]; then
|
|
52
|
+
# Check if any exec marker exists at all
|
|
53
|
+
exec_any=$(echo "$comments_json" | \
|
|
54
|
+
grep -o '{[^}]*}' | grep '"phase"' | \
|
|
55
|
+
jq -r 'select(.phase == "exec")' 2>/dev/null)
|
|
56
|
+
|
|
57
|
+
if [[ -n "$exec_any" ]]; then
|
|
58
|
+
echo "⚠️ Exec phase not completed (status: $(echo "$exec_any" | jq -r '.status')). Run /exec first."
|
|
59
|
+
else
|
|
60
|
+
echo "ℹ️ No phase markers found — proceeding with QA (may be a fresh issue or legacy workflow)."
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Behavior:**
|
|
66
|
+
- If `exec:completed` marker found → Normal QA execution
|
|
67
|
+
- If `exec:failed` or `exec:in_progress` → Warn "Exec not complete, run /exec first" (but don't block — QA may still be useful for partial review)
|
|
68
|
+
- If no markers found → Normal execution (backward compatible)
|
|
69
|
+
- If detection fails (API error) → Fall through to normal execution
|
|
70
|
+
|
|
71
|
+
**Phase Marker Emission:**
|
|
72
|
+
|
|
73
|
+
When posting the QA review comment to GitHub, append a phase marker at the end:
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
<!-- SEQUANT_PHASE: {"phase":"qa","status":"completed","timestamp":"<ISO-8601>"} -->
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If QA determines AC_NOT_MET, emit:
|
|
80
|
+
```markdown
|
|
81
|
+
<!-- SEQUANT_PHASE: {"phase":"qa","status":"failed","timestamp":"<ISO-8601>","error":"AC_NOT_MET"} -->
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Include this marker in every `gh issue comment` that represents QA completion.
|
|
85
|
+
|
|
40
86
|
## Behavior
|
|
41
87
|
|
|
42
88
|
Invocation:
|
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
- Bash(gh label:*)
|
|
14
14
|
- Bash(git worktree:*)
|
|
15
15
|
- Bash(git -C:*)
|
|
16
|
-
- Task
|
|
16
|
+
- Task(Explore)
|
|
17
17
|
- AgentOutputTool
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -30,6 +30,44 @@ When invoked as `/spec`, your job is to:
|
|
|
30
30
|
3. Identify ambiguities, gaps, or risks.
|
|
31
31
|
4. Draft a GitHub issue comment summarizing AC + the agreed plan.
|
|
32
32
|
|
|
33
|
+
## Phase Detection (Smart Resumption)
|
|
34
|
+
|
|
35
|
+
**Before executing**, check if this phase has already been completed by reading phase markers from issue comments:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Check for existing phase markers
|
|
39
|
+
phase_data=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]' | \
|
|
40
|
+
grep -o '{[^}]*}' | grep '"phase"' | tail -1)
|
|
41
|
+
|
|
42
|
+
if [[ -n "$phase_data" ]]; then
|
|
43
|
+
phase=$(echo "$phase_data" | jq -r '.phase')
|
|
44
|
+
status=$(echo "$phase_data" | jq -r '.status')
|
|
45
|
+
|
|
46
|
+
# Skip if spec is already completed or a later phase is completed
|
|
47
|
+
if [[ "$phase" == "spec" && "$status" == "completed" ]] || \
|
|
48
|
+
[[ "$phase" == "exec" || "$phase" == "test" || "$phase" == "qa" ]]; then
|
|
49
|
+
echo "⏭️ Spec phase already completed (detected: $phase:$status). Skipping."
|
|
50
|
+
# Exit early — no work needed
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Behavior:**
|
|
56
|
+
- If `spec:completed` or a later phase is detected → Skip with message
|
|
57
|
+
- If `spec:failed` → Re-run spec (retry)
|
|
58
|
+
- If no markers found → Normal execution (fresh start)
|
|
59
|
+
- If detection fails (API error) → Fall through to normal execution
|
|
60
|
+
|
|
61
|
+
**Phase Marker Emission:**
|
|
62
|
+
|
|
63
|
+
When posting the spec plan comment to GitHub, append a phase marker at the end:
|
|
64
|
+
|
|
65
|
+
```markdown
|
|
66
|
+
<!-- SEQUANT_PHASE: {"phase":"spec","status":"completed","timestamp":"<ISO-8601>"} -->
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Include this marker in every `gh issue comment` that represents phase completion.
|
|
70
|
+
|
|
33
71
|
## Behavior
|
|
34
72
|
|
|
35
73
|
When called like `/spec 123`:
|