@structured-vibe-coding/core 0.16.1 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,11 +10,31 @@ Used by [`@structured-vibe-coding/cli`](https://www.npmjs.com/package/@structure
10
10
  npm install @structured-vibe-coding/core
11
11
  ```
12
12
 
13
+ ## Architecture
14
+
15
+ ```text
16
+ cli → core → fs / zod / yaml / MCP SDK / typescript
17
+ ```
18
+
19
+ - **Public API:** only what is exported from `src/index.ts`.
20
+ - **Do not** import deep paths (`@structured-vibe-coding/core/src/...`) from apps.
21
+ - CLI owns UX (prompts, colors, agent launch). Core owns process state and pure logic.
22
+
23
+ ## Public surface (stable intent)
24
+
25
+ | Area | Examples |
26
+ |------|----------|
27
+ | Lifecycle | `initProject`, `createChange`, `advanceStage`, `completeChange` |
28
+ | Gates | `canLeaveStage`, `approveGate` |
29
+ | Greenfield | `startGreenfield`, `startFeatureFromBacklog` |
30
+ | Code context | `generateCodeContext` |
31
+ | MCP *client* | `loadMcpConfig`, `addMcpSource`, `gatherMcpContext` |
32
+ | Errors | `SddError`, `isSddError` |
33
+
13
34
  ## Docs
14
35
 
15
- - **Site (GitHub Pages):** https://harsha09.github.io/spec-driven-development/
16
- - [First change tutorial](https://harsha09.github.io/spec-driven-development/tutorials/first-change/)
17
- - [Greenfield](https://harsha09.github.io/spec-driven-development/guides/greenfield/)
36
+ - **Site:** https://harsha09.github.io/spec-driven-development/
37
+ - [MCP sources](https://harsha09.github.io/spec-driven-development/guides/mcp/)
18
38
  - [Repository](https://github.com/harsha09/spec-driven-development)
19
39
 
20
40
  ## License
@@ -19,11 +19,12 @@ import {
19
19
  removeOtherAgentHosts,
20
20
  renderThinAgent,
21
21
  writeAgentHandoff
22
- } from "./chunk-AOIPJ4FM.js";
23
- import "./chunk-M7YRPPNU.js";
24
- import "./chunk-OJFMJHNL.js";
22
+ } from "./chunk-BTUI2WYQ.js";
23
+ import "./chunk-BCLKMBKF.js";
24
+ import "./chunk-WHDG6OTG.js";
25
25
  import "./chunk-A752UVPF.js";
26
26
  import "./chunk-67AUAGNV.js";
27
+ import "./chunk-MHODNFIL.js";
27
28
  import "./chunk-7MJYPJMR.js";
28
29
  import "./chunk-EPFURATC.js";
29
30
  export {
@@ -3,22 +3,71 @@ import {
3
3
  interpolateArgs,
4
4
  loadMcpConfig
5
5
  } from "./chunk-KYWETNFU.js";
6
+ import {
7
+ SddError
8
+ } from "./chunk-MHODNFIL.js";
6
9
 
7
10
  // src/mcp/client.ts
8
11
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
12
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
13
+
14
+ // src/env.ts
15
+ function toProcessEnv(base, extra) {
16
+ const out = {};
17
+ for (const [k, v] of Object.entries(base)) {
18
+ if (v !== void 0) out[k] = String(v);
19
+ }
20
+ if (extra) {
21
+ for (const [k, v] of Object.entries(extra)) {
22
+ if (v !== void 0) out[k] = String(v);
23
+ }
24
+ }
25
+ return out;
26
+ }
27
+
28
+ // src/version.ts
29
+ var SDD_CORE_VERSION = "0.16.2" ? "0.16.2" : "0.0.0-dev";
30
+
31
+ // src/mcp/client.ts
32
+ function mcpTimeoutMs() {
33
+ const raw = process.env.SDD_MCP_TIMEOUT_MS;
34
+ if (raw) {
35
+ const n = Number(raw);
36
+ if (Number.isFinite(n) && n > 0) return n;
37
+ }
38
+ return 3e4;
39
+ }
10
40
  function contentToText(result) {
11
41
  const parts = result.content ?? [];
12
42
  return parts.map((p) => p.type === "text" ? p.text ?? "" : JSON.stringify(p)).join("\n").trim();
13
43
  }
44
+ async function withTimeout(promise, ms, label) {
45
+ let timer;
46
+ try {
47
+ return await Promise.race([
48
+ promise,
49
+ new Promise((_, reject) => {
50
+ timer = setTimeout(() => {
51
+ reject(
52
+ new SddError(`MCP ${label} timed out after ${ms}ms`, {
53
+ code: "MCP_TIMEOUT",
54
+ hint: "Raise SDD_MCP_TIMEOUT_MS or fix the source server."
55
+ })
56
+ );
57
+ }, ms);
58
+ })
59
+ ]);
60
+ } finally {
61
+ if (timer) clearTimeout(timer);
62
+ }
63
+ }
14
64
  async function withMcpSource(source, projectRoot, fn) {
15
65
  const vars = { projectRoot };
16
66
  const cwd = source.cwd ? interpolate(source.cwd, vars) : projectRoot;
17
- const env = {
18
- ...process.env,
67
+ const env = toProcessEnv(process.env, {
19
68
  ...source.env,
20
69
  SDD_PROJECT_ROOT: projectRoot
21
- };
70
+ });
22
71
  const transport = new StdioClientTransport({
23
72
  command: source.command,
24
73
  args: source.args,
@@ -27,11 +76,12 @@ async function withMcpSource(source, projectRoot, fn) {
27
76
  });
28
77
  const client = new Client({
29
78
  name: "sdd-mcp-client",
30
- version: "0.14.0"
79
+ version: SDD_CORE_VERSION
31
80
  });
32
- await client.connect(transport);
81
+ const ms = mcpTimeoutMs();
82
+ await withTimeout(client.connect(transport), ms, `connect (${source.id})`);
33
83
  try {
34
- return await fn(client);
84
+ return await withTimeout(fn(client), ms, `call (${source.id})`);
35
85
  } finally {
36
86
  try {
37
87
  await client.close();
@@ -156,16 +206,8 @@ async function gatherMcpContext(opts) {
156
206
  continue;
157
207
  }
158
208
  const baseArgs = opts.args ?? source.invoke?.args ?? {};
159
- const args = interpolateArgs(
160
- baseArgs,
161
- vars
162
- );
163
- const called = await callMcpSourceTool(
164
- source,
165
- opts.projectRoot,
166
- tool,
167
- args
168
- );
209
+ const args = interpolateArgs(baseArgs, vars);
210
+ const called = await callMcpSourceTool(source, opts.projectRoot, tool, args);
169
211
  const max = cfg.max_chars_per_source ?? 6e3;
170
212
  let text = called.text;
171
213
  if (text.length > max) {
@@ -224,6 +266,9 @@ function listMatchingSourceIds(sources, match) {
224
266
  }
225
267
 
226
268
  export {
269
+ toProcessEnv,
270
+ SDD_CORE_VERSION,
271
+ mcpTimeoutMs,
227
272
  withMcpSource,
228
273
  listMcpSourceTools,
229
274
  callMcpSourceTool,
@@ -50,9 +50,7 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
50
50
  parts.push(`- Title: ${ctx.meta.title}`);
51
51
  parts.push(`- ID: ${ctx.id}`);
52
52
  parts.push(`- Workflow: ${ctx.meta.workflow}`);
53
- parts.push(
54
- `- Current stage: ${ctx.meta.stage}${stage?.title ? ` (${stage.title})` : ""}`
55
- );
53
+ parts.push(`- Current stage: ${ctx.meta.stage}${stage?.title ? ` (${stage.title})` : ""}`);
56
54
  parts.push(`- Change path: ${ctx.path}`);
57
55
  parts.push("");
58
56
  if (stage?.summary) {
@@ -93,53 +91,41 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
93
91
  if (stageId === "implement" || stageId === "local_verify") {
94
92
  parts.push(`## Code context (product code)`);
95
93
  parts.push("");
96
- parts.push(
97
- `Process context above is not enough for product code structure.`
98
- );
99
- parts.push(
100
- `When you need symbols, callers, or package-boundary context:`
101
- );
94
+ parts.push(`Process context above is not enough for product code structure.`);
95
+ parts.push(`When you need symbols, callers, or package-boundary context:`);
102
96
  parts.push(
103
97
  `1. Run: \`sdd context\` (optional: \`--path \u2026\`, \`--symbol \u2026\`, \`--out change\`)`
104
98
  );
105
- parts.push(
106
- `2. If present, read: \`changes/${ctx.id}/code-context.md\` (regenerate if stale)`
107
- );
99
+ parts.push(`2. If present, read: \`changes/${ctx.id}/code-context.md\` (regenerate if stale)`);
108
100
  parts.push(
109
101
  `3. Org libraries / remote AST: \`.sdd/mcp.yaml\` sources via \`sdd mcp sources\` / \`sdd mcp fetch\``
110
102
  );
111
- parts.push(
112
- `Do not dump unbounded source into the prompt; prefer sliced / MCP output.`
113
- );
103
+ parts.push(`Do not dump unbounded source into the prompt; prefer sliced / MCP output.`);
114
104
  parts.push("");
115
105
  }
116
106
  try {
117
107
  const { loadMcpConfig } = await import("./sources-PF45EUH2.js");
118
- const {
119
- gatherMcpContextForChange,
120
- formatMcpContextForHandoff
121
- } = await import("./gather-74DQ35QX.js");
108
+ const { gatherMcpContextForChange, formatMcpContextForHandoff } = await import("./gather-2LQ464XU.js");
122
109
  const mcpCfg = await loadMcpConfig(projectRoot);
123
110
  if (mcpCfg.auto_fetch_on_handoff && mcpCfg.sources.length) {
124
111
  const fetched = await gatherMcpContextForChange(projectRoot, ctx);
125
- const block = formatMcpContextForHandoff(
126
- fetched.filter((r) => r.ok || r.error)
127
- );
112
+ const block = formatMcpContextForHandoff(fetched.filter((r) => r.ok || r.error));
128
113
  if (block) {
129
114
  parts.push(block);
130
115
  }
131
116
  } else if (mcpCfg.sources.length === 0) {
132
117
  parts.push(`## External MCP sources`);
133
118
  parts.push("");
134
- parts.push(
135
- `No sources in \`.sdd/mcp.yaml\` yet. To attach org libraries / AST engines:`
136
- );
137
- parts.push(
138
- `\`sdd mcp sources add --help\` \xB7 docs: Configure MCP sources`
139
- );
119
+ parts.push(`No sources in \`.sdd/mcp.yaml\` yet. To attach org libraries / AST engines:`);
120
+ parts.push(`\`sdd mcp sources add --help\` \xB7 docs: Configure MCP sources`);
140
121
  parts.push("");
141
122
  }
142
- } catch {
123
+ } catch (err) {
124
+ const msg = err instanceof Error ? err.message : String(err);
125
+ parts.push(`## External MCP sources`);
126
+ parts.push("");
127
+ parts.push(`_MCP auto-fetch skipped (non-fatal): ${msg}_`);
128
+ parts.push("");
143
129
  }
144
130
  parts.push(
145
131
  `When done with this stage, the human will run \`sdd next\` or \`sdd verify\` / \`sdd complete\`.`
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  buildAgentPrompt,
3
3
  formatStatus
4
- } from "./chunk-M7YRPPNU.js";
4
+ } from "./chunk-BCLKMBKF.js";
5
5
  import {
6
6
  loadConfig
7
- } from "./chunk-OJFMJHNL.js";
7
+ } from "./chunk-WHDG6OTG.js";
8
8
  import {
9
9
  buildContext,
10
10
  getActiveChangeId
@@ -86,14 +86,7 @@ var AGENT_TARGET_OPTIONS = AGENT_INTEGRATIONS.map(
86
86
  installUrl
87
87
  })
88
88
  );
89
- var IDE_NAMES = /* @__PURE__ */ new Set([
90
- "intellij",
91
- "idea",
92
- "jetbrains",
93
- "vscode",
94
- "vs-code",
95
- "cursor"
96
- ]);
89
+ var IDE_NAMES = /* @__PURE__ */ new Set(["intellij", "idea", "jetbrains", "vscode", "vs-code", "cursor"]);
97
90
  function getIntegration(target) {
98
91
  const found = AGENT_INTEGRATIONS.find((i) => i.id === target);
99
92
  if (!found) throw new Error(`Unknown agent integration: ${target}`);
@@ -0,0 +1,26 @@
1
+ // src/errors.ts
2
+ var SddError = class extends Error {
3
+ code;
4
+ hint;
5
+ exitCode;
6
+ constructor(message, options) {
7
+ super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
8
+ this.name = "SddError";
9
+ this.code = options?.code ?? "SDD_ERROR";
10
+ this.hint = options?.hint;
11
+ this.exitCode = options?.exitCode ?? 1;
12
+ }
13
+ };
14
+ function isSddError(err) {
15
+ return err instanceof SddError;
16
+ }
17
+ function errorMessage(err) {
18
+ if (err instanceof Error) return err.message;
19
+ return String(err);
20
+ }
21
+
22
+ export {
23
+ SddError,
24
+ isSddError,
25
+ errorMessage
26
+ };
@@ -40,12 +40,6 @@ import {
40
40
  // src/greenfield.ts
41
41
  import { join as join3 } from "pathe";
42
42
 
43
- // src/change.ts
44
- import { join as join2 } from "pathe";
45
-
46
- // src/stage-gates.ts
47
- import { join } from "pathe";
48
-
49
43
  // src/artifacts.ts
50
44
  function isSubstantiveArtifactContent(content) {
51
45
  const stripped = content.replace(/<!--[\s\S]*?-->/g, "").replace(/^#{1,6}\s+.*$/gm, "").replace(/^>\s.*$/gm, "").replace(/^\s*[-*+]\s*$/gm, "").replace(/^\s*[-*+]\s*\.\.\.\s*$/gm, "").replace(/^\s*[-*+]\s*TBD\s*$/gim, "").replace(/^\s*[-*+]\s*TODO\s*$/gim, "").replace(/^\s*\|.*\|\s*$/gm, "").replace(/`+/g, "").trim();
@@ -73,7 +67,11 @@ function incompleteArtifactReason(path, content) {
73
67
  return null;
74
68
  }
75
69
 
70
+ // src/change.ts
71
+ import { join as join2 } from "pathe";
72
+
76
73
  // src/stage-gates.ts
74
+ import { join } from "pathe";
77
75
  async function checkRequiredArtifacts(changePath2, stageId, artifacts) {
78
76
  const errors = [];
79
77
  for (const artifact of artifacts) {
@@ -110,11 +108,7 @@ async function incompletePriorStageErrors(ctx) {
110
108
  }
111
109
  const required = stage.artifacts.filter((a) => a.required);
112
110
  if (!required.length && stage.optional) continue;
113
- const stageErrors = await checkRequiredArtifacts(
114
- ctx.path,
115
- stage.id,
116
- stage.artifacts
117
- );
111
+ const stageErrors = await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts);
118
112
  if (stageErrors.length) {
119
113
  errors.push(
120
114
  `Prior stage "${stage.id}" is not complete \u2014 finish it or \`sdd skip ${stage.id} -r "\u2026"\` if optional.`
@@ -132,9 +126,7 @@ async function canLeaveStage(ctx, config) {
132
126
  return { ok: false, errors: [`Unknown stage: ${ctx.meta.stage}`], warnings };
133
127
  }
134
128
  errors.push(...await incompletePriorStageErrors(ctx));
135
- errors.push(
136
- ...await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts)
137
- );
129
+ errors.push(...await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts));
138
130
  const declared = stage.gate?.type ?? "soft";
139
131
  let effectiveGate = declared;
140
132
  if (config.policy.gates === "hard" && declared === "soft") {
@@ -149,9 +141,7 @@ async function canLeaveStage(ctx, config) {
149
141
  }
150
142
  } else if (effectiveGate === "soft" && stage.gate?.checklist?.length) {
151
143
  if (!gateState || gateState.status === "pending") {
152
- warnings.push(
153
- `Soft gate checklist not signed off on "${stage.id}" (proceeding allowed).`
154
- );
144
+ warnings.push(`Soft gate checklist not signed off on "${stage.id}" (proceeding allowed).`);
155
145
  }
156
146
  }
157
147
  const requiredCmds = stage.verify?.commands?.filter((c) => c.required) ?? [];
@@ -245,7 +235,7 @@ async function createChange(input) {
245
235
  await materializeStageArtifacts(projectRoot, config, id, workflow, meta, firstStage);
246
236
  await setActiveChange(projectRoot, config, id);
247
237
  try {
248
- const { refreshActiveAgentContext } = await import("./agents-TJQMAD24.js");
238
+ const { refreshActiveAgentContext } = await import("./agents-3MD2GPYI.js");
249
239
  await refreshActiveAgentContext(projectRoot);
250
240
  } catch {
251
241
  }
@@ -312,7 +302,7 @@ function defaultArtifactContent(artifactId, meta, stage, workflow) {
312
302
  `;
313
303
  }
314
304
  async function advanceStage(projectRoot, config, changeId, opts) {
315
- let ctx = await buildContext(projectRoot, config, changeId);
305
+ const ctx = await buildContext(projectRoot, config, changeId);
316
306
  const warnings = [];
317
307
  if (ctx.meta.status === "completed") {
318
308
  throw new Error("Change is already completed.");
@@ -333,7 +323,7 @@ async function advanceStage(projectRoot, config, changeId, opts) {
333
323
  stage2
334
324
  );
335
325
  try {
336
- const { refreshActiveAgentContext } = await import("./agents-TJQMAD24.js");
326
+ const { refreshActiveAgentContext } = await import("./agents-3MD2GPYI.js");
337
327
  await refreshActiveAgentContext(projectRoot);
338
328
  } catch {
339
329
  }
@@ -380,7 +370,7 @@ async function advanceStage(projectRoot, config, changeId, opts) {
380
370
  stage
381
371
  );
382
372
  try {
383
- const { refreshActiveAgentContext } = await import("./agents-TJQMAD24.js");
373
+ const { refreshActiveAgentContext } = await import("./agents-3MD2GPYI.js");
384
374
  await refreshActiveAgentContext(projectRoot);
385
375
  } catch {
386
376
  }
@@ -412,10 +402,7 @@ async function skipStage(projectRoot, config, changeId, stageId, reason) {
412
402
  const skip_stages = new Set(ctx.meta.overrides.skip_stages ?? []);
413
403
  skip_stages.add(stageId);
414
404
  ctx.meta.overrides.skip_stages = [...skip_stages];
415
- ctx.meta.skipped = [
416
- ...ctx.meta.skipped ?? [],
417
- { stage: stageId, reason, at: nowIso() }
418
- ];
405
+ ctx.meta.skipped = [...ctx.meta.skipped ?? [], { stage: stageId, reason, at: nowIso() }];
419
406
  if (ctx.meta.stage === stageId) {
420
407
  const next = nextStageId(ctx.workflow, ctx.meta);
421
408
  if (next) {
@@ -484,7 +471,7 @@ async function completeChange(projectRoot, config, changeId) {
484
471
  let promoted;
485
472
  if (ctx.meta.workflow === "greenfield" || ctx.meta.flags?.greenfield === true) {
486
473
  try {
487
- const { promoteGreenfieldToMemory: promoteGreenfieldToMemory2 } = await import("./greenfield-PKVWSKHN.js");
474
+ const { promoteGreenfieldToMemory: promoteGreenfieldToMemory2 } = await import("./greenfield-YUAMU7F3.js");
488
475
  promoted = await promoteGreenfieldToMemory2(projectRoot, config, changeId);
489
476
  } catch {
490
477
  }
@@ -529,7 +516,9 @@ var FEATURE_HEADER = /^##\s+(F-\d+)\s*:\s*(.+?)\s*$/i;
529
516
  async function startGreenfield(input) {
530
517
  const idea = input.idea.trim();
531
518
  if (!idea || idea.length < 3) {
532
- throw new Error('Provide a one-line product idea, e.g. sdd greenfield "Team expense tracker for remote startups"');
519
+ throw new Error(
520
+ 'Provide a one-line product idea, e.g. sdd greenfield "Team expense tracker for remote startups"'
521
+ );
533
522
  }
534
523
  const title = idea.length > 80 ? `Greenfield: ${idea.slice(0, 77)}\u2026` : `Greenfield: ${idea}`;
535
524
  const ctx = await createChange({
@@ -546,14 +535,11 @@ async function startGreenfield(input) {
546
535
  body = body.replaceAll("{{idea}}", idea);
547
536
  await writeText(visionPath, body);
548
537
  } else if (!body.includes(idea)) {
549
- body = body.replace(
550
- /## One-liner\n+(\s*[-–—]\s*\n)?/,
551
- `## One-liner
538
+ body = body.replace(/## One-liner\n+(\s*[-–—]\s*\n)?/, `## One-liner
552
539
 
553
540
  ${idea}
554
541
 
555
- `
556
- );
542
+ `);
557
543
  if (!body.includes(idea)) {
558
544
  body = `${body.trim()}
559
545
 
@@ -647,9 +633,7 @@ async function listBacklogFeatures(projectRoot, config) {
647
633
  }
648
634
  const features = parseFeaturesBacklog(await readText(path), path);
649
635
  if (!features.length) {
650
- throw new Error(
651
- `No F-NNN features parsed in ${path}. Use headings like: ## F-001: Short name`
652
- );
636
+ throw new Error(`No F-NNN features parsed in ${path}. Use headings like: ## F-001: Short name`);
653
637
  }
654
638
  return { path, features };
655
639
  }
@@ -658,10 +642,7 @@ async function startFeatureFromBacklog(input) {
658
642
  if (!/^F-\d+$/i.test(id)) {
659
643
  throw new Error(`Feature id must look like F-001 (got "${input.featureId}")`);
660
644
  }
661
- const { path, features } = await listBacklogFeatures(
662
- input.projectRoot,
663
- input.config
664
- );
645
+ const { path, features } = await listBacklogFeatures(input.projectRoot, input.config);
665
646
  const feature = features.find((f) => f.id === id);
666
647
  if (!feature) {
667
648
  const known = features.map((f) => f.id).join(", ");
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  ConfigSchema
3
3
  } from "./chunk-67AUAGNV.js";
4
+ import {
5
+ SddError
6
+ } from "./chunk-MHODNFIL.js";
4
7
  import {
5
8
  configPath,
6
9
  sddRoot
@@ -19,9 +22,10 @@ async function isInitialized(projectRoot) {
19
22
  async function loadConfig(projectRoot) {
20
23
  const path = configPath(projectRoot);
21
24
  if (!await pathExists(path)) {
22
- throw new Error(
23
- `SDD is not initialized in ${projectRoot}. Run \`sdd init\` first.`
24
- );
25
+ throw new SddError(`SDD is not initialized in ${projectRoot}. Run \`sdd init\` first.`, {
26
+ code: "NOT_INITIALIZED",
27
+ hint: "cd into your app and run: sdd init --here --ai copilot"
28
+ });
25
29
  }
26
30
  const raw = await readYaml(path);
27
31
  return ConfigSchema.parse(raw);
@@ -3,8 +3,9 @@ import {
3
3
  isInitialized,
4
4
  loadConfig,
5
5
  saveConfig
6
- } from "./chunk-OJFMJHNL.js";
6
+ } from "./chunk-WHDG6OTG.js";
7
7
  import "./chunk-67AUAGNV.js";
8
+ import "./chunk-MHODNFIL.js";
8
9
  import "./chunk-7MJYPJMR.js";
9
10
  import "./chunk-EPFURATC.js";
10
11
  export {
@@ -3,8 +3,9 @@ import {
3
3
  gatherMcpContext,
4
4
  gatherMcpContextForChange,
5
5
  listMatchingSourceIds
6
- } from "./chunk-VD3CPFAI.js";
6
+ } from "./chunk-2C3WHUAR.js";
7
7
  import "./chunk-KYWETNFU.js";
8
+ import "./chunk-MHODNFIL.js";
8
9
  import "./chunk-7MJYPJMR.js";
9
10
  import "./chunk-EPFURATC.js";
10
11
  export {
@@ -6,8 +6,8 @@ import {
6
6
  startFeatureFromBacklog,
7
7
  startGreenfield,
8
8
  updateFeatureStatus
9
- } from "./chunk-BIY3WKP3.js";
10
- import "./chunk-M7YRPPNU.js";
9
+ } from "./chunk-TNZHQ6B5.js";
10
+ import "./chunk-BCLKMBKF.js";
11
11
  import "./chunk-A752UVPF.js";
12
12
  import "./chunk-67AUAGNV.js";
13
13
  import "./chunk-7MJYPJMR.js";
package/dist/index.d.ts CHANGED
@@ -1431,6 +1431,33 @@ declare function copyDirSkipExisting(src: string, dest: string): Promise<void>;
1431
1431
  declare function writeTextIfMissing(path: string, content: string): Promise<boolean>;
1432
1432
  declare function removePath(path: string): Promise<void>;
1433
1433
 
1434
+ /**
1435
+ * Typed, user-facing errors for sdd core.
1436
+ * CLI maps these to exit codes and optional hints.
1437
+ */
1438
+ declare class SddError extends Error {
1439
+ readonly code: string;
1440
+ readonly hint?: string;
1441
+ readonly exitCode: number;
1442
+ constructor(message: string, options?: {
1443
+ code?: string;
1444
+ hint?: string;
1445
+ exitCode?: number;
1446
+ cause?: unknown;
1447
+ });
1448
+ }
1449
+ declare function isSddError(err: unknown): err is SddError;
1450
+ /** Normalize unknown throws into Error for logging. */
1451
+ declare function errorMessage(err: unknown): string;
1452
+
1453
+ /**
1454
+ * Sanitize env maps for child processes (MCP stdio, verify commands).
1455
+ */
1456
+ /** Drop undefined; coerce values to strings for spawn env. */
1457
+ declare function toProcessEnv(base: NodeJS.ProcessEnv | Record<string, string | undefined>, extra?: Record<string, string | undefined>): Record<string, string>;
1458
+
1459
+ declare const SDD_CORE_VERSION: string;
1460
+
1434
1461
  /**
1435
1462
  * Public + internal types for the on-demand structure-aware code context pipeline.
1436
1463
  * Contracts: LLD §2.1 / research §6.5.
@@ -2122,6 +2149,8 @@ declare function explainMatch(source: McpSource, ctx: McpMatchContext): string;
2122
2149
  * sdd as MCP *client* — connect to external stdio MCP servers and call tools.
2123
2150
  */
2124
2151
 
2152
+ /** Default timeout for connect + tool call (ms). Override with SDD_MCP_TIMEOUT_MS. */
2153
+ declare function mcpTimeoutMs(): number;
2125
2154
  interface CallMcpToolResult {
2126
2155
  ok: boolean;
2127
2156
  text: string;
@@ -2159,4 +2188,4 @@ declare function gatherMcpContextForChange(projectRoot: string, ctx: ChangeConte
2159
2188
  declare function formatMcpContextForHandoff(results: McpFetchResult[]): string;
2160
2189
  declare function listMatchingSourceIds(sources: McpSource[], match: McpMatchContext): string[];
2161
2190
 
2162
- export { AGENT_INTEGRATIONS, AGENT_TARGET_OPTIONS, ALL_AGENT_TARGETS, type AdapterExtractResult, AdapterRegistry, type AdvanceResult, type AgentIntegration, type AgentTarget, type AgentTargetOption, type ArtifactDef, ArtifactDefSchema, type BuildRefinePlanOptions, type CallMcpToolResult, type ChangeContext, type ChangeMeta, ChangeMetaSchema, type CodeContextCaps, type CodeContextGap, type CodeContextGapCode, type CodeContextRequest, type CodeContextResult, type CodeContextSummary, type CodeSlice, type Complexity, type Config, ConfigSchema, type CreateChangeInput, DEFAULT_CODE_CONTEXT_CAPS, DEFAULT_INIT_INTEGRATION, type FocusPlan, type Gate, GateSchema, type GatherMcpOptions, type GenerateCodeContextDeps, type GreenfieldFeature, type InitOptions, type InitResult, type InstallAgentOptions, type InstallAgentResult, type InstallAgentsOptions, type InstallAgentsResult, type InstalledAgentSnapshot, type LanguageAdapter, type McpConfig, McpConfigSchema, type McpFetchResult, type McpInvoke, McpInvokeSchema, type McpMatchContext, type McpSource, McpSourceSchema, type McpWhen, McpWhenSchema, PROTOCOL_MD, type RefineArtifactRef, type RefineMode, type RefinePlan, SDD_AGENT_ROLES, SDD_DIR, type SddAgentRole, type SddAgentRoleId, type Stage, StageSchema, type StartFeatureInput, type StartGreenfieldInput, type StartGreenfieldResult, type SymbolInfo, type SymbolKind, VerifyCommandSchema, type VerifyResult, type Workflow, WorkflowSchema, activePointerPath, activeStages, addMcpSource, advanceStage, agentHostPaths, agentKickoffMessage, approveGate, archiveDir, buildAgentPrompt, buildContext, buildRefinePlan, callMcpSourceTool, canLeaveStage, changeDirName, changeMetaPath, changePath, changesDir, classifyPath, clearActiveIf, completeChange, configPath, copyDir, copyDirSkipExisting, createChange, defaultConfig, defaultMcpConfig, defaultMcpYamlPath, defaultMemoryDir, defaultTemplatesDir, defaultWorkflowsDir, defaultsRoot, domainsDir, ensureDir, ensureMcpConfig, explainMatch, fallbackAdapter, fallbackIncompleteStageId, findFeaturesBacklogPath, firstActiveStageId, formatJsonSummary, formatMcpContextForHandoff, formatStatus, gatherMcpContext, gatherMcpContextForChange, generateCodeContext, getActiveChangeId, getIntegration, getStage, incompletePriorStageErrors, inferComplexity, initProject, installAgentIntegration, installAgentIntegrations, integrationKeyFor, interpolate, interpolateArgs, isIgnoredPath, isInitialized, isSecretPath, isStageSkipped, isSubstantiveArtifactContent, listBacklogFeatures, listChanges, listDirs, listFiles, listMatchingSourceIds, listMcpSourceTools, listWorkflowNames, loadChangeMeta, loadConfig, loadInstalledAgent, loadMcpConfig, loadWorkflow, matchMcpSources, mcpConfigPath, memoryDir, mergeCaps, nextStageId, nowIso, optionForTarget, parseAgentTargets, parseFeaturesBacklog, parseIntegration, pathExists, promoteGreenfieldToMemory, readText, readYaml, recommendWorkflow, refreshActiveAgentContext, removeMcpSource, removeOtherAgentHosts, removePath, renderThinAgent, resolveChangeId, resolveFocus, resolveStages, runLocalVerify, saveChangeMeta, saveConfig, saveMcpConfig, sddRoot, setActiveChange, shouldAutoSkip, skipStage, slugify, startFeatureFromBacklog, startGreenfield, switchWorkflow, templatesDir, todayIsoDate, tokenizeKeywords, typescriptAdapter, uniqueChangeDirName, updateFeatureStatus, withMcpSource, workflowsDir, writeAgentHandoff, writeRefineBrief, writeText, writeTextIfMissing, writeYaml };
2191
+ export { AGENT_INTEGRATIONS, AGENT_TARGET_OPTIONS, ALL_AGENT_TARGETS, type AdapterExtractResult, AdapterRegistry, type AdvanceResult, type AgentIntegration, type AgentTarget, type AgentTargetOption, type ArtifactDef, ArtifactDefSchema, type BuildRefinePlanOptions, type CallMcpToolResult, type ChangeContext, type ChangeMeta, ChangeMetaSchema, type CodeContextCaps, type CodeContextGap, type CodeContextGapCode, type CodeContextRequest, type CodeContextResult, type CodeContextSummary, type CodeSlice, type Complexity, type Config, ConfigSchema, type CreateChangeInput, DEFAULT_CODE_CONTEXT_CAPS, DEFAULT_INIT_INTEGRATION, type FocusPlan, type Gate, GateSchema, type GatherMcpOptions, type GenerateCodeContextDeps, type GreenfieldFeature, type InitOptions, type InitResult, type InstallAgentOptions, type InstallAgentResult, type InstallAgentsOptions, type InstallAgentsResult, type InstalledAgentSnapshot, type LanguageAdapter, type McpConfig, McpConfigSchema, type McpFetchResult, type McpInvoke, McpInvokeSchema, type McpMatchContext, type McpSource, McpSourceSchema, type McpWhen, McpWhenSchema, PROTOCOL_MD, type RefineArtifactRef, type RefineMode, type RefinePlan, SDD_AGENT_ROLES, SDD_CORE_VERSION, SDD_DIR, type SddAgentRole, type SddAgentRoleId, SddError, type Stage, StageSchema, type StartFeatureInput, type StartGreenfieldInput, type StartGreenfieldResult, type SymbolInfo, type SymbolKind, VerifyCommandSchema, type VerifyResult, type Workflow, WorkflowSchema, activePointerPath, activeStages, addMcpSource, advanceStage, agentHostPaths, agentKickoffMessage, approveGate, archiveDir, buildAgentPrompt, buildContext, buildRefinePlan, callMcpSourceTool, canLeaveStage, changeDirName, changeMetaPath, changePath, changesDir, classifyPath, clearActiveIf, completeChange, configPath, copyDir, copyDirSkipExisting, createChange, defaultConfig, defaultMcpConfig, defaultMcpYamlPath, defaultMemoryDir, defaultTemplatesDir, defaultWorkflowsDir, defaultsRoot, domainsDir, ensureDir, ensureMcpConfig, errorMessage, explainMatch, fallbackAdapter, fallbackIncompleteStageId, findFeaturesBacklogPath, firstActiveStageId, formatJsonSummary, formatMcpContextForHandoff, formatStatus, gatherMcpContext, gatherMcpContextForChange, generateCodeContext, getActiveChangeId, getIntegration, getStage, incompletePriorStageErrors, inferComplexity, initProject, installAgentIntegration, installAgentIntegrations, integrationKeyFor, interpolate, interpolateArgs, isIgnoredPath, isInitialized, isSddError, isSecretPath, isStageSkipped, isSubstantiveArtifactContent, listBacklogFeatures, listChanges, listDirs, listFiles, listMatchingSourceIds, listMcpSourceTools, listWorkflowNames, loadChangeMeta, loadConfig, loadInstalledAgent, loadMcpConfig, loadWorkflow, matchMcpSources, mcpConfigPath, mcpTimeoutMs, memoryDir, mergeCaps, nextStageId, nowIso, optionForTarget, parseAgentTargets, parseFeaturesBacklog, parseIntegration, pathExists, promoteGreenfieldToMemory, readText, readYaml, recommendWorkflow, refreshActiveAgentContext, removeMcpSource, removeOtherAgentHosts, removePath, renderThinAgent, resolveChangeId, resolveFocus, resolveStages, runLocalVerify, saveChangeMeta, saveConfig, saveMcpConfig, sddRoot, setActiveChange, shouldAutoSkip, skipStage, slugify, startFeatureFromBacklog, startGreenfield, switchWorkflow, templatesDir, toProcessEnv, todayIsoDate, tokenizeKeywords, typescriptAdapter, uniqueChangeDirName, updateFeatureStatus, withMcpSource, workflowsDir, writeAgentHandoff, writeRefineBrief, writeText, writeTextIfMissing, writeYaml };
package/dist/index.js CHANGED
@@ -16,8 +16,9 @@ import {
16
16
  startGreenfield,
17
17
  switchWorkflow,
18
18
  updateFeatureStatus
19
- } from "./chunk-BIY3WKP3.js";
19
+ } from "./chunk-TNZHQ6B5.js";
20
20
  import {
21
+ SDD_CORE_VERSION,
21
22
  callMcpSourceTool,
22
23
  explainMatch,
23
24
  formatMcpContextForHandoff,
@@ -26,8 +27,10 @@ import {
26
27
  listMatchingSourceIds,
27
28
  listMcpSourceTools,
28
29
  matchMcpSources,
30
+ mcpTimeoutMs,
31
+ toProcessEnv,
29
32
  withMcpSource
30
- } from "./chunk-VD3CPFAI.js";
33
+ } from "./chunk-2C3WHUAR.js";
31
34
  import {
32
35
  McpConfigSchema,
33
36
  McpInvokeSchema,
@@ -64,17 +67,17 @@ import {
64
67
  removeOtherAgentHosts,
65
68
  renderThinAgent,
66
69
  writeAgentHandoff
67
- } from "./chunk-AOIPJ4FM.js";
70
+ } from "./chunk-BTUI2WYQ.js";
68
71
  import {
69
72
  buildAgentPrompt,
70
73
  formatStatus
71
- } from "./chunk-M7YRPPNU.js";
74
+ } from "./chunk-BCLKMBKF.js";
72
75
  import {
73
76
  defaultConfig,
74
77
  isInitialized,
75
78
  loadConfig,
76
79
  saveConfig
77
- } from "./chunk-OJFMJHNL.js";
80
+ } from "./chunk-WHDG6OTG.js";
78
81
  import {
79
82
  activeStages,
80
83
  buildContext,
@@ -110,6 +113,11 @@ import {
110
113
  VerifyCommandSchema,
111
114
  WorkflowSchema
112
115
  } from "./chunk-67AUAGNV.js";
116
+ import {
117
+ SddError,
118
+ errorMessage,
119
+ isSddError
120
+ } from "./chunk-MHODNFIL.js";
113
121
  import {
114
122
  SDD_DIR,
115
123
  activePointerPath,
@@ -448,14 +456,11 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
448
456
  });
449
457
  if (evidenceDir) {
450
458
  const logPath = join3(evidenceDir, `${cmd.name.replace(/\s+/g, "-")}.log`);
451
- await writeText(
452
- logPath,
453
- `# ${cmd.name}
459
+ await writeText(logPath, `# ${cmd.name}
454
460
  $ ${cmd.run}
455
461
  exit: ${exitCode}
456
462
 
457
- ${output}`
458
- );
463
+ ${output}`);
459
464
  }
460
465
  }
461
466
  }
@@ -506,10 +511,7 @@ ${output}`
506
511
  }
507
512
  if (evidenceDir) {
508
513
  const stamp = nowIso();
509
- await writeText(
510
- join3(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`),
511
- summaryBody
512
- );
514
+ await writeText(join3(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`), summaryBody);
513
515
  }
514
516
  } else {
515
517
  await writeText(summaryPath, summaryBody);
@@ -539,8 +541,8 @@ import { extname as extname4, join as join6, normalize as normalize5 } from "pat
539
541
  import { extname as extname2 } from "pathe";
540
542
 
541
543
  // src/code-context/adapters/typescript.ts
542
- import ts from "typescript";
543
544
  import { extname } from "pathe";
545
+ import ts from "typescript";
544
546
  var TS_JS_EXTENSIONS = [
545
547
  ".ts",
546
548
  ".tsx",
@@ -733,127 +735,6 @@ var AdapterRegistry = class {
733
735
  }
734
736
  };
735
737
 
736
- // src/code-context/formatter.ts
737
- function fenceLang(filePath) {
738
- const lower = filePath.toLowerCase();
739
- if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) return "tsx";
740
- if (lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts")) {
741
- return "ts";
742
- }
743
- if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) {
744
- return "js";
745
- }
746
- return "";
747
- }
748
- function formatMarkdown(input) {
749
- const lines = [];
750
- lines.push("# Code context");
751
- lines.push("");
752
- lines.push(
753
- "> Generated on-demand for agents. Not a substitute for process context"
754
- );
755
- lines.push(
756
- "> (.sdd/protocol.md, active-context, change packs)."
757
- );
758
- lines.push(
759
- "> Regenerable \u2014 run `sdd context --out change` (or pass `--path` / `--symbol`)."
760
- );
761
- lines.push("");
762
- lines.push("## Summary");
763
- lines.push(
764
- `- Change: ${input.changeTitle ?? input.plan.changeId ?? "(none)"}`
765
- );
766
- lines.push(
767
- `- Focus paths: ${input.summary.focusPaths.length ? input.summary.focusPaths.join(", ") : "(none)"}`
768
- );
769
- lines.push(
770
- `- Focus symbols: ${input.summary.focusSymbols.length ? input.summary.focusSymbols.join(", ") : "(none)"}`
771
- );
772
- lines.push(
773
- `- Keywords: ${input.summary.keywords.length ? input.summary.keywords.join(", ") : "(none)"}`
774
- );
775
- lines.push(
776
- `- Files analyzed: ${input.summary.filesAnalyzed} \xB7 Symbols: ${input.summary.symbolsExtracted} \xB7 Slices: ${input.summary.slicesEmitted}`
777
- );
778
- lines.push(`- Truncated: ${input.summary.truncated ? "yes" : "no"}`);
779
- if (input.plan.notes.length) {
780
- lines.push(`- Notes: ${input.plan.notes.join("; ")}`);
781
- }
782
- lines.push("");
783
- lines.push("## Gaps");
784
- if (!input.gaps.length) {
785
- lines.push("_None._");
786
- } else {
787
- for (const g of input.gaps) {
788
- const pathPart = g.path ? ` \u2014 \`${g.path}\`` : "";
789
- lines.push(`- (\`${g.code}\`) ${g.message}${pathPart}`);
790
- }
791
- }
792
- lines.push("");
793
- lines.push("## Slices");
794
- lines.push("");
795
- let truncated = input.summary.truncated;
796
- const maxLines = input.maxOutputLines ?? 8e3;
797
- const maxTokens = input.maxTokensApprox ?? 12e3;
798
- let approxTokens = 0;
799
- for (const slice of input.slices) {
800
- if (lines.length >= maxLines) {
801
- truncated = true;
802
- break;
803
- }
804
- const header = `### \`${slice.filePath}\`${slice.symbolName ? ` \xB7 \`${slice.symbolName}\`` : ""} (lines ${slice.startLine}\u2013${slice.endLine}) \xB7 ${slice.reason}`;
805
- lines.push(header);
806
- lines.push("```" + fenceLang(slice.filePath));
807
- lines.push(slice.body);
808
- lines.push("```");
809
- lines.push("");
810
- approxTokens += Math.ceil((header.length + slice.body.length) / 4);
811
- if (approxTokens > maxTokens) {
812
- truncated = true;
813
- break;
814
- }
815
- }
816
- if (!input.slices.length) {
817
- lines.push("_No slices emitted._");
818
- lines.push("");
819
- }
820
- lines.push("## Structural notes");
821
- const topFiles = [
822
- ...new Set(input.slices.map((s) => s.filePath))
823
- ].slice(0, 15);
824
- if (topFiles.length) {
825
- lines.push(`- Ranked files (sample): ${topFiles.join(", ")}`);
826
- } else {
827
- lines.push("- No structural neighborhood (empty analysis set).");
828
- }
829
- lines.push("");
830
- let markdown = lines.join("\n");
831
- if (Math.ceil(markdown.length / 4) > maxTokens) {
832
- truncated = true;
833
- markdown = markdown.slice(0, maxTokens * 4) + "\n\n\u2026(truncated)\n";
834
- }
835
- return { markdown, truncated };
836
- }
837
- function formatJsonSummary(summary, gaps, slices, ok) {
838
- return JSON.stringify(
839
- {
840
- ok,
841
- summary,
842
- gaps,
843
- slices: slices.map((s) => ({
844
- id: s.id,
845
- filePath: s.filePath,
846
- startLine: s.startLine,
847
- endLine: s.endLine,
848
- symbolName: s.symbolName,
849
- reason: s.reason
850
- }))
851
- },
852
- null,
853
- 2
854
- );
855
- }
856
-
857
738
  // src/code-context/focus.ts
858
739
  import { promises as fs } from "fs";
859
740
  import { isAbsolute, join as join4, normalize as normalize3, relative } from "pathe";
@@ -1252,12 +1133,7 @@ async function resolveFocus(request, options) {
1252
1133
  }
1253
1134
  }
1254
1135
  if (!request.paths?.length && (keywords.length || seedSymbols.length)) {
1255
- const found = await searchByKeywords(
1256
- projectRoot,
1257
- keywords,
1258
- seedSymbols,
1259
- maxWalk
1260
- );
1136
+ const found = await searchByKeywords(projectRoot, keywords, seedSymbols, maxWalk);
1261
1137
  for (const f of found) {
1262
1138
  if (!candidatePaths.includes(f)) candidatePaths.push(f);
1263
1139
  if (!seedPaths.includes(f) && seedPaths.length < 20) seedPaths.push(f);
@@ -1267,12 +1143,7 @@ async function resolveFocus(request, options) {
1267
1143
  }
1268
1144
  if (!request.paths?.length && !change && (request.query || seedSymbols.length)) {
1269
1145
  notes.push("Focus from query/symbols only");
1270
- const found = await searchByKeywords(
1271
- projectRoot,
1272
- keywords,
1273
- seedSymbols,
1274
- maxWalk
1275
- );
1146
+ const found = await searchByKeywords(projectRoot, keywords, seedSymbols, maxWalk);
1276
1147
  for (const f of found) {
1277
1148
  if (!candidatePaths.includes(f)) candidatePaths.push(f);
1278
1149
  if (!seedPaths.includes(f)) seedPaths.push(f);
@@ -1311,6 +1182,117 @@ async function resolveFocus(request, options) {
1311
1182
  };
1312
1183
  }
1313
1184
 
1185
+ // src/code-context/formatter.ts
1186
+ function fenceLang(filePath) {
1187
+ const lower = filePath.toLowerCase();
1188
+ if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) return "tsx";
1189
+ if (lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts")) {
1190
+ return "ts";
1191
+ }
1192
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) {
1193
+ return "js";
1194
+ }
1195
+ return "";
1196
+ }
1197
+ function formatMarkdown(input) {
1198
+ const lines = [];
1199
+ lines.push("# Code context");
1200
+ lines.push("");
1201
+ lines.push("> Generated on-demand for agents. Not a substitute for process context");
1202
+ lines.push("> (.sdd/protocol.md, active-context, change packs).");
1203
+ lines.push("> Regenerable \u2014 run `sdd context --out change` (or pass `--path` / `--symbol`).");
1204
+ lines.push("");
1205
+ lines.push("## Summary");
1206
+ lines.push(`- Change: ${input.changeTitle ?? input.plan.changeId ?? "(none)"}`);
1207
+ lines.push(
1208
+ `- Focus paths: ${input.summary.focusPaths.length ? input.summary.focusPaths.join(", ") : "(none)"}`
1209
+ );
1210
+ lines.push(
1211
+ `- Focus symbols: ${input.summary.focusSymbols.length ? input.summary.focusSymbols.join(", ") : "(none)"}`
1212
+ );
1213
+ lines.push(
1214
+ `- Keywords: ${input.summary.keywords.length ? input.summary.keywords.join(", ") : "(none)"}`
1215
+ );
1216
+ lines.push(
1217
+ `- Files analyzed: ${input.summary.filesAnalyzed} \xB7 Symbols: ${input.summary.symbolsExtracted} \xB7 Slices: ${input.summary.slicesEmitted}`
1218
+ );
1219
+ lines.push(`- Truncated: ${input.summary.truncated ? "yes" : "no"}`);
1220
+ if (input.plan.notes.length) {
1221
+ lines.push(`- Notes: ${input.plan.notes.join("; ")}`);
1222
+ }
1223
+ lines.push("");
1224
+ lines.push("## Gaps");
1225
+ if (!input.gaps.length) {
1226
+ lines.push("_None._");
1227
+ } else {
1228
+ for (const g of input.gaps) {
1229
+ const pathPart = g.path ? ` \u2014 \`${g.path}\`` : "";
1230
+ lines.push(`- (\`${g.code}\`) ${g.message}${pathPart}`);
1231
+ }
1232
+ }
1233
+ lines.push("");
1234
+ lines.push("## Slices");
1235
+ lines.push("");
1236
+ let truncated = input.summary.truncated;
1237
+ const maxLines = input.maxOutputLines ?? 8e3;
1238
+ const maxTokens = input.maxTokensApprox ?? 12e3;
1239
+ let approxTokens = 0;
1240
+ for (const slice of input.slices) {
1241
+ if (lines.length >= maxLines) {
1242
+ truncated = true;
1243
+ break;
1244
+ }
1245
+ const header = `### \`${slice.filePath}\`${slice.symbolName ? ` \xB7 \`${slice.symbolName}\`` : ""} (lines ${slice.startLine}\u2013${slice.endLine}) \xB7 ${slice.reason}`;
1246
+ lines.push(header);
1247
+ lines.push("```" + fenceLang(slice.filePath));
1248
+ lines.push(slice.body);
1249
+ lines.push("```");
1250
+ lines.push("");
1251
+ approxTokens += Math.ceil((header.length + slice.body.length) / 4);
1252
+ if (approxTokens > maxTokens) {
1253
+ truncated = true;
1254
+ break;
1255
+ }
1256
+ }
1257
+ if (!input.slices.length) {
1258
+ lines.push("_No slices emitted._");
1259
+ lines.push("");
1260
+ }
1261
+ lines.push("## Structural notes");
1262
+ const topFiles = [...new Set(input.slices.map((s) => s.filePath))].slice(0, 15);
1263
+ if (topFiles.length) {
1264
+ lines.push(`- Ranked files (sample): ${topFiles.join(", ")}`);
1265
+ } else {
1266
+ lines.push("- No structural neighborhood (empty analysis set).");
1267
+ }
1268
+ lines.push("");
1269
+ let markdown = lines.join("\n");
1270
+ if (Math.ceil(markdown.length / 4) > maxTokens) {
1271
+ truncated = true;
1272
+ markdown = markdown.slice(0, maxTokens * 4) + "\n\n\u2026(truncated)\n";
1273
+ }
1274
+ return { markdown, truncated };
1275
+ }
1276
+ function formatJsonSummary(summary, gaps, slices, ok) {
1277
+ return JSON.stringify(
1278
+ {
1279
+ ok,
1280
+ summary,
1281
+ gaps,
1282
+ slices: slices.map((s) => ({
1283
+ id: s.id,
1284
+ filePath: s.filePath,
1285
+ startLine: s.startLine,
1286
+ endLine: s.endLine,
1287
+ symbolName: s.symbolName,
1288
+ reason: s.reason
1289
+ }))
1290
+ },
1291
+ null,
1292
+ 2
1293
+ );
1294
+ }
1295
+
1314
1296
  // src/code-context/graph.ts
1315
1297
  import { dirname as dirname2, join as join5, normalize as normalize4 } from "pathe";
1316
1298
  var GraphLite = class {
@@ -1419,9 +1401,7 @@ function scoreSymbol(symbol, plan, graph, caps) {
1419
1401
  score += 100;
1420
1402
  reasons.push("exact-symbol");
1421
1403
  } else {
1422
- const ci = seedSymbols.find(
1423
- (s) => s.toLowerCase() === symbol.name.toLowerCase()
1424
- );
1404
+ const ci = seedSymbols.find((s) => s.toLowerCase() === symbol.name.toLowerCase());
1425
1405
  if (ci) {
1426
1406
  score += 90;
1427
1407
  reasons.push("symbol-ci");
@@ -1540,12 +1520,11 @@ function emitSlices(ranked, graph, caps, options) {
1540
1520
  if (isSecretPath(sym.filePath)) continue;
1541
1521
  const source = graph.getSource(sym.filePath);
1542
1522
  if (!source) continue;
1543
- const { body, end, truncated: lineTrunc } = sliceBody(
1544
- source,
1545
- sym.startLine,
1546
- sym.endLine,
1547
- caps.maxLinesPerSlice
1548
- );
1523
+ const {
1524
+ body,
1525
+ end,
1526
+ truncated: lineTrunc
1527
+ } = sliceBody(source, sym.startLine, sym.endLine, caps.maxLinesPerSlice);
1549
1528
  if (lineTrunc) truncated = true;
1550
1529
  const id = `${sym.filePath}:${sym.startLine}-${end}:${sym.name}`;
1551
1530
  if (seen.has(id)) continue;
@@ -1569,12 +1548,7 @@ function emitSlices(ranked, graph, caps, options) {
1569
1548
  if (!nSource || !nNode) continue;
1570
1549
  const nSym = nNode.extract.symbols.find((s) => s.exported) ?? nNode.extract.symbols[0];
1571
1550
  if (!nSym) continue;
1572
- const nb = sliceBody(
1573
- nSource,
1574
- nSym.startLine,
1575
- nSym.endLine,
1576
- caps.maxLinesPerSlice
1577
- );
1551
+ const nb = sliceBody(nSource, nSym.startLine, nSym.endLine, caps.maxLinesPerSlice);
1578
1552
  const nid = `${nSym.filePath}:${nSym.startLine}-${nb.end}:${nSym.name}:neighbor`;
1579
1553
  if (seen.has(nid)) continue;
1580
1554
  seen.add(nid);
@@ -1665,9 +1639,7 @@ function hardFailure(gaps, message, summary) {
1665
1639
  `- Error: ${message}`,
1666
1640
  "",
1667
1641
  "## Gaps",
1668
- ...gaps.map(
1669
- (g) => `- (\`${g.code}\`) ${g.message}${g.path ? ` \u2014 \`${g.path}\`` : ""}`
1670
- ),
1642
+ ...gaps.map((g) => `- (\`${g.code}\`) ${g.message}${g.path ? ` \u2014 \`${g.path}\`` : ""}`),
1671
1643
  "",
1672
1644
  "## Slices",
1673
1645
  "",
@@ -1866,7 +1838,7 @@ async function generateCodeContext(request, deps) {
1866
1838
  plan.changeId,
1867
1839
  "meta.yaml"
1868
1840
  );
1869
- const { loadConfig: loadConfig2 } = await import("./config-ZYEEIRXJ.js");
1841
+ const { loadConfig: loadConfig2 } = await import("./config-X5VDBVR4.js");
1870
1842
  const { changePath: changePath2 } = await import("./paths-64GA32IZ.js");
1871
1843
  const config = await loadConfig2(projectRoot);
1872
1844
  const mp = join6(changePath2(projectRoot, config, plan.changeId), "meta.yaml");
@@ -1941,11 +1913,7 @@ function listMarkdown(refs) {
1941
1913
  ).join("\n");
1942
1914
  }
1943
1915
  async function buildRefinePlan(opts) {
1944
- const ctx = await buildContext(
1945
- opts.projectRoot,
1946
- opts.config,
1947
- opts.changeId
1948
- );
1916
+ const ctx = await buildContext(opts.projectRoot, opts.config, opts.changeId);
1949
1917
  const mode = opts.mode ?? "refine";
1950
1918
  const focusOnly = Boolean(opts.focusOnly);
1951
1919
  const focusStageId = opts.stageId?.trim() || ctx.meta.stage;
@@ -2139,7 +2107,9 @@ export {
2139
2107
  McpWhenSchema,
2140
2108
  PROTOCOL_MD,
2141
2109
  SDD_AGENT_ROLES,
2110
+ SDD_CORE_VERSION,
2142
2111
  SDD_DIR,
2112
+ SddError,
2143
2113
  StageSchema,
2144
2114
  VerifyCommandSchema,
2145
2115
  WorkflowSchema,
@@ -2177,6 +2147,7 @@ export {
2177
2147
  domainsDir,
2178
2148
  ensureDir,
2179
2149
  ensureMcpConfig,
2150
+ errorMessage,
2180
2151
  explainMatch,
2181
2152
  fallbackAdapter,
2182
2153
  fallbackIncompleteStageId,
@@ -2201,6 +2172,7 @@ export {
2201
2172
  interpolateArgs,
2202
2173
  isIgnoredPath,
2203
2174
  isInitialized,
2175
+ isSddError,
2204
2176
  isSecretPath,
2205
2177
  isStageSkipped,
2206
2178
  isSubstantiveArtifactContent,
@@ -2218,6 +2190,7 @@ export {
2218
2190
  loadWorkflow,
2219
2191
  matchMcpSources,
2220
2192
  mcpConfigPath,
2193
+ mcpTimeoutMs,
2221
2194
  memoryDir,
2222
2195
  mergeCaps,
2223
2196
  nextStageId,
@@ -2252,6 +2225,7 @@ export {
2252
2225
  startGreenfield,
2253
2226
  switchWorkflow,
2254
2227
  templatesDir,
2228
+ toProcessEnv,
2255
2229
  todayIsoDate,
2256
2230
  tokenizeKeywords,
2257
2231
  typescriptAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@structured-vibe-coding/core",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "Core engine for sdd — local Spec-Driven Development workflows, change packs, greenfield, and agent handoff",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,11 +57,11 @@
57
57
  "local-sdd"
58
58
  ],
59
59
  "scripts": {
60
- "build": "tsup src/index.ts --format esm --dts --clean",
61
- "dev": "tsup src/index.ts --format esm --dts --watch",
60
+ "build": "tsup",
61
+ "dev": "tsup --watch",
62
62
  "test": "vitest run",
63
63
  "test:watch": "vitest",
64
64
  "typecheck": "tsc --noEmit",
65
- "lint": "tsc --noEmit"
65
+ "lint": "cd ../.. && biome check packages/core/src"
66
66
  }
67
67
  }