sequant 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +47 -3
- package/dist/dashboard/server.js +4 -0
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +9 -0
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +331 -12
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +7 -18
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +5 -1
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +62 -8
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +187 -28
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +127 -23
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +130 -13
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +306 -8
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +79 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +42 -20
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +14 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +23 -6
- package/dist/src/commands/doctor.js +20 -18
- package/dist/src/commands/ready.js +7 -1
- package/dist/src/commands/status.js +4 -0
- package/dist/src/lib/ac-linter.js +26 -0
- package/dist/src/lib/ac-parser.d.ts +40 -0
- package/dist/src/lib/ac-parser.js +202 -16
- package/dist/src/lib/markdown-fence.d.ts +24 -0
- package/dist/src/lib/markdown-fence.js +51 -0
- package/dist/src/lib/mcp-config.d.ts +24 -0
- package/dist/src/lib/mcp-config.js +51 -0
- package/dist/src/lib/scope/analyzer.d.ts +4 -0
- package/dist/src/lib/scope/analyzer.js +7 -1
- package/dist/src/lib/settings.d.ts +73 -14
- package/dist/src/lib/settings.js +45 -3
- package/dist/src/lib/system.d.ts +7 -3
- package/dist/src/lib/system.js +7 -3
- package/dist/src/lib/test-tautology-detector.js +50 -3
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +81 -6
- package/dist/src/lib/workflow/config-resolver.d.ts +30 -2
- package/dist/src/lib/workflow/config-resolver.js +59 -2
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +14 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +36 -4
- package/dist/src/lib/workflow/metrics-schema.d.ts +10 -1
- package/dist/src/lib/workflow/metrics-schema.js +13 -1
- package/dist/src/lib/workflow/metrics-writer.d.ts +3 -1
- package/dist/src/lib/workflow/mutation-marker.d.ts +86 -0
- package/dist/src/lib/workflow/mutation-marker.js +97 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +17 -0
- package/dist/src/lib/workflow/phase-executor.js +60 -6
- package/dist/src/lib/workflow/qa-gaps-marker.d.ts +38 -0
- package/dist/src/lib/workflow/qa-gaps-marker.js +66 -0
- package/dist/src/lib/workflow/ready-gate.d.ts +25 -1
- package/dist/src/lib/workflow/ready-gate.js +81 -11
- package/dist/src/lib/workflow/reconcile.js +4 -2
- package/dist/src/lib/workflow/run-log-schema.d.ts +120 -0
- package/dist/src/lib/workflow/run-log-schema.js +40 -0
- package/dist/src/lib/workflow/run-orchestrator.d.ts +18 -0
- package/dist/src/lib/workflow/run-orchestrator.js +38 -2
- package/dist/src/lib/workflow/state-cleanup.d.ts +4 -4
- package/dist/src/lib/workflow/state-cleanup.js +9 -5
- package/dist/src/lib/workflow/state-schema.d.ts +10 -1
- package/dist/src/lib/workflow/state-schema.js +13 -1
- package/dist/src/lib/workflow/types.d.ts +20 -0
- package/dist/src/mcp/tools/run.js +10 -1
- package/package.json +13 -12
- package/templates/hooks/pre-tool.sh +108 -17
- package/templates/memory/constitution.md +112 -45
- package/templates/skills/exec/SKILL.md +1 -1
- package/templates/skills/fullsolve/SKILL.md +62 -9
- package/templates/skills/loop/SKILL.md +71 -12
- package/templates/skills/merger/SKILL.md +32 -3
- package/templates/skills/qa/SKILL.md +247 -2
- package/templates/skills/spec/SKILL.md +11 -5
- package/templates/skills/test/SKILL.md +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Detects installed MCP clients (Claude Desktop, Cursor, VS Code)
|
|
5
5
|
* and generates appropriate configuration entries for Sequant MCP server.
|
|
6
6
|
*/
|
|
7
|
+
import { type McpServerConfig } from "./system.js";
|
|
7
8
|
/** Path to the project-level MCP config file used by Claude Code */
|
|
8
9
|
export declare const PROJECT_MCP_JSON = ".mcp.json";
|
|
9
10
|
/**
|
|
@@ -38,6 +39,29 @@ export declare function getSequantMcpConfig(options?: {
|
|
|
38
39
|
projectDir?: string;
|
|
39
40
|
clientType?: McpClientType;
|
|
40
41
|
}): Record<string, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Build the MCP server set for an autonomous phase agent (#936).
|
|
44
|
+
*
|
|
45
|
+
* Phase agents are a different trust domain from the interactive Claude
|
|
46
|
+
* Desktop app: they run unattended, and Claude Desktop configs cannot use
|
|
47
|
+
* `${VAR}` references, so they hold literal secrets that the SDK would
|
|
48
|
+
* otherwise serialize verbatim into the child process's `--mcp-config`
|
|
49
|
+
* argv. This builder allowlists instead of passing through — it unions the
|
|
50
|
+
* project's own `.mcp.json` (secret-free by convention, committed to git)
|
|
51
|
+
* with a guaranteed sequant server entry, and never reads
|
|
52
|
+
* `claude_desktop_config.json` **unless** a server name is explicitly
|
|
53
|
+
* listed in `opts.desktopAllowlist` (from `settings.run.mcpAllowlist`) —
|
|
54
|
+
* the deliberate per-server opt-in for a desktop-only server. A name not
|
|
55
|
+
* present in the desktop config is silently ignored.
|
|
56
|
+
*
|
|
57
|
+
* @param cwd - Directory to resolve `.mcp.json` from (the phase worktree)
|
|
58
|
+
* @param opts.desktopAllowlist - Exact `mcpServers` keys to pass through
|
|
59
|
+
* from Claude Desktop config, despite the default exclusion
|
|
60
|
+
* @returns MCP server configurations for the phase agent
|
|
61
|
+
*/
|
|
62
|
+
export declare function getPhaseMcpServersConfig(cwd?: string, opts?: {
|
|
63
|
+
desktopAllowlist?: string[];
|
|
64
|
+
}): Record<string, McpServerConfig>;
|
|
41
65
|
/**
|
|
42
66
|
* Detect which MCP-compatible clients are installed
|
|
43
67
|
*/
|
|
@@ -8,6 +8,7 @@ import * as fs from "fs";
|
|
|
8
8
|
import * as os from "os";
|
|
9
9
|
import * as path from "path";
|
|
10
10
|
import { getVersion } from "./version.js";
|
|
11
|
+
import { getMcpServersConfig } from "./system.js";
|
|
11
12
|
/** Path to the project-level MCP config file used by Claude Code */
|
|
12
13
|
export const PROJECT_MCP_JSON = ".mcp.json";
|
|
13
14
|
/**
|
|
@@ -59,6 +60,56 @@ export function getSequantMcpConfig(options) {
|
|
|
59
60
|
}
|
|
60
61
|
return config;
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the MCP server set for an autonomous phase agent (#936).
|
|
65
|
+
*
|
|
66
|
+
* Phase agents are a different trust domain from the interactive Claude
|
|
67
|
+
* Desktop app: they run unattended, and Claude Desktop configs cannot use
|
|
68
|
+
* `${VAR}` references, so they hold literal secrets that the SDK would
|
|
69
|
+
* otherwise serialize verbatim into the child process's `--mcp-config`
|
|
70
|
+
* argv. This builder allowlists instead of passing through — it unions the
|
|
71
|
+
* project's own `.mcp.json` (secret-free by convention, committed to git)
|
|
72
|
+
* with a guaranteed sequant server entry, and never reads
|
|
73
|
+
* `claude_desktop_config.json` **unless** a server name is explicitly
|
|
74
|
+
* listed in `opts.desktopAllowlist` (from `settings.run.mcpAllowlist`) —
|
|
75
|
+
* the deliberate per-server opt-in for a desktop-only server. A name not
|
|
76
|
+
* present in the desktop config is silently ignored.
|
|
77
|
+
*
|
|
78
|
+
* @param cwd - Directory to resolve `.mcp.json` from (the phase worktree)
|
|
79
|
+
* @param opts.desktopAllowlist - Exact `mcpServers` keys to pass through
|
|
80
|
+
* from Claude Desktop config, despite the default exclusion
|
|
81
|
+
* @returns MCP server configurations for the phase agent
|
|
82
|
+
*/
|
|
83
|
+
export function getPhaseMcpServersConfig(cwd, opts) {
|
|
84
|
+
const mcpJsonPath = path.resolve(cwd ?? ".", PROJECT_MCP_JSON);
|
|
85
|
+
let servers = {};
|
|
86
|
+
try {
|
|
87
|
+
const content = fs.readFileSync(mcpJsonPath, "utf-8");
|
|
88
|
+
const config = JSON.parse(content);
|
|
89
|
+
if (config.mcpServers &&
|
|
90
|
+
typeof config.mcpServers === "object" &&
|
|
91
|
+
!Array.isArray(config.mcpServers)) {
|
|
92
|
+
servers = config.mcpServers;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// .mcp.json doesn't exist or is invalid — sequant entry still applies
|
|
97
|
+
}
|
|
98
|
+
const allowedDesktopServers = {};
|
|
99
|
+
if (opts?.desktopAllowlist && opts.desktopAllowlist.length > 0) {
|
|
100
|
+
const desktopServers = getMcpServersConfig() ?? {};
|
|
101
|
+
for (const name of opts.desktopAllowlist) {
|
|
102
|
+
if (desktopServers[name]) {
|
|
103
|
+
allowedDesktopServers[name] = desktopServers[name];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
...allowedDesktopServers,
|
|
109
|
+
...servers,
|
|
110
|
+
sequant: getSequantMcpConfig(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
62
113
|
/**
|
|
63
114
|
* Detect which MCP-compatible clients are installed
|
|
64
115
|
*/
|
|
@@ -73,6 +73,10 @@ export declare function detectFeatures(criteria: AcceptanceCriterion[], title: s
|
|
|
73
73
|
*
|
|
74
74
|
* Looks for a "Non-Goals" or "Out of Scope" section with checkbox items.
|
|
75
75
|
*
|
|
76
|
+
* Fenced code blocks are excluded before matching (#947 sibling) — a
|
|
77
|
+
* Non-Goals example shown inside a fence (e.g. illustrating checkbox
|
|
78
|
+
* syntax) must not itself be read as a real non-goal item.
|
|
79
|
+
*
|
|
76
80
|
* @param issueBody - Full issue body markdown
|
|
77
81
|
* @returns Non-goals extraction result
|
|
78
82
|
*/
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
import { DEFAULT_SCOPE_CONFIG } from "./types.js";
|
|
19
|
+
import { stripFencedLines } from "../markdown-fence.js";
|
|
19
20
|
/**
|
|
20
21
|
* Keywords for clustering AC items by functional area
|
|
21
22
|
*/
|
|
@@ -247,14 +248,19 @@ export function detectFeatures(criteria, title) {
|
|
|
247
248
|
*
|
|
248
249
|
* Looks for a "Non-Goals" or "Out of Scope" section with checkbox items.
|
|
249
250
|
*
|
|
251
|
+
* Fenced code blocks are excluded before matching (#947 sibling) — a
|
|
252
|
+
* Non-Goals example shown inside a fence (e.g. illustrating checkbox
|
|
253
|
+
* syntax) must not itself be read as a real non-goal item.
|
|
254
|
+
*
|
|
250
255
|
* @param issueBody - Full issue body markdown
|
|
251
256
|
* @returns Non-goals extraction result
|
|
252
257
|
*/
|
|
253
258
|
export function parseNonGoals(issueBody) {
|
|
254
259
|
const items = [];
|
|
260
|
+
const strippedBody = stripFencedLines(issueBody);
|
|
255
261
|
// Find Non-Goals section (case-insensitive)
|
|
256
262
|
const sectionPattern = /##\s*(?:Non[- ]?Goals|Out\s+of\s+Scope|Scope\s+Boundaries)\s*\n([\s\S]*?)(?=\n##|\n---|$)/i;
|
|
257
|
-
const sectionMatch =
|
|
263
|
+
const sectionMatch = strippedBody.match(sectionPattern);
|
|
258
264
|
if (!sectionMatch) {
|
|
259
265
|
return {
|
|
260
266
|
items: [],
|
|
@@ -40,14 +40,14 @@ export interface AgentSettings {
|
|
|
40
40
|
*/
|
|
41
41
|
parallel: boolean;
|
|
42
42
|
/**
|
|
43
|
-
* Default model for sub-agents.
|
|
44
|
-
* Options: "haiku" (cheapest), "sonnet" (balanced), "opus" (most capable)
|
|
43
|
+
* Default model for sub-agents (free string — any alias or dated ID accepted).
|
|
45
44
|
* Default: "haiku" — currently inert per anthropics/claude-code#43869.
|
|
45
|
+
* See `run.modelRoles` for semantic role indirection (#975).
|
|
46
46
|
* @deprecated currently inert; see anthropics/claude-code#43869. Subagents
|
|
47
47
|
* inherit the parent session's model regardless of this value. Kept so
|
|
48
48
|
* existing user settings.json files continue to parse without error.
|
|
49
49
|
*/
|
|
50
|
-
model:
|
|
50
|
+
model: string;
|
|
51
51
|
/**
|
|
52
52
|
* Isolate parallel agent groups in separate worktrees.
|
|
53
53
|
* When true, each agent in a parallel group gets its own sub-worktree,
|
|
@@ -64,6 +64,23 @@ export interface AgentSettings {
|
|
|
64
64
|
* two validation points cannot drift apart on which values are accepted.
|
|
65
65
|
*/
|
|
66
66
|
export declare const EFFORT_LEVELS: readonly ["low", "medium", "high", "xhigh", "max"];
|
|
67
|
+
/**
|
|
68
|
+
* Map of semantic role names to model strings, used by `run.modelRoles` (#975).
|
|
69
|
+
*
|
|
70
|
+
* A value is either:
|
|
71
|
+
* - a plain string shorthand — desugars to `{ "claude-code": value }` (claude-code driver only)
|
|
72
|
+
* - an object keyed by driver registry name, for cross-driver mappings
|
|
73
|
+
*
|
|
74
|
+
* Role references use a `role:` prefix in phase policy / ladder entries;
|
|
75
|
+
* bare strings pass through verbatim (AC-3 backward compat).
|
|
76
|
+
*/
|
|
77
|
+
export declare const ModelRolesSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
78
|
+
export type ModelRoles = z.infer<typeof ModelRolesSchema>;
|
|
79
|
+
/**
|
|
80
|
+
* Shipped default role map (#975). Family aliases only — no dated model IDs.
|
|
81
|
+
* Claude-code shorthand form; no opencode/aider entries shipped by default.
|
|
82
|
+
*/
|
|
83
|
+
export declare const DEFAULT_MODEL_ROLES: ModelRoles;
|
|
67
84
|
/**
|
|
68
85
|
* A single phase's `model`/`effort` override for the claude-code driver
|
|
69
86
|
* (#914). See `RunSettings.phases`.
|
|
@@ -117,11 +134,35 @@ export interface RunSettings {
|
|
|
117
134
|
defaultBase?: string;
|
|
118
135
|
/**
|
|
119
136
|
* Enable MCP servers in headless mode.
|
|
120
|
-
* When true,
|
|
137
|
+
* When true, injects the sequant MCP server plus any servers declared in
|
|
138
|
+
* the project's own `.mcp.json` — never a passthrough of the user's Claude
|
|
139
|
+
* Desktop config, which is a different trust domain and may carry literal
|
|
140
|
+
* secrets (#936), unless a server is explicitly named in `mcpAllowlist`
|
|
141
|
+
* below.
|
|
121
142
|
* When false or --no-mcp flag is used, MCPs are disabled.
|
|
122
143
|
* Default: true
|
|
123
144
|
*/
|
|
124
145
|
mcp: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Explicit per-server opt-in to pass specific Claude Desktop MCP servers
|
|
148
|
+
* through to phase execution (#936).
|
|
149
|
+
*
|
|
150
|
+
* `mcp` above never reads Claude Desktop config by default — phase agents
|
|
151
|
+
* get the sequant server plus the project's own `.mcp.json` only. This is
|
|
152
|
+
* the deliberate escape hatch for a server that exists only in Claude
|
|
153
|
+
* Desktop config (e.g. never committed to git): list its exact
|
|
154
|
+
* `mcpServers` key here and phase agents additionally receive it. A name
|
|
155
|
+
* not present in the desktop config is silently ignored — this is a
|
|
156
|
+
* filter, not a requirement. Unset or empty (default): no desktop servers
|
|
157
|
+
* pass through, matching `mcp`'s secure-by-default behavior.
|
|
158
|
+
*
|
|
159
|
+
* ⚠️ Desktop Claude configs cannot use `${VAR}` references, so a server you
|
|
160
|
+
* allowlist here may carry a literal secret that reaches the phase
|
|
161
|
+
* process's argv (the SDK serializes `mcpServers` into `--mcp-config`).
|
|
162
|
+
* Only allowlist a server with no credential, or one whose credential you
|
|
163
|
+
* accept exposing to phase agents and to `ps`.
|
|
164
|
+
*/
|
|
165
|
+
mcpAllowlist?: string[];
|
|
125
166
|
/**
|
|
126
167
|
* Enable automatic retry with MCP fallback.
|
|
127
168
|
* When true (default), failed phases are retried with MCP disabled.
|
|
@@ -198,6 +239,26 @@ export interface RunSettings {
|
|
|
198
239
|
* default). See `effort-escalation.ts` for the resolver.
|
|
199
240
|
*/
|
|
200
241
|
effortEscalation: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Whether `/fullsolve`'s Phase 5.3 merges the PR automatically once QA
|
|
244
|
+
* passes (#958). Default `false` — the workflow stops at PR creation +
|
|
245
|
+
* final summary, preserving the human merge gate kept by #817–#819.
|
|
246
|
+
* Overridable per-invocation with `--auto-merge`. Read directly by the
|
|
247
|
+
* `/fullsolve` skill prose (not by any runtime code path — `sequant run`
|
|
248
|
+
* never merges regardless of this setting).
|
|
249
|
+
*/
|
|
250
|
+
autoMerge: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Map of semantic role names to model strings (#975).
|
|
253
|
+
*
|
|
254
|
+
* Config expresses **roles**; this map resolves roles to concrete model
|
|
255
|
+
* strings. Phase policy, ladder, and other model-referencing surfaces use
|
|
256
|
+
* `role:<name>` to reference an entry; bare strings pass through verbatim.
|
|
257
|
+
*
|
|
258
|
+
* Default: `{ fast: "sonnet", strong: "opus", frontier: "fable" }`.
|
|
259
|
+
* `sequant setup` writes nothing here — absent key → defaults apply.
|
|
260
|
+
*/
|
|
261
|
+
modelRoles: ModelRoles;
|
|
201
262
|
}
|
|
202
263
|
/**
|
|
203
264
|
* Scope assessment threshold configuration
|
|
@@ -329,11 +390,7 @@ export declare const AiderSettingsSchema: z.ZodObject<{
|
|
|
329
390
|
/** Zod schema for AgentSettings */
|
|
330
391
|
export declare const AgentSettingsSchema: z.ZodObject<{
|
|
331
392
|
parallel: z.ZodDefault<z.ZodBoolean>;
|
|
332
|
-
model: z.ZodDefault<z.
|
|
333
|
-
haiku: "haiku";
|
|
334
|
-
sonnet: "sonnet";
|
|
335
|
-
opus: "opus";
|
|
336
|
-
}>>;
|
|
393
|
+
model: z.ZodDefault<z.ZodString>;
|
|
337
394
|
isolateParallel: z.ZodDefault<z.ZodBoolean>;
|
|
338
395
|
}, z.core.$strip>;
|
|
339
396
|
/**
|
|
@@ -373,6 +430,7 @@ export declare const RunSettingsSchema: z.ZodObject<{
|
|
|
373
430
|
}, z.core.$strip>>;
|
|
374
431
|
defaultBase: z.ZodOptional<z.ZodString>;
|
|
375
432
|
mcp: z.ZodDefault<z.ZodBoolean>;
|
|
433
|
+
mcpAllowlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
376
434
|
retry: z.ZodDefault<z.ZodBoolean>;
|
|
377
435
|
autoWaitMinutes: z.ZodDefault<z.ZodNumber>;
|
|
378
436
|
staleBranchThreshold: z.ZodDefault<z.ZodNumber>;
|
|
@@ -397,6 +455,8 @@ export declare const RunSettingsSchema: z.ZodObject<{
|
|
|
397
455
|
}>>;
|
|
398
456
|
}, z.core.$strip>>>;
|
|
399
457
|
effortEscalation: z.ZodDefault<z.ZodBoolean>;
|
|
458
|
+
autoMerge: z.ZodDefault<z.ZodBoolean>;
|
|
459
|
+
modelRoles: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
400
460
|
}, z.core.$strip>;
|
|
401
461
|
/** Zod schema for ScopeThreshold (base — fields required, no defaults) */
|
|
402
462
|
export declare const ScopeThresholdSchema: z.ZodObject<{
|
|
@@ -477,6 +537,7 @@ export declare const SettingsSchema: z.ZodObject<{
|
|
|
477
537
|
}, z.core.$strip>>;
|
|
478
538
|
defaultBase: z.ZodOptional<z.ZodString>;
|
|
479
539
|
mcp: z.ZodDefault<z.ZodBoolean>;
|
|
540
|
+
mcpAllowlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
480
541
|
retry: z.ZodDefault<z.ZodBoolean>;
|
|
481
542
|
autoWaitMinutes: z.ZodDefault<z.ZodNumber>;
|
|
482
543
|
staleBranchThreshold: z.ZodDefault<z.ZodNumber>;
|
|
@@ -501,14 +562,12 @@ export declare const SettingsSchema: z.ZodObject<{
|
|
|
501
562
|
}>>;
|
|
502
563
|
}, z.core.$strip>>>;
|
|
503
564
|
effortEscalation: z.ZodDefault<z.ZodBoolean>;
|
|
565
|
+
autoMerge: z.ZodDefault<z.ZodBoolean>;
|
|
566
|
+
modelRoles: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
504
567
|
}, z.core.$strip>>;
|
|
505
568
|
agents: z.ZodDefault<z.ZodObject<{
|
|
506
569
|
parallel: z.ZodDefault<z.ZodBoolean>;
|
|
507
|
-
model: z.ZodDefault<z.
|
|
508
|
-
haiku: "haiku";
|
|
509
|
-
sonnet: "sonnet";
|
|
510
|
-
opus: "opus";
|
|
511
|
-
}>>;
|
|
570
|
+
model: z.ZodDefault<z.ZodString>;
|
|
512
571
|
isolateParallel: z.ZodDefault<z.ZodBoolean>;
|
|
513
572
|
}, z.core.$strip>>;
|
|
514
573
|
scopeAssessment: z.ZodDefault<z.ZodObject<{
|
package/dist/src/lib/settings.js
CHANGED
|
@@ -25,6 +25,26 @@ export const SETTINGS_VERSION = "1.0";
|
|
|
25
25
|
* two validation points cannot drift apart on which values are accepted.
|
|
26
26
|
*/
|
|
27
27
|
export const EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
28
|
+
/**
|
|
29
|
+
* Map of semantic role names to model strings, used by `run.modelRoles` (#975).
|
|
30
|
+
*
|
|
31
|
+
* A value is either:
|
|
32
|
+
* - a plain string shorthand — desugars to `{ "claude-code": value }` (claude-code driver only)
|
|
33
|
+
* - an object keyed by driver registry name, for cross-driver mappings
|
|
34
|
+
*
|
|
35
|
+
* Role references use a `role:` prefix in phase policy / ladder entries;
|
|
36
|
+
* bare strings pass through verbatim (AC-3 backward compat).
|
|
37
|
+
*/
|
|
38
|
+
export const ModelRolesSchema = z.record(z.string(), z.union([z.string(), z.record(z.string(), z.string())]));
|
|
39
|
+
/**
|
|
40
|
+
* Shipped default role map (#975). Family aliases only — no dated model IDs.
|
|
41
|
+
* Claude-code shorthand form; no opencode/aider entries shipped by default.
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_MODEL_ROLES = {
|
|
44
|
+
fast: "sonnet",
|
|
45
|
+
strong: "opus",
|
|
46
|
+
frontier: "fable",
|
|
47
|
+
};
|
|
28
48
|
// ─── Zod Schemas (AC-1, AC-5) ────────────────────────────────────────────────
|
|
29
49
|
/** Zod schema for RotationSettings */
|
|
30
50
|
export const RotationSettingsSchema = z.object({
|
|
@@ -41,7 +61,7 @@ export const AiderSettingsSchema = z.object({
|
|
|
41
61
|
/** Zod schema for AgentSettings */
|
|
42
62
|
export const AgentSettingsSchema = z.object({
|
|
43
63
|
parallel: z.boolean().default(false),
|
|
44
|
-
model: z.
|
|
64
|
+
model: z.string().default("haiku"),
|
|
45
65
|
isolateParallel: z.boolean().default(false),
|
|
46
66
|
});
|
|
47
67
|
/**
|
|
@@ -78,6 +98,7 @@ export const RunSettingsSchema = z.object({
|
|
|
78
98
|
rotation: RotationSettingsSchema.default(() => RotationSettingsSchema.parse({})),
|
|
79
99
|
defaultBase: z.string().optional(),
|
|
80
100
|
mcp: z.boolean().default(true),
|
|
101
|
+
mcpAllowlist: z.array(z.string()).optional(),
|
|
81
102
|
retry: z.boolean().default(true),
|
|
82
103
|
autoWaitMinutes: z.number().min(0).default(0),
|
|
83
104
|
staleBranchThreshold: z.number().default(5),
|
|
@@ -104,6 +125,18 @@ export const RunSettingsSchema = z.object({
|
|
|
104
125
|
* `effort-escalation.ts` for the resolver.
|
|
105
126
|
*/
|
|
106
127
|
effortEscalation: z.boolean().default(false),
|
|
128
|
+
/**
|
|
129
|
+
* Whether `/fullsolve`'s Phase 5.3 merges the PR automatically once QA
|
|
130
|
+
* passes (#958). Default `false` — preserves the human merge gate kept by
|
|
131
|
+
* #817–#819. Overridable per-invocation with `--auto-merge`.
|
|
132
|
+
*/
|
|
133
|
+
autoMerge: z.boolean().default(false),
|
|
134
|
+
/**
|
|
135
|
+
* Semantic role → model string map (#975). See `ModelRolesSchema` and
|
|
136
|
+
* `DEFAULT_MODEL_ROLES` for the shipped defaults. Absent from generated
|
|
137
|
+
* settings — absent key → defaults apply. Do not emit in `generateSettingsJsonc`.
|
|
138
|
+
*/
|
|
139
|
+
modelRoles: ModelRolesSchema.default(() => ({ ...DEFAULT_MODEL_ROLES })),
|
|
107
140
|
});
|
|
108
141
|
/** Zod schema for ScopeThreshold (base — fields required, no defaults) */
|
|
109
142
|
export const ScopeThresholdSchema = z.object({
|
|
@@ -216,6 +249,7 @@ const KNOWN_KEYS = {
|
|
|
216
249
|
"rotation",
|
|
217
250
|
"defaultBase",
|
|
218
251
|
"mcp",
|
|
252
|
+
"mcpAllowlist",
|
|
219
253
|
"retry",
|
|
220
254
|
"staleBranchThreshold",
|
|
221
255
|
"resolvedIssueTTL",
|
|
@@ -225,6 +259,7 @@ const KNOWN_KEYS = {
|
|
|
225
259
|
"aider",
|
|
226
260
|
"relay",
|
|
227
261
|
"phases",
|
|
262
|
+
"modelRoles",
|
|
228
263
|
]),
|
|
229
264
|
// #914: keyed by real phase name so a typo (`run.phases.exce`) warns
|
|
230
265
|
// instead of silently resolving to nothing. Computed from the registry
|
|
@@ -451,6 +486,8 @@ export const DEFAULT_SETTINGS = {
|
|
|
451
486
|
resolvedIssueTTL: 7, // Auto-prune resolved issues after 7 days
|
|
452
487
|
relay: true, // Enable interactive relay (#383) by default
|
|
453
488
|
effortEscalation: false, // #915: off by default — raises token spend
|
|
489
|
+
autoMerge: false, // #958: off by default — preserves the human merge gate
|
|
490
|
+
modelRoles: DEFAULT_MODEL_ROLES, // #975: shipped defaults; absent key → these
|
|
454
491
|
},
|
|
455
492
|
agents: DEFAULT_AGENT_SETTINGS,
|
|
456
493
|
scopeAssessment: DEFAULT_SCOPE_ASSESSMENT_SETTINGS,
|
|
@@ -581,6 +618,10 @@ export function generateSettingsJsonc(settings) {
|
|
|
581
618
|
lines.push(` "smartTests": ${JSON.stringify(settings.run.smartTests)},`);
|
|
582
619
|
lines.push(` // Enable MCP servers in headless mode`);
|
|
583
620
|
lines.push(` "mcp": ${JSON.stringify(settings.run.mcp)},`);
|
|
621
|
+
if (settings.run.mcpAllowlist !== undefined) {
|
|
622
|
+
lines.push(` // Desktop MCP servers explicitly allowlisted for phases`);
|
|
623
|
+
lines.push(` "mcpAllowlist": ${JSON.stringify(settings.run.mcpAllowlist)},`);
|
|
624
|
+
}
|
|
584
625
|
lines.push(` // Enable automatic retry with MCP fallback`);
|
|
585
626
|
lines.push(` "retry": ${JSON.stringify(settings.run.retry)},`);
|
|
586
627
|
lines.push(` // Commits behind main before warning`);
|
|
@@ -603,7 +644,7 @@ export function generateSettingsJsonc(settings) {
|
|
|
603
644
|
lines.push(` "agents": {`);
|
|
604
645
|
lines.push(` // Run agents in parallel (faster, higher token usage)`);
|
|
605
646
|
lines.push(` "parallel": ${JSON.stringify(settings.agents.parallel)},`);
|
|
606
|
-
lines.push(` // Default model for sub-agents (
|
|
647
|
+
lines.push(` // Default model for sub-agents (any alias/ID) — currently inert per anthropics/claude-code#43869`);
|
|
607
648
|
lines.push(` "model": ${JSON.stringify(settings.agents.model)},`);
|
|
608
649
|
lines.push(` // Isolate parallel agent groups in separate worktrees`);
|
|
609
650
|
lines.push(` "isolateParallel": ${JSON.stringify(settings.agents.isolateParallel)}`);
|
|
@@ -698,6 +739,7 @@ Generated by \`sequant init\`. See defaults below.
|
|
|
698
739
|
| \`smartTests\` | boolean | \`true\` | Enable smart test detection |
|
|
699
740
|
| \`defaultBase\` | string | — | Default base branch for worktree creation |
|
|
700
741
|
| \`mcp\` | boolean | \`true\` | Enable MCP servers in headless mode |
|
|
742
|
+
| \`mcpAllowlist\` | string[] | — | Desktop MCP server names to pass through to phases despite \`mcp\`'s default exclusion (#936) |
|
|
701
743
|
| \`retry\` | boolean | \`true\` | Enable automatic retry with MCP fallback |
|
|
702
744
|
| \`staleBranchThreshold\` | number | \`5\` | Commits behind main before warning |
|
|
703
745
|
| \`resolvedIssueTTL\` | number | \`7\` | Days before resolved issues auto-prune (0=never, -1=immediate) |
|
|
@@ -724,7 +766,7 @@ Generated by \`sequant init\`. See defaults below.
|
|
|
724
766
|
| Key | Type | Default | Description |
|
|
725
767
|
|-----|------|---------|-------------|
|
|
726
768
|
| \`parallel\` | boolean | \`false\` | Run agents in parallel (faster, higher token usage) |
|
|
727
|
-
| \`model\` |
|
|
769
|
+
| \`model\` | string | \`"haiku"\` | Default model (any alias or dated ID). **Currently inert** per [anthropics/claude-code#43869](https://github.com/anthropics/claude-code/issues/43869) — subagents inherit the parent session's model. See \`run.modelRoles\` for semantic roles (#975). |
|
|
728
770
|
| \`isolateParallel\` | boolean | \`false\` | Isolate parallel agents in separate worktrees |
|
|
729
771
|
|
|
730
772
|
## \`scopeAssessment\` — Scope Assessment Settings
|
package/dist/src/lib/system.d.ts
CHANGED
|
@@ -54,9 +54,13 @@ export interface McpServerConfig {
|
|
|
54
54
|
/**
|
|
55
55
|
* Get full MCP server configurations from Claude Desktop config
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* For interactive `doctor` diagnostics only — reports what's configured for
|
|
58
|
+
* the human user. Never pass this to phase execution (#936): Claude Desktop
|
|
59
|
+
* is a different trust domain (interactive, human-supervised) than an
|
|
60
|
+
* autonomous phase agent, and desktop configs carry literal secrets since
|
|
61
|
+
* they cannot use `${VAR}` references. Phase agents use
|
|
62
|
+
* `getPhaseMcpServersConfig` from `mcp-config.ts` instead, which allowlists
|
|
63
|
+
* from the project's own `.mcp.json`.
|
|
60
64
|
*
|
|
61
65
|
* @returns MCP server configurations or undefined
|
|
62
66
|
*/
|
package/dist/src/lib/system.js
CHANGED
|
@@ -136,9 +136,13 @@ export function getConfiguredMcpServers() {
|
|
|
136
136
|
/**
|
|
137
137
|
* Get full MCP server configurations from Claude Desktop config
|
|
138
138
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
139
|
+
* For interactive `doctor` diagnostics only — reports what's configured for
|
|
140
|
+
* the human user. Never pass this to phase execution (#936): Claude Desktop
|
|
141
|
+
* is a different trust domain (interactive, human-supervised) than an
|
|
142
|
+
* autonomous phase agent, and desktop configs carry literal secrets since
|
|
143
|
+
* they cannot use `${VAR}` references. Phase agents use
|
|
144
|
+
* `getPhaseMcpServersConfig` from `mcp-config.ts` instead, which allowlists
|
|
145
|
+
* from the project's own `.mcp.json`.
|
|
142
146
|
*
|
|
143
147
|
* @returns MCP server configurations or undefined
|
|
144
148
|
*/
|
|
@@ -390,12 +390,20 @@ const PROJECT_SCRIPT_PATTERN = /\b(?:hooks\/[\w.-]+\.sh|scripts\/[\w./-]+)/;
|
|
|
390
390
|
* across declarations, and must reach one of the two markers. Spawning a
|
|
391
391
|
* *system* binary (`git`, `bash` with a temp fixture) matches neither, which
|
|
392
392
|
* is the intended exclusion — those are not this project's code.
|
|
393
|
+
*
|
|
394
|
+
* An optional type annotation is allowed between the name and `=`, e.g.
|
|
395
|
+
* const HOOK_COPIES: Array<[label: string, path: string]> = [...];
|
|
396
|
+
* The annotation submatch (`[^=;]*`) stops at the first `=`, so an arrow-
|
|
397
|
+
* function-typed annotation (`const f: (x: string) => void = ...`) breaks
|
|
398
|
+
* the match at its `=>` instead of reaching the real assignment — accepted
|
|
399
|
+
* as a narrow miss; typed function-value declarations are not the shape this
|
|
400
|
+
* collector targets (path-bearing table/tuple declarations are).
|
|
393
401
|
*/
|
|
394
402
|
function collectBuildOutputVars(content) {
|
|
395
403
|
const names = new Set();
|
|
396
404
|
const patterns = [
|
|
397
|
-
/(?:const|let|var)\s+(\w+)\s
|
|
398
|
-
/(?:const|let|var)\s+(\w+)\s
|
|
405
|
+
/(?:const|let|var)\s+(\w+)\s*(?::[^=;]*)?=\s*[^;]*?\bdist\//g,
|
|
406
|
+
/(?:const|let|var)\s+(\w+)\s*(?::[^=;]*)?=\s*[^;]*?\b(?:hooks\/[\w.-]+\.sh|scripts\/[\w./-]+)/g,
|
|
399
407
|
];
|
|
400
408
|
for (const pattern of patterns) {
|
|
401
409
|
let match;
|
|
@@ -405,6 +413,44 @@ function collectBuildOutputVars(content) {
|
|
|
405
413
|
}
|
|
406
414
|
return [...names];
|
|
407
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Collect callback parameter names bound to a build-output table's rows via
|
|
418
|
+
* `describe.each(X)("...", (a, b) => {...})`, when `X` is itself a
|
|
419
|
+
* build-output source: a var already collected by collectBuildOutputVars, or
|
|
420
|
+
* an inline array literal containing a build-output token directly.
|
|
421
|
+
*
|
|
422
|
+
* `describe.each` destructures each table row into positional callback
|
|
423
|
+
* params. A helper spawning `hookPath` — the param, not the table var — is
|
|
424
|
+
* exercising production code just as much as one spawning `HOOK_COPIES`
|
|
425
|
+
* directly; static analysis of the table alone misses it entirely. The
|
|
426
|
+
* string-title argument is matched by quote char (not `[^()]*`) because test
|
|
427
|
+
* titles routinely contain literal parens (e.g. `"... (#564) [%s]"`), which
|
|
428
|
+
* a paren-excluding class would truncate on.
|
|
429
|
+
*
|
|
430
|
+
* Bounded like the rest of this file's helper matchers: no nested parens in
|
|
431
|
+
* the table-var/params captures, so a callback with a destructured or
|
|
432
|
+
* default-valued param is skipped rather than mis-parsed.
|
|
433
|
+
*/
|
|
434
|
+
function collectDescribeEachParams(content, buildOutputVars) {
|
|
435
|
+
const names = new Set();
|
|
436
|
+
const pattern = /describe\.each\(\s*([^()]*?)\s*\)\s*\(\s*(['"`])(?:(?!\2)[\s\S])*?\2\s*,\s*(?:async\s+)?\(([^()]*)\)\s*=>/g;
|
|
437
|
+
let match;
|
|
438
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
439
|
+
const tableArg = match[1].trim();
|
|
440
|
+
const isKnownVar = /^\w+$/.test(tableArg) && buildOutputVars.includes(tableArg);
|
|
441
|
+
const isInlineSource = BUILD_OUTPUT_PATTERN.test(tableArg) ||
|
|
442
|
+
PROJECT_SCRIPT_PATTERN.test(tableArg);
|
|
443
|
+
if (!isKnownVar && !isInlineSource)
|
|
444
|
+
continue;
|
|
445
|
+
for (const param of match[3].split(",")) {
|
|
446
|
+
const name = param.trim().split(":")[0].trim();
|
|
447
|
+
if (/^\w+$/.test(name)) {
|
|
448
|
+
names.add(name);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return [...names];
|
|
453
|
+
}
|
|
408
454
|
/**
|
|
409
455
|
* Whether a code body references a build-output token: either the literal
|
|
410
456
|
* `dist/` marker or one of the collected build-path variable names.
|
|
@@ -588,7 +634,8 @@ export function analyzeTestFile(content, filePath) {
|
|
|
588
634
|
}
|
|
589
635
|
try {
|
|
590
636
|
const importedFunctions = extractImports(content);
|
|
591
|
-
const
|
|
637
|
+
const declaredBuildOutputVars = collectBuildOutputVars(content);
|
|
638
|
+
const buildOutputVars = declaredBuildOutputVars.concat(collectDescribeEachParams(content, declaredBuildOutputVars));
|
|
592
639
|
const productionHandles = collectProductionHandles(content, buildOutputVars, importedFunctions);
|
|
593
640
|
const testBlocks = extractTestBlocks(content);
|
|
594
641
|
const analyzedBlocks = testBlocks.map((block) => ({
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* creation).
|
|
9
9
|
*/
|
|
10
10
|
import { LogWriter } from "./log-writer.js";
|
|
11
|
-
import { ExecutionConfig, PhaseResult, IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext, type ProgressCallback } from "./types.js";
|
|
11
|
+
import { ExecutionConfig, PhaseResult, IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext, type ProgressCallback, type QaVerdict, type QaSummary } from "./types.js";
|
|
12
12
|
import { type ErrorCategory } from "./error-classifier.js";
|
|
13
13
|
export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutionContext, } from "./types.js";
|
|
14
14
|
/**
|
|
@@ -205,4 +205,23 @@ export declare function billingHaltReason(result: PhaseResult): string;
|
|
|
205
205
|
* @internal Exported for testing
|
|
206
206
|
*/
|
|
207
207
|
export declare function windowHaltResumeAtMs(result: PhaseResult): number | null;
|
|
208
|
+
/**
|
|
209
|
+
* Build the comment body for a standard-qa-phase verdict post (#964).
|
|
210
|
+
* Includes AC coverage and any gaps/suggestions from the parsed `QaSummary`,
|
|
211
|
+
* plus a machine marker so a future dedup pass has an anchor.
|
|
212
|
+
* @internal Exported for testing.
|
|
213
|
+
*/
|
|
214
|
+
export declare function buildQaVerdictComment(verdict: QaVerdict, summary: QaSummary | undefined, commitHash: string | undefined, iteration: number): string;
|
|
215
|
+
/**
|
|
216
|
+
* Post the qa-verdict comment for a standard (non-ready-gate) qa phase under
|
|
217
|
+
* orchestrated `sequant run` (#964). This is the channel `qa/SKILL.md` §9
|
|
218
|
+
* promises ("orchestrator handles aggregated summary") but batch-executor
|
|
219
|
+
* never backed — a re-run producing a fresh, different verdict left a stale,
|
|
220
|
+
* contradicted comment as the only externally-visible one.
|
|
221
|
+
*
|
|
222
|
+
* Best-effort: a post failure is caught and logged, never fails the run —
|
|
223
|
+
* mirrors {@link runReadyGateForIssue}'s `postReport` contract (#937 AC-4).
|
|
224
|
+
* @internal Exported for testing.
|
|
225
|
+
*/
|
|
226
|
+
export declare function postQaVerdictComment(issueNumber: number, verdict: QaVerdict, summary: QaSummary | undefined, commitHash: string | undefined, iteration: number, log: (msg: string) => void, postComment?: (issueNumber: number, body: string) => Promise<void>): Promise<void>;
|
|
208
227
|
export declare function runIssueWithLogging(ctx: IssueExecutionContext): Promise<IssueResult>;
|