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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sequant",
3
3
  "description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases",
4
- "version": "1.14.0",
4
+ "version": "1.14.2",
5
5
  "author": {
6
6
  "name": "admarble",
7
7
  "email": "github@admarble.com"
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
@@ -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) &&
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "1.14.0",
3
+ "version": "1.14.2",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- - Task
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:
@@ -9,7 +9,6 @@ allowed-tools:
9
9
  - Read
10
10
  - Glob
11
11
  - Grep
12
- - Task
13
12
  - Bash(git diff:*)
14
13
  - Bash(git status:*)
15
14
  - Bash(git log:*)
@@ -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`:
@@ -17,6 +17,7 @@ allowed-tools:
17
17
  - Bash(git worktree list:*)
18
18
  - Bash(ls:*)
19
19
  - Bash(mkdir:*)
20
+ - Task(general-purpose)
20
21
  ---
21
22
 
22
23
  # Test Generation Command