sequant 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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": "2.0.0",
4
+ "version": "2.0.1",
5
5
  "author": {
6
6
  "name": "sequant-io",
7
7
  "email": "hello@sequant.io"
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sequant
2
2
 
3
- **Workflow automation for [Claude Code](https://claude.ai/code).**
3
+ **Workflow automation for AI coding agents.**
4
4
 
5
5
  Solve GitHub issues with structured phases and quality gates — from issue to merge-ready PR.
6
6
 
@@ -64,8 +64,9 @@ Or step-by-step:
64
64
 
65
65
  ### Prerequisites
66
66
 
67
- **Required:**
68
- - [Claude Code](https://claude.ai/code) — AI coding assistant
67
+ **Required (one of):**
68
+ - [Claude Code](https://claude.ai/code) — default agent
69
+ - [Aider](https://aider.chat/) — alternative via `--agent aider`
69
70
  - [GitHub CLI](https://cli.github.com/) — run `gh auth login`
70
71
  - Git (for worktree-based isolation)
71
72
 
@@ -83,7 +84,7 @@ Or step-by-step:
83
84
 
84
85
  ## How It Works
85
86
 
86
- Sequant adds slash commands to Claude Code that enforce a structured workflow:
87
+ Sequant enforces a structured workflow through slash commands (interactive) or CLI (headless):
87
88
 
88
89
  ```
89
90
  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
@@ -133,9 +134,9 @@ When checks fail, `/loop` automatically fixes and re-runs (up to 3x).
133
134
 
134
135
  ## Two Ways to Use
135
136
 
136
- ### Interactive (Claude Code)
137
+ ### Interactive (Slash Commands)
137
138
 
138
- Type commands directly in Claude Code chat:
139
+ Type commands in Claude Code or any MCP-connected client:
139
140
 
140
141
  ```
141
142
  /fullsolve 123 # Complete pipeline
@@ -7,7 +7,7 @@
7
7
  * (including quality-loop retries, checkpoint commits, rebasing, and PR
8
8
  * creation).
9
9
  */
10
- import { IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext } from "./types.js";
10
+ import { PhaseResult, IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext } from "./types.js";
11
11
  export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutionContext, } from "./types.js";
12
12
  /**
13
13
  * Emit a structured progress line to stderr for MCP progress notifications.
@@ -19,6 +19,14 @@ export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutio
19
19
  * @param event - Phase lifecycle event: "start", "complete", or "failed"
20
20
  * @param extra - Optional fields: durationSeconds (on complete), error (on failed)
21
21
  */
22
+ /**
23
+ * Build enriched prompt context for the /loop phase from a failed phase result (#488).
24
+ * Passes QA verdict, failed ACs, and error directly so the /loop skill doesn't need
25
+ * to reconstruct context from GitHub comments (which fails in subprocess).
26
+ *
27
+ * @internal Exported for testing only
28
+ */
29
+ export declare function buildLoopContext(failedResult: PhaseResult): string;
22
30
  export declare function emitProgressLine(issue: number, phase: string, event?: "start" | "complete" | "failed", extra?: {
23
31
  durationSeconds?: number;
24
32
  error?: string;
@@ -26,6 +26,34 @@ import { detectPhasesFromLabels, parseRecommendedWorkflow, determinePhasesForIss
26
26
  * @param event - Phase lifecycle event: "start", "complete", or "failed"
27
27
  * @param extra - Optional fields: durationSeconds (on complete), error (on failed)
28
28
  */
29
+ /**
30
+ * Build enriched prompt context for the /loop phase from a failed phase result (#488).
31
+ * Passes QA verdict, failed ACs, and error directly so the /loop skill doesn't need
32
+ * to reconstruct context from GitHub comments (which fails in subprocess).
33
+ *
34
+ * @internal Exported for testing only
35
+ */
36
+ export function buildLoopContext(failedResult) {
37
+ const parts = [`Previous phase "${failedResult.phase}" failed.`];
38
+ if (failedResult.verdict) {
39
+ parts.push(`QA Verdict: ${failedResult.verdict}`);
40
+ }
41
+ if (failedResult.summary?.gaps?.length) {
42
+ parts.push(`QA Gaps:\n${failedResult.summary.gaps.map((gap) => `- ${gap}`).join("\n")}`);
43
+ }
44
+ if (failedResult.summary?.suggestions?.length) {
45
+ parts.push(`Suggestions:\n${failedResult.summary.suggestions.map((s) => `- ${s}`).join("\n")}`);
46
+ }
47
+ if (failedResult.error) {
48
+ parts.push(`Error: ${failedResult.error}`);
49
+ }
50
+ // Include tail of output for additional context (truncated to avoid prompt bloat)
51
+ if (failedResult.output) {
52
+ const tail = failedResult.output.slice(-2000);
53
+ parts.push(`Last output:\n${tail}`);
54
+ }
55
+ return parts.join("\n\n");
56
+ }
29
57
  export function emitProgressLine(issue, phase, event = "start", extra) {
30
58
  if (!process.env.SEQUANT_ORCHESTRATOR)
31
59
  return;
@@ -634,8 +662,17 @@ export async function runIssueWithLogging(ctx) {
634
662
  catch {
635
663
  /* progress errors must not halt */
636
664
  }
665
+ // Build enriched config for loop phase with QA context (#488).
666
+ // Pass verdict, failed ACs, and error directly so the /loop skill
667
+ // doesn't need to reconstruct context from GitHub comments.
668
+ const loopConfig = {
669
+ ...issueConfig,
670
+ lastVerdict: result.verdict ?? undefined,
671
+ failedAcs: result.summary?.gaps?.join("; ") ?? undefined,
672
+ promptContext: buildLoopContext(result),
673
+ };
637
674
  const loopStartTime = new Date();
638
- const loopResult = await executePhaseWithRetry(issueNumber, "loop", issueConfig, sessionId, worktreePath, shutdownManager, loopSpinner);
675
+ const loopResult = await executePhaseWithRetry(issueNumber, "loop", loopConfig, sessionId, worktreePath, shutdownManager, loopSpinner);
639
676
  const loopEndTime = new Date();
640
677
  phaseResults.push(loopResult);
641
678
  // Emit loop completion/failure progress event (AC-8)
@@ -48,7 +48,7 @@ export declare function formatDuration(seconds: number): string;
48
48
  *
49
49
  * @internal Exported for testing only
50
50
  */
51
- export declare function getPhasePrompt(phase: Phase, issueNumber: number, agent?: string): Promise<string>;
51
+ export declare function getPhasePrompt(phase: Phase, issueNumber: number, agent?: string, promptContext?: string): Promise<string>;
52
52
  /**
53
53
  * Execute a single phase for an issue using the configured AgentDriver.
54
54
  */
@@ -224,9 +224,13 @@ export function formatDuration(seconds) {
224
224
  *
225
225
  * @internal Exported for testing only
226
226
  */
227
- export async function getPhasePrompt(phase, issueNumber, agent) {
227
+ export async function getPhasePrompt(phase, issueNumber, agent, promptContext) {
228
228
  const prompts = agent && agent !== "claude-code" ? AIDER_PHASE_PROMPTS : PHASE_PROMPTS;
229
- const basePrompt = prompts[phase].replace(/\{issue\}/g, String(issueNumber));
229
+ let basePrompt = prompts[phase].replace(/\{issue\}/g, String(issueNumber));
230
+ // Append phase-specific context (e.g., QA findings for loop phase)
231
+ if (promptContext) {
232
+ basePrompt += `\n\n---\n\n${promptContext}`;
233
+ }
230
234
  // Include AGENTS.md content in the prompt context for non-Claude agent compatibility.
231
235
  // Claude reads CLAUDE.md natively, but other agents (Aider, Codex, Gemini CLI)
232
236
  // rely on AGENTS.md for project context.
@@ -241,7 +245,7 @@ export async function getPhasePrompt(phase, issueNumber, agent) {
241
245
  */
242
246
  async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner) {
243
247
  const startTime = Date.now();
244
- const prompt = await getPhasePrompt(phase, issueNumber, config.agent);
248
+ const prompt = await getPhasePrompt(phase, issueNumber, config.agent, config.promptContext);
245
249
  if (config.dryRun) {
246
250
  // Dry run - show the prompt that would be sent, then return
247
251
  if (config.verbose) {
@@ -318,6 +322,13 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
318
322
  if (config.issueType) {
319
323
  env.SEQUANT_ISSUE_TYPE = config.issueType;
320
324
  }
325
+ // Pass QA context to loop phase so it doesn't need to reconstruct from GitHub (#488)
326
+ if (config.lastVerdict) {
327
+ env.SEQUANT_LAST_VERDICT = config.lastVerdict;
328
+ }
329
+ if (config.failedAcs) {
330
+ env.SEQUANT_FAILED_ACS = config.failedAcs;
331
+ }
321
332
  // Track whether we're actively streaming verbose output
322
333
  // Pausing spinner once per streaming session prevents truncation from rapid pause/resume cycles
323
334
  // (Issue #283: ora's stop() clears the current line, which can truncate output when
@@ -452,36 +463,51 @@ delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
452
463
  if (config.retry === false) {
453
464
  return executePhaseFn(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
454
465
  }
466
+ // Skip cold-start retries for `loop` phase (#488).
467
+ // Loop is always a re-run after a failed QA — never a first boot.
468
+ // Failures at 47-51s are genuine skill failures, not cold-start issues.
469
+ // Without this guard, 2 cold-start retries + 1 MCP fallback = 3 wasted spawns per loop.
470
+ const skipColdStartRetry = phase === "loop";
455
471
  let lastResult;
456
- // Phase 1: Cold-start retry attempts (with MCP enabled if configured)
457
- for (let attempt = 0; attempt <= COLD_START_MAX_RETRIES; attempt++) {
472
+ if (skipColdStartRetry) {
473
+ // Single attempt no cold-start retry loop
458
474
  lastResult = await executePhaseFn(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
459
- const duration = lastResult.durationSeconds ?? 0;
460
- // Success → return immediately
461
475
  if (lastResult.success) {
462
476
  return lastResult;
463
477
  }
464
- // Genuine failure (took long enough to be real work) → skip cold-start retries.
465
- // For spec phase, break to allow Phase 3 (spec-specific retry) to run.
466
- // For other phases, return immediately no further retries.
467
- if (duration >= COLD_START_THRESHOLD_SECONDS) {
468
- if (phase === "spec") {
469
- break;
478
+ }
479
+ else {
480
+ // Phase 1: Cold-start retry attempts (with MCP enabled if configured)
481
+ for (let attempt = 0; attempt <= COLD_START_MAX_RETRIES; attempt++) {
482
+ lastResult = await executePhaseFn(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
483
+ const duration = lastResult.durationSeconds ?? 0;
484
+ // Success → return immediately
485
+ if (lastResult.success) {
486
+ return lastResult;
470
487
  }
471
- return lastResult;
472
- }
473
- // Cold-start failure detectedretry
474
- if (attempt < COLD_START_MAX_RETRIES) {
475
- if (config.verbose) {
476
- console.log(chalk.yellow(`\n ⟳ Cold-start failure detected (${duration.toFixed(1)}s), retrying... (attempt ${attempt + 2}/${COLD_START_MAX_RETRIES + 1})`));
488
+ // Genuine failure (took long enough to be real work) → skip cold-start retries.
489
+ // For spec phase, break to allow Phase 3 (spec-specific retry) to run.
490
+ // For other phases, return immediately no further retries.
491
+ if (duration >= COLD_START_THRESHOLD_SECONDS) {
492
+ if (phase === "spec") {
493
+ break;
494
+ }
495
+ return lastResult;
496
+ }
497
+ // Cold-start failure detected — retry
498
+ if (attempt < COLD_START_MAX_RETRIES) {
499
+ if (config.verbose) {
500
+ console.log(chalk.yellow(`\n ⟳ Cold-start failure detected (${duration.toFixed(1)}s), retrying... (attempt ${attempt + 2}/${COLD_START_MAX_RETRIES + 1})`));
501
+ }
477
502
  }
478
503
  }
479
504
  }
480
505
  // Capture the original error for better diagnostics
481
506
  const originalError = lastResult.error;
482
507
  // Phase 2: MCP fallback - if MCP is enabled and we're still failing, try without MCP
483
- // This handles npx-based MCP servers that fail on first run due to cold-cache issues
484
- if (config.mcp && !lastResult.success) {
508
+ // This handles npx-based MCP servers that fail on first run due to cold-cache issues.
509
+ // Skip for `loop` phase — MCP is never the cause of loop failures (#488).
510
+ if (config.mcp && !lastResult.success && !skipColdStartRetry) {
485
511
  console.log(chalk.yellow(`\n ⚠️ Phase failed with MCP enabled, retrying without MCP...`));
486
512
  // Create config copy with MCP disabled
487
513
  const configWithoutMcp = {
@@ -80,6 +80,22 @@ export interface ExecutionConfig {
80
80
  * Propagated as SEQUANT_ISSUE_TYPE env var to skills.
81
81
  */
82
82
  issueType?: string;
83
+ /**
84
+ * Additional context appended to the phase prompt.
85
+ * Used by the quality loop to pass QA findings directly to the /loop skill
86
+ * so it doesn't need to reconstruct context from GitHub comments.
87
+ */
88
+ promptContext?: string;
89
+ /**
90
+ * Last QA verdict from a preceding phase.
91
+ * Propagated as SEQUANT_LAST_VERDICT env var to skills.
92
+ */
93
+ lastVerdict?: string;
94
+ /**
95
+ * Failed AC descriptions from a preceding QA phase.
96
+ * Propagated as SEQUANT_FAILED_ACS env var to skills.
97
+ */
98
+ failedAcs?: string;
83
99
  }
84
100
  /**
85
101
  * Default execution configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,16 +32,26 @@
32
32
  "prepublishOnly": "npm run build"
33
33
  },
34
34
  "keywords": [
35
+ "ai",
35
36
  "claude",
36
37
  "claude-code",
38
+ "aider",
39
+ "mcp",
40
+ "cli",
37
41
  "workflow",
38
- "ai",
39
42
  "automation",
40
- "quality",
41
- "sequential",
42
- "agent-skills",
43
- "cursor",
44
- "vscode"
43
+ "coding-agent",
44
+ "agent",
45
+ "github",
46
+ "github-issues",
47
+ "quality-gates",
48
+ "code-quality",
49
+ "orchestrator",
50
+ "developer-tools",
51
+ "anthropic",
52
+ "llm",
53
+ "ai-coding",
54
+ "copilot"
45
55
  ],
46
56
  "author": "Sequant Contributors",
47
57
  "license": "MIT",