@structured-vibe-coding/core 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agents-YN3Z3VOH.js → agents-EDCIINXG.js} +4 -3
- package/dist/chunk-47GWMUCR.js +153 -0
- package/dist/{chunk-HUSXETPS.js → chunk-QE6RMJUS.js} +224 -39
- package/dist/{chunk-EDXT3RTN.js → chunk-RXVTVLCJ.js} +3 -129
- package/dist/{chunk-O4OQWVC7.js → chunk-WCYVBZC4.js} +2 -2
- package/dist/{chunk-NY5FFWXG.js → chunk-XHNO536D.js} +62 -5
- package/dist/{gather-ZKVMJW5L.js → gather-ZK722X65.js} +2 -2
- package/dist/{greenfield-O2DI27HY.js → greenfield-E5CHDQPH.js} +4 -3
- package/dist/index.d.ts +82 -6
- package/dist/index.js +600 -124
- package/package.json +1 -1
- package/dist/chunk-MHODNFIL.js +0 -26
|
@@ -26,10 +26,11 @@ import {
|
|
|
26
26
|
renderProtocolMd,
|
|
27
27
|
renderThinAgent,
|
|
28
28
|
writeAgentHandoff
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import "./chunk-
|
|
29
|
+
} from "./chunk-WCYVBZC4.js";
|
|
30
|
+
import "./chunk-QE6RMJUS.js";
|
|
31
31
|
import "./chunk-TNWFMRIA.js";
|
|
32
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-47GWMUCR.js";
|
|
33
|
+
import "./chunk-4BRO7BOJ.js";
|
|
33
34
|
import "./chunk-O5SP6X5R.js";
|
|
34
35
|
export {
|
|
35
36
|
AGENT_INTEGRATIONS,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
interpolate
|
|
3
|
+
} from "./chunk-4BRO7BOJ.js";
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var SddError = class extends Error {
|
|
7
|
+
code;
|
|
8
|
+
hint;
|
|
9
|
+
exitCode;
|
|
10
|
+
constructor(message, options) {
|
|
11
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
12
|
+
this.name = "SddError";
|
|
13
|
+
this.code = options?.code ?? "SDD_ERROR";
|
|
14
|
+
this.hint = options?.hint;
|
|
15
|
+
this.exitCode = options?.exitCode ?? 1;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function isSddError(err) {
|
|
19
|
+
return err instanceof SddError;
|
|
20
|
+
}
|
|
21
|
+
function errorMessage(err) {
|
|
22
|
+
if (err instanceof Error) return err.message;
|
|
23
|
+
return String(err);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/env.ts
|
|
27
|
+
function toProcessEnv(base, extra) {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [k, v] of Object.entries(base)) {
|
|
30
|
+
if (v !== void 0) out[k] = String(v);
|
|
31
|
+
}
|
|
32
|
+
if (extra) {
|
|
33
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
34
|
+
if (v !== void 0) out[k] = String(v);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/version.ts
|
|
41
|
+
var SDD_CORE_VERSION = "0.22.0" ? "0.22.0" : "0.0.0-dev";
|
|
42
|
+
|
|
43
|
+
// src/mcp/client.ts
|
|
44
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
45
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
46
|
+
function mcpTimeoutMs() {
|
|
47
|
+
const raw = process.env.SDD_MCP_TIMEOUT_MS;
|
|
48
|
+
if (raw) {
|
|
49
|
+
const n = Number(raw);
|
|
50
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
51
|
+
}
|
|
52
|
+
return 3e4;
|
|
53
|
+
}
|
|
54
|
+
function contentToText(result) {
|
|
55
|
+
const parts = result.content ?? [];
|
|
56
|
+
return parts.map((p) => p.type === "text" ? p.text ?? "" : JSON.stringify(p)).join("\n").trim();
|
|
57
|
+
}
|
|
58
|
+
async function withTimeout(promise, ms, label) {
|
|
59
|
+
let timer;
|
|
60
|
+
try {
|
|
61
|
+
return await Promise.race([
|
|
62
|
+
promise,
|
|
63
|
+
new Promise((_, reject) => {
|
|
64
|
+
timer = setTimeout(() => {
|
|
65
|
+
reject(
|
|
66
|
+
new SddError(`MCP ${label} timed out after ${ms}ms`, {
|
|
67
|
+
code: "MCP_TIMEOUT",
|
|
68
|
+
hint: "Raise SDD_MCP_TIMEOUT_MS or fix the source server."
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}, ms);
|
|
72
|
+
})
|
|
73
|
+
]);
|
|
74
|
+
} finally {
|
|
75
|
+
if (timer) clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function withMcpSource(source, projectRoot, fn) {
|
|
79
|
+
const vars = { projectRoot };
|
|
80
|
+
const cwd = source.cwd ? interpolate(source.cwd, vars) : projectRoot;
|
|
81
|
+
const env = toProcessEnv(process.env, {
|
|
82
|
+
...source.env,
|
|
83
|
+
SDD_PROJECT_ROOT: projectRoot
|
|
84
|
+
});
|
|
85
|
+
const transport = new StdioClientTransport({
|
|
86
|
+
command: source.command,
|
|
87
|
+
args: source.args,
|
|
88
|
+
cwd,
|
|
89
|
+
env
|
|
90
|
+
});
|
|
91
|
+
const client = new Client({
|
|
92
|
+
name: "sdd-mcp-client",
|
|
93
|
+
version: SDD_CORE_VERSION
|
|
94
|
+
});
|
|
95
|
+
const ms = mcpTimeoutMs();
|
|
96
|
+
await withTimeout(client.connect(transport), ms, `connect (${source.id})`);
|
|
97
|
+
try {
|
|
98
|
+
return await withTimeout(fn(client), ms, `call (${source.id})`);
|
|
99
|
+
} finally {
|
|
100
|
+
try {
|
|
101
|
+
await client.close();
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function listMcpSourceTools(source, projectRoot) {
|
|
107
|
+
return withMcpSource(source, projectRoot, async (client) => {
|
|
108
|
+
const listed = await client.listTools();
|
|
109
|
+
return listed.tools.map((t) => ({
|
|
110
|
+
name: t.name,
|
|
111
|
+
description: t.description
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function callMcpSourceTool(source, projectRoot, tool, args) {
|
|
116
|
+
try {
|
|
117
|
+
if (source.tools?.length && !source.tools.includes(tool)) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
text: "",
|
|
121
|
+
error: `Tool "${tool}" not in allowlist for source ${source.id}: ${source.tools.join(", ")}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return await withMcpSource(source, projectRoot, async (client) => {
|
|
125
|
+
const result = await client.callTool({ name: tool, arguments: args });
|
|
126
|
+
const text = contentToText(result);
|
|
127
|
+
const isError = Boolean(result.isError);
|
|
128
|
+
return {
|
|
129
|
+
ok: !isError,
|
|
130
|
+
text: text || (isError ? "(empty error result)" : "(empty)"),
|
|
131
|
+
error: isError ? text || "tool returned isError" : void 0
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
text: "",
|
|
138
|
+
error: err instanceof Error ? err.message : String(err)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
SddError,
|
|
145
|
+
isSddError,
|
|
146
|
+
errorMessage,
|
|
147
|
+
toProcessEnv,
|
|
148
|
+
SDD_CORE_VERSION,
|
|
149
|
+
mcpTimeoutMs,
|
|
150
|
+
withMcpSource,
|
|
151
|
+
listMcpSourceTools,
|
|
152
|
+
callMcpSourceTool
|
|
153
|
+
};
|
|
@@ -6,8 +6,13 @@ import {
|
|
|
6
6
|
shouldAutoSkip
|
|
7
7
|
} from "./chunk-TNWFMRIA.js";
|
|
8
8
|
import {
|
|
9
|
-
SddError
|
|
10
|
-
|
|
9
|
+
SddError,
|
|
10
|
+
callMcpSourceTool,
|
|
11
|
+
mcpTimeoutMs
|
|
12
|
+
} from "./chunk-47GWMUCR.js";
|
|
13
|
+
import {
|
|
14
|
+
loadMcpConfig
|
|
15
|
+
} from "./chunk-4BRO7BOJ.js";
|
|
11
16
|
import {
|
|
12
17
|
activePointerPath,
|
|
13
18
|
changesPathRel,
|
|
@@ -196,7 +201,9 @@ var SearchConfigSchema = z.object({
|
|
|
196
201
|
k: z.number().int().min(1).max(100).default(20),
|
|
197
202
|
embeddings: z.boolean().default(false),
|
|
198
203
|
budgets: BudgetSchema.default({ default: 12e3 }),
|
|
199
|
-
exclude: z.array(z.string().min(1)).default([])
|
|
204
|
+
exclude: z.array(z.string().min(1)).default([]),
|
|
205
|
+
mcp_source: z.string().min(1).optional(),
|
|
206
|
+
mcp_tool: z.string().min(1).optional()
|
|
200
207
|
});
|
|
201
208
|
function budgetForStage(config, stage) {
|
|
202
209
|
if (!stage) return config.budgets.default;
|
|
@@ -221,22 +228,13 @@ async function loadSearchConfig(projectRoot) {
|
|
|
221
228
|
|
|
222
229
|
// src/search/pack.ts
|
|
223
230
|
var EN_DASH = "\u2013";
|
|
224
|
-
function searchPreamble(query, scope, budget, hybrid = false) {
|
|
225
|
-
|
|
226
|
-
return `# Search
|
|
227
|
-
Query: ${query}
|
|
228
|
-
Scope: ${scope}
|
|
229
|
-
Provider: builtin
|
|
230
|
-
Mode: hybrid
|
|
231
|
-
Budget: ${budget}
|
|
232
|
-
|
|
233
|
-
`;
|
|
234
|
-
}
|
|
231
|
+
function searchPreamble(query, scope, budget, hybrid = false, provider = "builtin") {
|
|
232
|
+
const mode = hybrid ? "Mode: hybrid\n" : "";
|
|
235
233
|
return `# Search
|
|
236
234
|
Query: ${query}
|
|
237
235
|
Scope: ${scope}
|
|
238
|
-
Provider:
|
|
239
|
-
Budget: ${budget}
|
|
236
|
+
Provider: ${provider}
|
|
237
|
+
${mode}Budget: ${budget}
|
|
240
238
|
|
|
241
239
|
`;
|
|
242
240
|
}
|
|
@@ -2331,6 +2329,173 @@ async function searchProject(projectRoot, query, options, runtime) {
|
|
|
2331
2329
|
};
|
|
2332
2330
|
}
|
|
2333
2331
|
|
|
2332
|
+
// src/search/mcp-provider.ts
|
|
2333
|
+
var McpSearchError = class extends Error {
|
|
2334
|
+
constructor() {
|
|
2335
|
+
super("Search provider is unavailable.");
|
|
2336
|
+
this.name = "McpSearchError";
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
function emptyStats(considered = 0, reindexed = 0) {
|
|
2340
|
+
return {
|
|
2341
|
+
provider: "mcp",
|
|
2342
|
+
files: 0,
|
|
2343
|
+
chunks: 0,
|
|
2344
|
+
lastIndexFilesConsidered: considered,
|
|
2345
|
+
lastIndexFilesReindexed: reindexed,
|
|
2346
|
+
lastIndexChunksReindexed: 0,
|
|
2347
|
+
lastIndexChunksEmbedded: 0,
|
|
2348
|
+
indexedCommit: null
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function isSafeRelativePath(path) {
|
|
2352
|
+
if (!path || path.startsWith("/") || path.startsWith("\\")) return false;
|
|
2353
|
+
if (path.length >= 2 && path.charCodeAt(1) === 58) {
|
|
2354
|
+
const c = path.charCodeAt(0);
|
|
2355
|
+
if (c >= 65 && c <= 90 || c >= 97 && c <= 122) return false;
|
|
2356
|
+
}
|
|
2357
|
+
const parts = path.replaceAll("\\", "/").split("/");
|
|
2358
|
+
for (const part of parts) {
|
|
2359
|
+
if (part.length === 0 || part === "." || part === "..") return false;
|
|
2360
|
+
}
|
|
2361
|
+
return true;
|
|
2362
|
+
}
|
|
2363
|
+
function withTimeout(promise, ms) {
|
|
2364
|
+
return new Promise((resolve, reject) => {
|
|
2365
|
+
const timer = setTimeout(() => reject(new McpSearchError()), ms);
|
|
2366
|
+
promise.then(
|
|
2367
|
+
(value) => {
|
|
2368
|
+
clearTimeout(timer);
|
|
2369
|
+
resolve(value);
|
|
2370
|
+
},
|
|
2371
|
+
() => {
|
|
2372
|
+
clearTimeout(timer);
|
|
2373
|
+
reject(new McpSearchError());
|
|
2374
|
+
}
|
|
2375
|
+
);
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
function allowedPaths(paths, exclude) {
|
|
2379
|
+
const kept = [];
|
|
2380
|
+
for (const raw of paths) {
|
|
2381
|
+
const path = raw.replaceAll("\\", "/");
|
|
2382
|
+
if (!isSafeRelativePath(path) || exclude(path)) continue;
|
|
2383
|
+
kept.push(path);
|
|
2384
|
+
}
|
|
2385
|
+
return kept;
|
|
2386
|
+
}
|
|
2387
|
+
function positive(value) {
|
|
2388
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) return void 0;
|
|
2389
|
+
return value;
|
|
2390
|
+
}
|
|
2391
|
+
function firstLine(body) {
|
|
2392
|
+
const nl = body.indexOf("\n");
|
|
2393
|
+
return nl === -1 ? body : body.slice(0, nl);
|
|
2394
|
+
}
|
|
2395
|
+
function asHit(value, scope, exclude) {
|
|
2396
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2397
|
+
const rec = value;
|
|
2398
|
+
if (typeof rec.path !== "string") return void 0;
|
|
2399
|
+
const path = rec.path.replaceAll("\\", "/");
|
|
2400
|
+
if (!isSafeRelativePath(path) || exclude(path)) return void 0;
|
|
2401
|
+
const startLine2 = positive(rec.startLine);
|
|
2402
|
+
const endLine2 = positive(rec.endLine);
|
|
2403
|
+
if (startLine2 === void 0 || endLine2 === void 0 || endLine2 < startLine2) return void 0;
|
|
2404
|
+
if (typeof rec.symbol !== "string") return void 0;
|
|
2405
|
+
const symbol = rec.symbol.trim();
|
|
2406
|
+
if (!symbol || typeof rec.body !== "string") return void 0;
|
|
2407
|
+
const signature = typeof rec.signature === "string" ? rec.signature : firstLine(rec.body);
|
|
2408
|
+
const language = typeof rec.language === "string" ? rec.language : "";
|
|
2409
|
+
const score = typeof rec.score === "number" && Number.isFinite(rec.score) ? rec.score : 0;
|
|
2410
|
+
return { scope, path, startLine: startLine2, endLine: endLine2, symbol, score, signature, body: rec.body, language };
|
|
2411
|
+
}
|
|
2412
|
+
function parseHits(text, scope, exclude) {
|
|
2413
|
+
let parsed;
|
|
2414
|
+
try {
|
|
2415
|
+
parsed = JSON.parse(text);
|
|
2416
|
+
} catch {
|
|
2417
|
+
throw new McpSearchError();
|
|
2418
|
+
}
|
|
2419
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new McpSearchError();
|
|
2420
|
+
const hits = parsed.hits;
|
|
2421
|
+
if (!Array.isArray(hits)) throw new McpSearchError();
|
|
2422
|
+
const out = [];
|
|
2423
|
+
for (const hit of hits) {
|
|
2424
|
+
const row = asHit(hit, scope, exclude);
|
|
2425
|
+
if (row) out.push(row);
|
|
2426
|
+
}
|
|
2427
|
+
return out;
|
|
2428
|
+
}
|
|
2429
|
+
async function connect(input) {
|
|
2430
|
+
const exclude = await loadExcluder(input.projectRoot, input.search.exclude);
|
|
2431
|
+
let sources = [];
|
|
2432
|
+
try {
|
|
2433
|
+
const cfg = await loadMcpConfig(input.projectRoot);
|
|
2434
|
+
sources = cfg.sources.filter((source2) => source2.enabled);
|
|
2435
|
+
} catch {
|
|
2436
|
+
throw new McpSearchError();
|
|
2437
|
+
}
|
|
2438
|
+
const named = input.search.mcp_source;
|
|
2439
|
+
const source = named ? sources.find((item) => item.id === named) : sources.length === 1 ? sources[0] : void 0;
|
|
2440
|
+
if (!source) throw new McpSearchError();
|
|
2441
|
+
const tool = input.search.mcp_tool || source.invoke?.tool;
|
|
2442
|
+
if (!tool) throw new McpSearchError();
|
|
2443
|
+
if (source.tools?.length && !source.tools.includes(tool)) throw new McpSearchError();
|
|
2444
|
+
const call = input.call ?? (async (req) => {
|
|
2445
|
+
const result = await callMcpSourceTool(source, input.projectRoot, req.tool, req.args);
|
|
2446
|
+
return { ok: result.ok, text: result.text };
|
|
2447
|
+
});
|
|
2448
|
+
return { sourceId: source.id, tool, exclude, call };
|
|
2449
|
+
}
|
|
2450
|
+
function openMcpSearchProvider(input) {
|
|
2451
|
+
const timeoutMs = input.timeoutMs ?? mcpTimeoutMs();
|
|
2452
|
+
let ready3;
|
|
2453
|
+
async function load() {
|
|
2454
|
+
if (ready3) return ready3;
|
|
2455
|
+
ready3 = await connect(input);
|
|
2456
|
+
return ready3;
|
|
2457
|
+
}
|
|
2458
|
+
return {
|
|
2459
|
+
id: "mcp",
|
|
2460
|
+
async index(changed) {
|
|
2461
|
+
const session = await load();
|
|
2462
|
+
if (!changed) return emptyStats();
|
|
2463
|
+
const paths = allowedPaths(changed, session.exclude);
|
|
2464
|
+
if (paths.length === 0) return emptyStats(changed.length);
|
|
2465
|
+
const result = await withTimeout(
|
|
2466
|
+
session.call({ sourceId: session.sourceId, tool: session.tool, args: { paths } }),
|
|
2467
|
+
timeoutMs
|
|
2468
|
+
);
|
|
2469
|
+
if (!result.ok) throw new McpSearchError();
|
|
2470
|
+
return emptyStats(changed.length, paths.length);
|
|
2471
|
+
},
|
|
2472
|
+
async search(query, options) {
|
|
2473
|
+
const session = await load();
|
|
2474
|
+
const args = { query, scope: options.scope };
|
|
2475
|
+
const result = await withTimeout(
|
|
2476
|
+
session.call({ sourceId: session.sourceId, tool: session.tool, args }),
|
|
2477
|
+
timeoutMs
|
|
2478
|
+
);
|
|
2479
|
+
if (!result.ok) throw new McpSearchError();
|
|
2480
|
+
const hits = parseHits(result.text, options.scope, session.exclude);
|
|
2481
|
+
const preamble = searchPreamble(query, options.scope, options.budget, false, "mcp");
|
|
2482
|
+
const packed = packHits({ preamble, hits, budget: options.budget });
|
|
2483
|
+
return {
|
|
2484
|
+
provider: "mcp",
|
|
2485
|
+
query,
|
|
2486
|
+
scope: options.scope,
|
|
2487
|
+
budget: options.budget,
|
|
2488
|
+
usedTokens: packed.usedTokens,
|
|
2489
|
+
hits: packed.hits,
|
|
2490
|
+
text: packed.text
|
|
2491
|
+
};
|
|
2492
|
+
},
|
|
2493
|
+
stats() {
|
|
2494
|
+
return Promise.resolve(emptyStats());
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2334
2499
|
// src/search/registry.ts
|
|
2335
2500
|
var extra = /* @__PURE__ */ new Map();
|
|
2336
2501
|
function registerSearchProvider(provider) {
|
|
@@ -2347,13 +2512,18 @@ function joinWarning(left, right) {
|
|
|
2347
2512
|
${right}`;
|
|
2348
2513
|
return left || right;
|
|
2349
2514
|
}
|
|
2350
|
-
function resolveSearchProvider(projectRoot, config, search, runtime) {
|
|
2515
|
+
function resolveSearchProvider(projectRoot, config, search, runtime, run) {
|
|
2351
2516
|
const builtin = createBuiltinProvider(projectRoot, config, runtime);
|
|
2352
2517
|
const name = search.provider || "builtin";
|
|
2353
2518
|
if (name === "builtin") return builtin;
|
|
2354
2519
|
const registered = extra.get(name);
|
|
2355
|
-
let active =
|
|
2356
|
-
|
|
2520
|
+
let active = name === "mcp" ? openMcpSearchProvider({
|
|
2521
|
+
projectRoot,
|
|
2522
|
+
search,
|
|
2523
|
+
call: run?.mcpCall,
|
|
2524
|
+
timeoutMs: run?.mcpTimeoutMs
|
|
2525
|
+
}) : registered ?? builtin;
|
|
2526
|
+
let warning = name === "mcp" || registered ? void 0 : fallbackWarning(name);
|
|
2357
2527
|
const mark = () => {
|
|
2358
2528
|
warning = fallbackWarning(name);
|
|
2359
2529
|
active = builtin;
|
|
@@ -2373,7 +2543,6 @@ function resolveSearchProvider(projectRoot, config, search, runtime) {
|
|
|
2373
2543
|
try {
|
|
2374
2544
|
const result = await active.search(query, options);
|
|
2375
2545
|
result.warning = joinWarning(warning, result.warning);
|
|
2376
|
-
result.provider = "builtin";
|
|
2377
2546
|
return result;
|
|
2378
2547
|
} catch (err) {
|
|
2379
2548
|
if (active.id === "builtin") throw err;
|
|
@@ -2404,12 +2573,11 @@ async function buildRetrievedInner(projectRoot, config, title, stage, budget, ru
|
|
|
2404
2573
|
return `Retrieved context skipped: ${safeMessage(err, projectRoot)}.`;
|
|
2405
2574
|
}
|
|
2406
2575
|
const limit = budget ?? budgetForStage(search, stage);
|
|
2407
|
-
const provider =
|
|
2576
|
+
const provider = createBuiltinProvider(projectRoot, config, runtime);
|
|
2408
2577
|
try {
|
|
2409
2578
|
await provider.index();
|
|
2410
2579
|
} catch (err) {
|
|
2411
|
-
|
|
2412
|
-
return `Retrieved context skipped: ${message}.`;
|
|
2580
|
+
return `Retrieved context skipped: ${safeMessage(err, projectRoot)}.`;
|
|
2413
2581
|
}
|
|
2414
2582
|
const request = {
|
|
2415
2583
|
projectRoot,
|
|
@@ -2456,13 +2624,12 @@ async function buildRetrievedInner(projectRoot, config, title, stage, budget, ru
|
|
|
2456
2624
|
}
|
|
2457
2625
|
return packed.text;
|
|
2458
2626
|
} catch (err) {
|
|
2459
|
-
|
|
2460
|
-
return `Retrieved context skipped: ${message}.`;
|
|
2627
|
+
return `Retrieved context skipped: ${safeMessage(err, projectRoot)}.`;
|
|
2461
2628
|
}
|
|
2462
2629
|
}
|
|
2463
|
-
async function executeSearch(projectRoot, config, query, scope, budget) {
|
|
2630
|
+
async function executeSearch(projectRoot, config, query, scope, budget, run) {
|
|
2464
2631
|
const search = await loadSearchConfig(projectRoot);
|
|
2465
|
-
const provider = resolveSearchProvider(projectRoot, config, search);
|
|
2632
|
+
const provider = resolveSearchProvider(projectRoot, config, search, void 0, run);
|
|
2466
2633
|
await provider.index();
|
|
2467
2634
|
const options = {
|
|
2468
2635
|
scope,
|
|
@@ -2668,7 +2835,7 @@ function stringList(value) {
|
|
|
2668
2835
|
}
|
|
2669
2836
|
return out;
|
|
2670
2837
|
}
|
|
2671
|
-
function
|
|
2838
|
+
function splitFrontmatter(raw) {
|
|
2672
2839
|
const text = raw.replace(/^\uFEFF/, "");
|
|
2673
2840
|
const nl = text.indexOf("\n");
|
|
2674
2841
|
if (nl < 0 || text.slice(0, nl).trim() !== "---") return null;
|
|
@@ -2682,14 +2849,22 @@ function parseRoleDocument(name, raw) {
|
|
|
2682
2849
|
return null;
|
|
2683
2850
|
}
|
|
2684
2851
|
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
2685
|
-
|
|
2852
|
+
return {
|
|
2853
|
+
data,
|
|
2854
|
+
body: rest.slice(close.index + close[0].length).trim()
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
function parseRoleDocument(name, raw) {
|
|
2858
|
+
const split = splitFrontmatter(raw);
|
|
2859
|
+
if (!split) return null;
|
|
2860
|
+
const rec = split.data;
|
|
2686
2861
|
const lists = emptyRoleLists();
|
|
2687
2862
|
for (const [field2, key] of LIST_FIELDS) {
|
|
2688
2863
|
const parsed = stringList(rec[key]);
|
|
2689
2864
|
if (!parsed) return null;
|
|
2690
2865
|
lists[field2] = parsed;
|
|
2691
2866
|
}
|
|
2692
|
-
const body =
|
|
2867
|
+
const body = split.body;
|
|
2693
2868
|
return {
|
|
2694
2869
|
name,
|
|
2695
2870
|
label: asText(rec.label),
|
|
@@ -2701,7 +2876,7 @@ function parseRoleDocument(name, raw) {
|
|
|
2701
2876
|
...lists
|
|
2702
2877
|
};
|
|
2703
2878
|
}
|
|
2704
|
-
function renderRole(role) {
|
|
2879
|
+
function renderRole(role, routines) {
|
|
2705
2880
|
const record = {
|
|
2706
2881
|
name: role.name,
|
|
2707
2882
|
label: role.label,
|
|
@@ -2710,6 +2885,7 @@ function renderRole(role) {
|
|
|
2710
2885
|
pinned: role.pinned,
|
|
2711
2886
|
hidden: role.hidden
|
|
2712
2887
|
};
|
|
2888
|
+
if (routines?.keep) record.routines = routines.value;
|
|
2713
2889
|
for (const [field2, key] of LIST_FIELDS) {
|
|
2714
2890
|
if (role[field2].length > 0) record[key] = role[field2];
|
|
2715
2891
|
}
|
|
@@ -2748,8 +2924,15 @@ async function readRole(root, name) {
|
|
|
2748
2924
|
return null;
|
|
2749
2925
|
}
|
|
2750
2926
|
}
|
|
2751
|
-
async function
|
|
2752
|
-
|
|
2927
|
+
async function keptRoutines(root, name) {
|
|
2928
|
+
const path = roleFilePath(root, name);
|
|
2929
|
+
if (!await pathExists(path)) return void 0;
|
|
2930
|
+
const split = splitFrontmatter(await readText(path));
|
|
2931
|
+
if (!split || !Object.hasOwn(split.data, "routines")) return void 0;
|
|
2932
|
+
return { keep: true, value: split.data.routines };
|
|
2933
|
+
}
|
|
2934
|
+
async function writeRole(root, role, routines) {
|
|
2935
|
+
await writeText(roleFilePath(root, role.name), renderRole(role, routines));
|
|
2753
2936
|
}
|
|
2754
2937
|
async function createRole(root, input) {
|
|
2755
2938
|
const name = canonicalRoleName(input.name);
|
|
@@ -2784,7 +2967,7 @@ async function editRole(root, nameInput, patch) {
|
|
|
2784
2967
|
if (patch.description !== void 0) role.description = patch.description.trim();
|
|
2785
2968
|
if (patch.askFirst !== void 0) role.askFirst = patch.askFirst;
|
|
2786
2969
|
if (patch.stages !== void 0) role.stages = patch.stages;
|
|
2787
|
-
await writeRole(root, role);
|
|
2970
|
+
await writeRole(root, role, await keptRoutines(root, name));
|
|
2788
2971
|
return ok();
|
|
2789
2972
|
}
|
|
2790
2973
|
async function setFlag(root, nameInput, flag) {
|
|
@@ -2793,7 +2976,7 @@ async function setFlag(root, nameInput, flag) {
|
|
|
2793
2976
|
const role = await readRole(root, name);
|
|
2794
2977
|
if (!role) return fail(ROLE_TEXT.notFound);
|
|
2795
2978
|
role[flag] = true;
|
|
2796
|
-
await writeRole(root, role);
|
|
2979
|
+
await writeRole(root, role, await keptRoutines(root, name));
|
|
2797
2980
|
return ok();
|
|
2798
2981
|
}
|
|
2799
2982
|
function pinRole(root, name) {
|
|
@@ -2836,7 +3019,7 @@ async function duplicateRole(root, nameInput) {
|
|
|
2836
3019
|
const copyName = canonicalRoleName(`${name} copy`);
|
|
2837
3020
|
if (!copyName) return fail(ROLE_TEXT.badName);
|
|
2838
3021
|
if (await pathExists(roleFilePath(root, copyName))) return fail(ROLE_TEXT.exists);
|
|
2839
|
-
await writeRole(root, { ...source, name: copyName });
|
|
3022
|
+
await writeRole(root, { ...source, name: copyName }, await keptRoutines(root, name));
|
|
2840
3023
|
return ok();
|
|
2841
3024
|
}
|
|
2842
3025
|
async function deleteRole(root, nameInput) {
|
|
@@ -3321,9 +3504,9 @@ async function buildAgentPrompt(ctx, config, projectRoot, budget, runtime) {
|
|
|
3321
3504
|
parts.push("");
|
|
3322
3505
|
}
|
|
3323
3506
|
try {
|
|
3324
|
-
const { loadMcpConfig } = await import("./sources-3TXUPU6V.js");
|
|
3325
|
-
const { gatherMcpContextForChange, formatMcpContextForHandoff } = await import("./gather-
|
|
3326
|
-
const mcpCfg = await
|
|
3507
|
+
const { loadMcpConfig: loadMcpConfig2 } = await import("./sources-3TXUPU6V.js");
|
|
3508
|
+
const { gatherMcpContextForChange, formatMcpContextForHandoff } = await import("./gather-ZK722X65.js");
|
|
3509
|
+
const mcpCfg = await loadMcpConfig2(projectRoot);
|
|
3327
3510
|
if (mcpCfg.auto_fetch_on_handoff && mcpCfg.sources.length) {
|
|
3328
3511
|
const fetched = await gatherMcpContextForChange(projectRoot, ctx);
|
|
3329
3512
|
const block = formatMcpContextForHandoff(fetched.filter((r) => r.ok || r.error));
|
|
@@ -3414,6 +3597,7 @@ export {
|
|
|
3414
3597
|
listScopeHits,
|
|
3415
3598
|
rankScope,
|
|
3416
3599
|
searchProject,
|
|
3600
|
+
openMcpSearchProvider,
|
|
3417
3601
|
registerSearchProvider,
|
|
3418
3602
|
clearRegisteredSearchProviders,
|
|
3419
3603
|
fallbackWarning,
|
|
@@ -3429,6 +3613,7 @@ export {
|
|
|
3429
3613
|
conversationDir,
|
|
3430
3614
|
memoryDir,
|
|
3431
3615
|
allowFilePath,
|
|
3616
|
+
splitFrontmatter,
|
|
3432
3617
|
parseRoleDocument,
|
|
3433
3618
|
rolesListingStage,
|
|
3434
3619
|
readRole,
|