sequant 1.20.3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/.claude-plugin/marketplace.json +2 -4
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +36 -15
  4. package/dist/bin/cli.js +25 -2
  5. package/dist/src/commands/doctor.js +42 -9
  6. package/dist/src/commands/init.d.ts +1 -0
  7. package/dist/src/commands/init.js +52 -0
  8. package/dist/src/commands/logs.d.ts +1 -0
  9. package/dist/src/commands/logs.js +18 -2
  10. package/dist/src/commands/run.d.ts +7 -0
  11. package/dist/src/commands/run.js +235 -68
  12. package/dist/src/commands/serve.d.ts +13 -0
  13. package/dist/src/commands/serve.js +131 -0
  14. package/dist/src/commands/stats.d.ts +1 -0
  15. package/dist/src/commands/stats.js +185 -26
  16. package/dist/src/commands/status.d.ts +2 -0
  17. package/dist/src/commands/status.js +99 -50
  18. package/dist/src/index.d.ts +2 -2
  19. package/dist/src/index.js +4 -1
  20. package/dist/src/lib/ac-parser.d.ts +2 -0
  21. package/dist/src/lib/ac-parser.js +12 -2
  22. package/dist/src/lib/assess-comment-parser.d.ts +137 -0
  23. package/dist/src/lib/assess-comment-parser.js +344 -0
  24. package/dist/src/lib/ci/config.d.ts +22 -0
  25. package/dist/src/lib/ci/config.js +134 -0
  26. package/dist/src/lib/ci/index.d.ts +12 -0
  27. package/dist/src/lib/ci/index.js +10 -0
  28. package/dist/src/lib/ci/inputs.d.ts +29 -0
  29. package/dist/src/lib/ci/inputs.js +103 -0
  30. package/dist/src/lib/ci/labels.d.ts +34 -0
  31. package/dist/src/lib/ci/labels.js +101 -0
  32. package/dist/src/lib/ci/outputs.d.ts +25 -0
  33. package/dist/src/lib/ci/outputs.js +84 -0
  34. package/dist/src/lib/ci/triggers.d.ts +9 -0
  35. package/dist/src/lib/ci/triggers.js +86 -0
  36. package/dist/src/lib/ci/types.d.ts +131 -0
  37. package/dist/src/lib/ci/types.js +47 -0
  38. package/dist/src/lib/mcp-config.d.ts +54 -0
  39. package/dist/src/lib/mcp-config.js +172 -0
  40. package/dist/src/lib/merge-check/index.js +6 -12
  41. package/dist/src/lib/merge-check/types.d.ts +20 -7
  42. package/dist/src/lib/merge-check/types.js +11 -0
  43. package/dist/src/lib/phase-signal.d.ts +3 -3
  44. package/dist/src/lib/phase-signal.js +5 -3
  45. package/dist/src/lib/settings.d.ts +52 -0
  46. package/dist/src/lib/settings.js +41 -0
  47. package/dist/src/lib/shutdown.d.ts +16 -5
  48. package/dist/src/lib/shutdown.js +32 -12
  49. package/dist/src/lib/solve-comment-parser.d.ts +9 -102
  50. package/dist/src/lib/solve-comment-parser.js +13 -248
  51. package/dist/src/lib/stacks.d.ts +8 -0
  52. package/dist/src/lib/stacks.js +34 -0
  53. package/dist/src/lib/system.js +3 -7
  54. package/dist/src/lib/test-tautology-detector.d.ts +10 -0
  55. package/dist/src/lib/test-tautology-detector.js +43 -4
  56. package/dist/src/lib/upstream/assessment.js +9 -59
  57. package/dist/src/lib/upstream/issues.js +12 -75
  58. package/dist/src/lib/version-check.d.ts +2 -2
  59. package/dist/src/lib/version-check.js +6 -3
  60. package/dist/src/lib/version.d.ts +4 -0
  61. package/dist/src/lib/version.js +25 -0
  62. package/dist/src/lib/workflow/batch-executor.d.ts +26 -86
  63. package/dist/src/lib/workflow/batch-executor.js +269 -55
  64. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +56 -0
  65. package/dist/src/lib/workflow/drivers/agent-driver.js +8 -0
  66. package/dist/src/lib/workflow/drivers/aider.d.ts +18 -0
  67. package/dist/src/lib/workflow/drivers/aider.js +160 -0
  68. package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -0
  69. package/dist/src/lib/workflow/drivers/claude-code.js +165 -0
  70. package/dist/src/lib/workflow/drivers/index.d.ts +20 -0
  71. package/dist/src/lib/workflow/drivers/index.js +27 -0
  72. package/dist/src/lib/workflow/error-classifier.d.ts +16 -0
  73. package/dist/src/lib/workflow/error-classifier.js +90 -0
  74. package/dist/src/lib/workflow/log-writer.d.ts +6 -3
  75. package/dist/src/lib/workflow/log-writer.js +57 -27
  76. package/dist/src/lib/workflow/metrics-schema.d.ts +9 -9
  77. package/dist/src/lib/workflow/phase-detection.d.ts +23 -0
  78. package/dist/src/lib/workflow/phase-detection.js +45 -29
  79. package/dist/src/lib/workflow/phase-executor.d.ts +42 -3
  80. package/dist/src/lib/workflow/phase-executor.js +375 -229
  81. package/dist/src/lib/workflow/phase-mapper.d.ts +1 -1
  82. package/dist/src/lib/workflow/phase-mapper.js +7 -7
  83. package/dist/src/lib/workflow/platforms/github.d.ts +157 -0
  84. package/dist/src/lib/workflow/platforms/github.js +466 -0
  85. package/dist/src/lib/workflow/platforms/index.d.ts +17 -0
  86. package/dist/src/lib/workflow/platforms/index.js +25 -0
  87. package/dist/src/lib/workflow/platforms/platform-provider.d.ts +67 -0
  88. package/dist/src/lib/workflow/platforms/platform-provider.js +8 -0
  89. package/dist/src/lib/workflow/pr-status.d.ts +2 -4
  90. package/dist/src/lib/workflow/pr-status.js +3 -16
  91. package/dist/src/lib/workflow/qa-cache.d.ts +58 -0
  92. package/dist/src/lib/workflow/qa-cache.js +88 -0
  93. package/dist/src/lib/workflow/reconcile.d.ts +69 -0
  94. package/dist/src/lib/workflow/reconcile.js +290 -0
  95. package/dist/src/lib/workflow/ring-buffer.d.ts +17 -0
  96. package/dist/src/lib/workflow/ring-buffer.js +37 -0
  97. package/dist/src/lib/workflow/run-log-schema.d.ts +115 -24
  98. package/dist/src/lib/workflow/run-log-schema.js +47 -12
  99. package/dist/src/lib/workflow/run-reflect.js +1 -1
  100. package/dist/src/lib/workflow/state-cleanup.js +21 -0
  101. package/dist/src/lib/workflow/state-manager.d.ts +34 -3
  102. package/dist/src/lib/workflow/state-manager.js +278 -126
  103. package/dist/src/lib/workflow/state-schema.d.ts +34 -30
  104. package/dist/src/lib/workflow/state-schema.js +35 -25
  105. package/dist/src/lib/workflow/state-utils.d.ts +3 -1
  106. package/dist/src/lib/workflow/state-utils.js +1 -0
  107. package/dist/src/lib/workflow/types.d.ts +224 -6
  108. package/dist/src/lib/workflow/types.js +20 -1
  109. package/dist/src/lib/workflow/worktree-discovery.d.ts +1 -1
  110. package/dist/src/lib/workflow/worktree-discovery.js +6 -14
  111. package/dist/src/lib/workflow/worktree-manager.js +33 -51
  112. package/dist/src/mcp/index.d.ts +4 -0
  113. package/dist/src/mcp/index.js +4 -0
  114. package/dist/src/mcp/resources.d.ts +7 -0
  115. package/dist/src/mcp/resources.js +111 -0
  116. package/dist/src/mcp/run-registry.d.ts +34 -0
  117. package/dist/src/mcp/run-registry.js +42 -0
  118. package/dist/src/mcp/server.d.ts +12 -0
  119. package/dist/src/mcp/server.js +50 -0
  120. package/dist/src/mcp/tools/logs.d.ts +7 -0
  121. package/dist/src/mcp/tools/logs.js +149 -0
  122. package/dist/src/mcp/tools/run.d.ts +121 -0
  123. package/dist/src/mcp/tools/run.js +591 -0
  124. package/dist/src/mcp/tools/status.d.ts +7 -0
  125. package/dist/src/mcp/tools/status.js +127 -0
  126. package/package.json +26 -7
  127. package/templates/hooks/post-tool.sh +19 -8
  128. package/templates/hooks/pre-tool.sh +36 -49
  129. package/templates/mcp.json +6 -0
  130. package/templates/skills/assess/SKILL.md +354 -352
  131. package/templates/skills/exec/SKILL.md +64 -1
  132. package/templates/skills/fullsolve/SKILL.md +35 -4
  133. package/templates/skills/qa/SKILL.md +486 -9
  134. package/templates/skills/qa/scripts/quality-checks.sh +1 -1
  135. package/templates/skills/setup/SKILL.md +386 -0
  136. package/templates/skills/solve/SKILL.md +38 -664
  137. package/templates/skills/spec/SKILL.md +90 -31
@@ -0,0 +1,172 @@
1
+ /**
2
+ * MCP client detection and configuration
3
+ *
4
+ * Detects installed MCP clients (Claude Desktop, Cursor, VS Code)
5
+ * and generates appropriate configuration entries for Sequant MCP server.
6
+ */
7
+ import * as fs from "fs";
8
+ import * as os from "os";
9
+ import * as path from "path";
10
+ /** Path to the project-level MCP config file used by Claude Code */
11
+ export const PROJECT_MCP_JSON = ".mcp.json";
12
+ /**
13
+ * Clients that need an explicit cwd because they don't run from the project directory.
14
+ */
15
+ const CLIENTS_NEEDING_CWD = new Set([
16
+ "claude-desktop",
17
+ "vscode-continue",
18
+ ]);
19
+ /**
20
+ * Sequant MCP server configuration entry.
21
+ *
22
+ * @param options.projectDir - Absolute project path (used as cwd for clients that need it)
23
+ * @param options.clientType - Target client; determines whether cwd/env are included
24
+ */
25
+ export function getSequantMcpConfig(options) {
26
+ const config = {
27
+ command: "npx",
28
+ args: ["sequant@latest", "serve"],
29
+ };
30
+ // Add cwd for clients that don't run from the project directory
31
+ if (options?.clientType && CLIENTS_NEEDING_CWD.has(options.clientType)) {
32
+ config.cwd = options.projectDir ?? process.cwd();
33
+ }
34
+ // Only include ANTHROPIC_API_KEY for global client configs (not .mcp.json,
35
+ // which is committed to git and must never contain secrets).
36
+ if (options?.clientType && process.env.ANTHROPIC_API_KEY) {
37
+ config.env = { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY };
38
+ }
39
+ return config;
40
+ }
41
+ /**
42
+ * Detect which MCP-compatible clients are installed
43
+ */
44
+ export function detectMcpClients() {
45
+ const clients = [];
46
+ const home = os.homedir();
47
+ // Claude Desktop
48
+ const claudeDesktopConfig = process.platform === "darwin"
49
+ ? path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
50
+ : process.platform === "win32"
51
+ ? path.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json")
52
+ : path.join(home, ".config", "claude", "claude_desktop_config.json");
53
+ clients.push({
54
+ name: "Claude Desktop",
55
+ clientType: "claude-desktop",
56
+ configPath: claudeDesktopConfig,
57
+ exists: fs.existsSync(claudeDesktopConfig),
58
+ });
59
+ // Cursor
60
+ const cursorConfig = path.join(process.cwd(), ".cursor", "mcp.json");
61
+ clients.push({
62
+ name: "Cursor",
63
+ clientType: "cursor",
64
+ configPath: cursorConfig,
65
+ exists: fs.existsSync(path.join(process.cwd(), ".cursor")),
66
+ });
67
+ // VS Code (Continue extension uses .continue/config.json)
68
+ const vscodeConfig = path.join(home, ".continue", "config.json");
69
+ clients.push({
70
+ name: "VS Code + Continue",
71
+ clientType: "vscode-continue",
72
+ configPath: vscodeConfig,
73
+ exists: fs.existsSync(vscodeConfig),
74
+ });
75
+ return clients;
76
+ }
77
+ /**
78
+ * Add Sequant MCP server to a client's config file.
79
+ * Returns true if written, false if already configured.
80
+ */
81
+ export function addSequantToMcpConfig(configPath, clientType) {
82
+ const sequantConfig = getSequantMcpConfig({
83
+ projectDir: process.cwd(),
84
+ clientType,
85
+ });
86
+ let config = {};
87
+ if (fs.existsSync(configPath)) {
88
+ try {
89
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
90
+ }
91
+ catch {
92
+ // Corrupt or empty file — start fresh
93
+ }
94
+ }
95
+ // Initialize mcpServers if needed (Array.isArray guard: typeof [] === "object")
96
+ if (!config.mcpServers ||
97
+ typeof config.mcpServers !== "object" ||
98
+ Array.isArray(config.mcpServers)) {
99
+ config.mcpServers = {};
100
+ }
101
+ const servers = config.mcpServers;
102
+ // Check if already configured
103
+ if (servers.sequant) {
104
+ return false;
105
+ }
106
+ servers.sequant = sequantConfig;
107
+ // Ensure parent directory exists
108
+ const dir = path.dirname(configPath);
109
+ if (!fs.existsSync(dir)) {
110
+ fs.mkdirSync(dir, { recursive: true });
111
+ }
112
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
113
+ return true;
114
+ }
115
+ /**
116
+ * Check whether .mcp.json already has a sequant server entry.
117
+ */
118
+ export function isSequantInProjectMcpJson(projectDir) {
119
+ const mcpJsonPath = path.resolve(projectDir ?? ".", PROJECT_MCP_JSON);
120
+ if (!fs.existsSync(mcpJsonPath))
121
+ return false;
122
+ try {
123
+ const config = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
124
+ return !!config?.mcpServers?.sequant;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ /**
131
+ * Create or update .mcp.json in the project root for Claude Code.
132
+ *
133
+ * - If .mcp.json doesn't exist → create it with the sequant server entry
134
+ * - If .mcp.json exists with a sequant entry → skip (already configured)
135
+ * - If .mcp.json exists without a sequant entry → merge it in
136
+ *
137
+ * Unlike global client configs, .mcp.json does NOT include cwd or env
138
+ * because Claude Code runs from the project root.
139
+ */
140
+ export function createProjectMcpJson(projectDir) {
141
+ const mcpJsonPath = path.resolve(projectDir ?? ".", PROJECT_MCP_JSON);
142
+ const sequantConfig = getSequantMcpConfig(); // No clientType → no cwd/env
143
+ let config = {};
144
+ let fileExisted = false;
145
+ if (fs.existsSync(mcpJsonPath)) {
146
+ fileExisted = true;
147
+ try {
148
+ config = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
149
+ }
150
+ catch {
151
+ // Corrupt or empty file — start fresh
152
+ config = {};
153
+ }
154
+ }
155
+ // Initialize mcpServers if needed (Array.isArray guard: typeof [] === "object")
156
+ if (!config.mcpServers ||
157
+ typeof config.mcpServers !== "object" ||
158
+ Array.isArray(config.mcpServers)) {
159
+ config.mcpServers = {};
160
+ }
161
+ const servers = config.mcpServers;
162
+ // Already configured — skip
163
+ if (servers.sequant) {
164
+ return { created: false, merged: false, skipped: true };
165
+ }
166
+ servers.sequant = sequantConfig;
167
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n");
168
+ if (fileExisted) {
169
+ return { created: false, merged: true, skipped: false };
170
+ }
171
+ return { created: true, merged: false, skipped: false };
172
+ }
@@ -8,6 +8,7 @@ import * as fs from "fs";
8
8
  import * as path from "path";
9
9
  import * as os from "os";
10
10
  import { spawnSync } from "child_process";
11
+ import { GitHubProvider } from "../workflow/platforms/github.js";
11
12
  import { RunLogSchema, LOG_PATHS, } from "../workflow/run-log-schema.js";
12
13
  import { getGitDiffStats } from "../workflow/git-diff-utils.js";
13
14
  import { DEFAULT_MIRROR_PAIRS } from "./types.js";
@@ -125,7 +126,9 @@ export function resolveBranches(issueNumbers, repoRoot, runLog) {
125
126
  : [];
126
127
  }
127
128
  const info = issueInfo.get(issueNumber);
128
- const title = info?.title ?? fetchIssueTitle(issueNumber) ?? `Issue #${issueNumber}`;
129
+ const title = info?.title ??
130
+ ghProvider.fetchIssueTitleSync(String(issueNumber)) ??
131
+ `Issue #${issueNumber}`;
129
132
  branches.push({
130
133
  issueNumber,
131
134
  title,
@@ -137,17 +140,8 @@ export function resolveBranches(issueNumbers, repoRoot, runLog) {
137
140
  }
138
141
  return branches;
139
142
  }
140
- /**
141
- * Fetch issue title from GitHub via gh CLI.
142
- * Returns null if gh is not available or the issue doesn't exist.
143
- */
144
- function fetchIssueTitle(issueNumber) {
145
- const result = spawnSync("gh", ["issue", "view", String(issueNumber), "--json", "title", "--jq", ".title"], { stdio: "pipe", encoding: "utf-8", timeout: 10_000 });
146
- if (result.status !== 0 || !result.stdout?.trim()) {
147
- return null;
148
- }
149
- return result.stdout.trim();
150
- }
143
+ /** Shared GitHubProvider instance for issue title lookups. */
144
+ const ghProvider = new GitHubProvider();
151
145
  /**
152
146
  * Determine which checks to run based on command options.
153
147
  *
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Shared type definitions for batch-level integration QA checks.
5
5
  */
6
+ import { z } from "zod";
6
7
  /**
7
8
  * Information about a feature branch in a batch
8
9
  */
@@ -21,13 +22,25 @@ export interface BranchInfo {
21
22
  filesModified: string[];
22
23
  }
23
24
  /**
24
- * Per-issue verdict from a check
25
- */
26
- export type CheckVerdict = "PASS" | "WARN" | "FAIL";
27
- /**
28
- * Batch-level verdict
29
- */
30
- export type BatchVerdict = "READY" | "NEEDS_ATTENTION" | "BLOCKED";
25
+ * Per-issue verdict from a check (Zod schema + inferred type)
26
+ */
27
+ export declare const CHECK_VERDICTS: readonly ["PASS", "WARN", "FAIL"];
28
+ export declare const CheckVerdictSchema: z.ZodEnum<{
29
+ PASS: "PASS";
30
+ WARN: "WARN";
31
+ FAIL: "FAIL";
32
+ }>;
33
+ export type CheckVerdict = z.infer<typeof CheckVerdictSchema>;
34
+ /**
35
+ * Batch-level verdict (Zod schema + inferred type)
36
+ */
37
+ export declare const BATCH_VERDICTS: readonly ["READY", "NEEDS_ATTENTION", "BLOCKED"];
38
+ export declare const BatchVerdictSchema: z.ZodEnum<{
39
+ READY: "READY";
40
+ NEEDS_ATTENTION: "NEEDS_ATTENTION";
41
+ BLOCKED: "BLOCKED";
42
+ }>;
43
+ export type BatchVerdict = z.infer<typeof BatchVerdictSchema>;
31
44
  /**
32
45
  * A single finding from a check
33
46
  */
@@ -3,6 +3,17 @@
3
3
  *
4
4
  * Shared type definitions for batch-level integration QA checks.
5
5
  */
6
+ import { z } from "zod";
7
+ /**
8
+ * Per-issue verdict from a check (Zod schema + inferred type)
9
+ */
10
+ export const CHECK_VERDICTS = ["PASS", "WARN", "FAIL"];
11
+ export const CheckVerdictSchema = z.enum(CHECK_VERDICTS);
12
+ /**
13
+ * Batch-level verdict (Zod schema + inferred type)
14
+ */
15
+ export const BATCH_VERDICTS = ["READY", "NEEDS_ATTENTION", "BLOCKED"];
16
+ export const BatchVerdictSchema = z.enum(BATCH_VERDICTS);
6
17
  /**
7
18
  * Default mirror pairs for this project
8
19
  */
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides types for tracking where phase recommendations come from
5
5
  * and logic for merging signals with priority:
6
- * Labels > Solve Comment > Title > Body
6
+ * Labels > Assess Comment > Title > Body
7
7
  *
8
8
  * @example
9
9
  * ```typescript
@@ -23,7 +23,7 @@ import type { Phase } from "./workflow/types.js";
23
23
  /**
24
24
  * Source of a phase signal, ordered by priority (highest first)
25
25
  */
26
- export type SignalSource = "label" | "solve" | "title" | "body";
26
+ export type SignalSource = "label" | "assess" | "solve" | "title" | "body";
27
27
  /**
28
28
  * Priority order for signal sources (higher = takes precedence)
29
29
  */
@@ -63,7 +63,7 @@ export interface MergedPhaseResult {
63
63
  /**
64
64
  * Merge phase signals with priority-based deduplication
65
65
  *
66
- * Priority order: Labels > Solve > Title > Body
66
+ * Priority order: Labels > Assess > Title > Body
67
67
  * When multiple signals suggest the same phase, the highest-priority
68
68
  * source wins. Signals can only ADD phases, never remove.
69
69
  *
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides types for tracking where phase recommendations come from
5
5
  * and logic for merging signals with priority:
6
- * Labels > Solve Comment > Title > Body
6
+ * Labels > Assess Comment > Title > Body
7
7
  *
8
8
  * @example
9
9
  * ```typescript
@@ -24,14 +24,16 @@
24
24
  */
25
25
  export const SIGNAL_PRIORITY = {
26
26
  label: 4, // Highest priority - explicit labels
27
- solve: 3, // Solve command analysis
27
+ assess: 3, // Assess command analysis
28
+ /** @deprecated Use `assess` instead */
29
+ solve: 3, // Solve command analysis (backward compat)
28
30
  title: 2, // Title keyword detection
29
31
  body: 1, // Body pattern detection (lowest)
30
32
  };
31
33
  /**
32
34
  * Merge phase signals with priority-based deduplication
33
35
  *
34
- * Priority order: Labels > Solve > Title > Body
36
+ * Priority order: Labels > Assess > Title > Body
35
37
  * When multiple signals suggest the same phase, the highest-priority
36
38
  * source wins. Signals can only ADD phases, never remove.
37
39
  *
@@ -45,6 +45,17 @@ export interface AgentSettings {
45
45
  */
46
46
  model: "haiku" | "sonnet" | "opus";
47
47
  }
48
+ /**
49
+ * Aider-specific settings for the aider agent driver.
50
+ */
51
+ export interface AiderSettings {
52
+ /** Model to use (e.g., "claude-3-sonnet", "gpt-4o") */
53
+ model?: string;
54
+ /** Edit format (e.g., "diff", "whole", "udiff") */
55
+ editFormat?: string;
56
+ /** Extra CLI arguments passed to aider */
57
+ extraArgs?: string[];
58
+ }
48
59
  /**
49
60
  * Run command settings
50
61
  */
@@ -59,6 +70,8 @@ export interface RunSettings {
59
70
  timeout: number;
60
71
  /** Run issues sequentially by default */
61
72
  sequential: boolean;
73
+ /** Max concurrent issues in parallel mode (default: 3) */
74
+ concurrency: number;
62
75
  /** Enable quality loop by default */
63
76
  qualityLoop: boolean;
64
77
  /** Max iterations for quality loop */
@@ -95,6 +108,23 @@ export interface RunSettings {
95
108
  * Default: 5
96
109
  */
97
110
  staleBranchThreshold: number;
111
+ /**
112
+ * TTL in days for resolved issues on the dashboard.
113
+ * After this period, resolved issues are auto-pruned on next read.
114
+ * - Default: 7 (one week)
115
+ * - 0: Never auto-prune (manual cleanup only)
116
+ * - -1: Prune immediately (resolved issues never shown)
117
+ */
118
+ resolvedIssueTTL: number;
119
+ /**
120
+ * Agent driver for phase execution.
121
+ * Default: "claude-code". Set to "aider" to use Aider CLI.
122
+ */
123
+ agent?: string;
124
+ /**
125
+ * Aider-specific configuration. Only used when agent is "aider".
126
+ */
127
+ aider?: AiderSettings;
98
128
  }
99
129
  /**
100
130
  * Scope assessment threshold configuration
@@ -151,6 +181,17 @@ export interface ScopeAssessmentSettings {
151
181
  directorySpread: ScopeThreshold;
152
182
  };
153
183
  }
184
+ /**
185
+ * QA skill settings
186
+ */
187
+ export interface QASettings {
188
+ /**
189
+ * Diff size threshold (additions + deletions) for the small-diff fast path.
190
+ * Diffs below this threshold skip sub-agent spawning and use inline checks.
191
+ * Default: 100
192
+ */
193
+ smallDiffThreshold: number;
194
+ }
154
195
  /**
155
196
  * Full settings schema
156
197
  */
@@ -163,6 +204,8 @@ export interface SequantSettings {
163
204
  agents: AgentSettings;
164
205
  /** Scope assessment settings */
165
206
  scopeAssessment: ScopeAssessmentSettings;
207
+ /** QA skill settings */
208
+ qa: QASettings;
166
209
  }
167
210
  /**
168
211
  * Default rotation settings
@@ -186,10 +229,19 @@ export declare const DEFAULT_TRIVIAL_THRESHOLDS: TrivialThresholds;
186
229
  * src/lib/scope/types.ts to ensure consistency.
187
230
  */
188
231
  export declare const DEFAULT_SCOPE_ASSESSMENT_SETTINGS: ScopeAssessmentSettings;
232
+ /**
233
+ * Default QA settings
234
+ */
235
+ export declare const DEFAULT_QA_SETTINGS: QASettings;
189
236
  /**
190
237
  * Default settings
191
238
  */
192
239
  export declare const DEFAULT_SETTINGS: SequantSettings;
240
+ /**
241
+ * Validate aider-specific settings.
242
+ * Throws on invalid types to catch config errors at load time.
243
+ */
244
+ export declare function validateAiderSettings(aider: unknown): AiderSettings | undefined;
193
245
  /**
194
246
  * Get the current project settings
195
247
  *
@@ -68,6 +68,12 @@ export const DEFAULT_SCOPE_ASSESSMENT_SETTINGS = {
68
68
  directorySpread: { yellow: 3, red: 5 },
69
69
  },
70
70
  };
71
+ /**
72
+ * Default QA settings
73
+ */
74
+ export const DEFAULT_QA_SETTINGS = {
75
+ smallDiffThreshold: 100,
76
+ };
71
77
  /**
72
78
  * Default settings
73
79
  */
@@ -79,6 +85,7 @@ export const DEFAULT_SETTINGS = {
79
85
  autoDetectPhases: true,
80
86
  timeout: 1800,
81
87
  sequential: false,
88
+ concurrency: 3,
82
89
  qualityLoop: false,
83
90
  maxIterations: 3,
84
91
  smartTests: true,
@@ -86,10 +93,37 @@ export const DEFAULT_SETTINGS = {
86
93
  mcp: true, // Enable MCP servers by default in headless mode
87
94
  retry: true, // Enable automatic retry with MCP fallback by default
88
95
  staleBranchThreshold: 5, // Block QA/test if feature is >5 commits behind main
96
+ resolvedIssueTTL: 7, // Auto-prune resolved issues after 7 days
89
97
  },
90
98
  agents: DEFAULT_AGENT_SETTINGS,
91
99
  scopeAssessment: DEFAULT_SCOPE_ASSESSMENT_SETTINGS,
100
+ qa: DEFAULT_QA_SETTINGS,
92
101
  };
102
+ /**
103
+ * Validate aider-specific settings.
104
+ * Throws on invalid types to catch config errors at load time.
105
+ */
106
+ export function validateAiderSettings(aider) {
107
+ if (aider == null)
108
+ return undefined;
109
+ if (typeof aider !== "object" || Array.isArray(aider)) {
110
+ throw new Error("settings.run.aider must be an object");
111
+ }
112
+ const obj = aider;
113
+ if (obj.model !== undefined && typeof obj.model !== "string") {
114
+ throw new Error("settings.run.aider.model must be a string");
115
+ }
116
+ if (obj.editFormat !== undefined && typeof obj.editFormat !== "string") {
117
+ throw new Error("settings.run.aider.editFormat must be a string");
118
+ }
119
+ if (obj.extraArgs !== undefined) {
120
+ if (!Array.isArray(obj.extraArgs) ||
121
+ !obj.extraArgs.every((a) => typeof a === "string")) {
122
+ throw new Error("settings.run.aider.extraArgs must be an array of strings");
123
+ }
124
+ }
125
+ return obj;
126
+ }
93
127
  /**
94
128
  * Get the current project settings
95
129
  *
@@ -102,12 +136,15 @@ export async function getSettings() {
102
136
  try {
103
137
  const content = await readFile(SETTINGS_PATH);
104
138
  const parsed = JSON.parse(content);
139
+ // Validate aider settings if present
140
+ const aiderSettings = validateAiderSettings(parsed.run?.aider);
105
141
  // Merge with defaults to ensure all fields exist
106
142
  return {
107
143
  version: parsed.version ?? DEFAULT_SETTINGS.version,
108
144
  run: {
109
145
  ...DEFAULT_SETTINGS.run,
110
146
  ...parsed.run,
147
+ ...(aiderSettings !== undefined ? { aider: aiderSettings } : {}),
111
148
  },
112
149
  agents: {
113
150
  ...DEFAULT_AGENT_SETTINGS,
@@ -125,6 +162,10 @@ export async function getSettings() {
125
162
  ...parsed.scopeAssessment?.thresholds,
126
163
  },
127
164
  },
165
+ qa: {
166
+ ...DEFAULT_QA_SETTINGS,
167
+ ...parsed.qa,
168
+ },
128
169
  };
129
170
  }
130
171
  catch {
@@ -46,7 +46,8 @@ export interface ShutdownManagerOptions {
46
46
  export declare class ShutdownManager {
47
47
  private cleanupTasks;
48
48
  private _isShuttingDown;
49
- private abortController;
49
+ /** Active abort controllers — supports concurrent phase execution (#404) */
50
+ private abortControllers;
50
51
  private forceExitTimeout;
51
52
  private output;
52
53
  private errorOutput;
@@ -63,14 +64,24 @@ export declare class ShutdownManager {
63
64
  */
64
65
  get shuttingDown(): boolean;
65
66
  /**
66
- * Set the abort controller for the current phase
67
+ * Register an abort controller for a running phase.
67
68
  *
68
- * When shutdown is triggered, this controller will be aborted
69
- * to cancel any in-flight operations.
69
+ * When shutdown is triggered, ALL registered controllers are aborted,
70
+ * supporting concurrent phase execution.
71
+ */
72
+ addAbortController(controller: AbortController): void;
73
+ /**
74
+ * Unregister a specific abort controller after a phase completes.
75
+ *
76
+ * Only removes the given controller — others remain active.
77
+ */
78
+ removeAbortController(controller: AbortController): void;
79
+ /**
80
+ * @deprecated Use addAbortController/removeAbortController for concurrent safety.
70
81
  */
71
82
  setAbortController(controller: AbortController): void;
72
83
  /**
73
- * Clear the abort controller (e.g., after phase completes)
84
+ * @deprecated Use removeAbortController(controller) instead.
74
85
  */
75
86
  clearAbortController(): void;
76
87
  /**
@@ -34,7 +34,8 @@ import chalk from "chalk";
34
34
  export class ShutdownManager {
35
35
  cleanupTasks = [];
36
36
  _isShuttingDown = false;
37
- abortController = null;
37
+ /** Active abort controllers — supports concurrent phase execution (#404) */
38
+ abortControllers = new Set();
38
39
  forceExitTimeout;
39
40
  output;
40
41
  errorOutput;
@@ -67,19 +68,34 @@ export class ShutdownManager {
67
68
  return this._isShuttingDown;
68
69
  }
69
70
  /**
70
- * Set the abort controller for the current phase
71
+ * Register an abort controller for a running phase.
71
72
  *
72
- * When shutdown is triggered, this controller will be aborted
73
- * to cancel any in-flight operations.
73
+ * When shutdown is triggered, ALL registered controllers are aborted,
74
+ * supporting concurrent phase execution.
75
+ */
76
+ addAbortController(controller) {
77
+ this.abortControllers.add(controller);
78
+ }
79
+ /**
80
+ * Unregister a specific abort controller after a phase completes.
81
+ *
82
+ * Only removes the given controller — others remain active.
83
+ */
84
+ removeAbortController(controller) {
85
+ this.abortControllers.delete(controller);
86
+ }
87
+ /**
88
+ * @deprecated Use addAbortController/removeAbortController for concurrent safety.
74
89
  */
75
90
  setAbortController(controller) {
76
- this.abortController = controller;
91
+ this.abortControllers.clear();
92
+ this.abortControllers.add(controller);
77
93
  }
78
94
  /**
79
- * Clear the abort controller (e.g., after phase completes)
95
+ * @deprecated Use removeAbortController(controller) instead.
80
96
  */
81
97
  clearAbortController() {
82
- this.abortController = null;
98
+ this.abortControllers.clear();
83
99
  }
84
100
  /**
85
101
  * Register a cleanup task
@@ -123,10 +139,14 @@ export class ShutdownManager {
123
139
  }
124
140
  this._isShuttingDown = true;
125
141
  this.output(chalk.yellow(`\n⚠️ Received ${signal}, shutting down gracefully...`));
126
- // Abort in-flight phase immediately
127
- if (this.abortController) {
128
- this.abortController.abort();
129
- this.output(chalk.green("✓ Aborted active phase"));
142
+ // Abort all in-flight phases immediately
143
+ if (this.abortControllers.size > 0) {
144
+ const count = this.abortControllers.size;
145
+ for (const controller of this.abortControllers) {
146
+ controller.abort();
147
+ }
148
+ this.abortControllers.clear();
149
+ this.output(chalk.green(`✓ Aborted ${count} active phase${count > 1 ? "s" : ""}`));
130
150
  }
131
151
  // Set up force exit timeout
132
152
  const forceExitTimer = setTimeout(() => {
@@ -160,7 +180,7 @@ export class ShutdownManager {
160
180
  process.removeListener("SIGINT", this.sigintHandler);
161
181
  process.removeListener("SIGTERM", this.sigtermHandler);
162
182
  this.cleanupTasks = [];
163
- this.abortController = null;
183
+ this.abortControllers.clear();
164
184
  }
165
185
  }
166
186
  /**