@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 +23 -3
- package/dist/{agents-TJQMAD24.js → agents-3MD2GPYI.js} +4 -3
- package/dist/{chunk-VD3CPFAI.js → chunk-2C3WHUAR.js} +61 -16
- package/dist/{chunk-M7YRPPNU.js → chunk-BCLKMBKF.js} +15 -29
- package/dist/{chunk-AOIPJ4FM.js → chunk-BTUI2WYQ.js} +3 -10
- package/dist/chunk-MHODNFIL.js +26 -0
- package/dist/{chunk-BIY3WKP3.js → chunk-TNZHQ6B5.js} +20 -39
- package/dist/{chunk-OJFMJHNL.js → chunk-WHDG6OTG.js} +7 -3
- package/dist/{config-ZYEEIRXJ.js → config-X5VDBVR4.js} +2 -1
- package/dist/{gather-74DQ35QX.js → gather-2LQ464XU.js} +2 -1
- package/dist/{greenfield-PKVWSKHN.js → greenfield-YUAMU7F3.js} +2 -2
- package/dist/index.d.ts +30 -1
- package/dist/index.js +146 -172
- package/package.json +4 -4
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
|
|
16
|
-
- [
|
|
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-
|
|
23
|
-
import "./chunk-
|
|
24
|
-
import "./chunk-
|
|
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:
|
|
79
|
+
version: SDD_CORE_VERSION
|
|
31
80
|
});
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
4
|
+
} from "./chunk-BCLKMBKF.js";
|
|
5
5
|
import {
|
|
6
6
|
loadConfig
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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(
|
|
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
|
|
23
|
-
|
|
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
|
gatherMcpContext,
|
|
4
4
|
gatherMcpContextForChange,
|
|
5
5
|
listMatchingSourceIds
|
|
6
|
-
} from "./chunk-
|
|
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-
|
|
10
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
70
|
+
} from "./chunk-BTUI2WYQ.js";
|
|
68
71
|
import {
|
|
69
72
|
buildAgentPrompt,
|
|
70
73
|
formatStatus
|
|
71
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-BCLKMBKF.js";
|
|
72
75
|
import {
|
|
73
76
|
defaultConfig,
|
|
74
77
|
isInitialized,
|
|
75
78
|
loadConfig,
|
|
76
79
|
saveConfig
|
|
77
|
-
} from "./chunk-
|
|
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 {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
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-
|
|
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.
|
|
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
|
|
61
|
-
"dev": "tsup
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"dev": "tsup --watch",
|
|
62
62
|
"test": "vitest run",
|
|
63
63
|
"test:watch": "vitest",
|
|
64
64
|
"typecheck": "tsc --noEmit",
|
|
65
|
-
"lint": "
|
|
65
|
+
"lint": "cd ../.. && biome check packages/core/src"
|
|
66
66
|
}
|
|
67
67
|
}
|