@oisincoveney/pipeline 2.8.0 → 2.8.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.
Files changed (38) hide show
  1. package/dist/argo-submit.d.ts +2 -4
  2. package/dist/argo-submit.js +80 -80
  3. package/dist/cluster-doctor.js +89 -101
  4. package/dist/config/defaults.js +9 -19
  5. package/dist/config/load.js +32 -39
  6. package/dist/config/schemas.d.ts +1 -1
  7. package/dist/gates.js +6 -225
  8. package/dist/mcp/gateway-error.js +15 -0
  9. package/dist/mcp/gateway.js +119 -220
  10. package/dist/moka-global-config.js +20 -20
  11. package/dist/moka-submit.d.ts +1 -1
  12. package/dist/pipeline-runtime.js +580 -371
  13. package/dist/run-state/git-refs.js +124 -94
  14. package/dist/runner-command-contract.d.ts +2 -2
  15. package/dist/runner-event-sink.js +37 -69
  16. package/dist/runtime/agent-node/agent-node.js +214 -173
  17. package/dist/runtime/builtins/builtins.js +344 -57
  18. package/dist/runtime/changed-files/changed-files.js +15 -27
  19. package/dist/runtime/changed-files/index.js +2 -0
  20. package/dist/runtime/drain-merge/drain-merge.js +124 -82
  21. package/dist/runtime/gates/gates.js +46 -28
  22. package/dist/runtime/hooks/hooks.js +74 -29
  23. package/dist/runtime/opencode-server.js +27 -21
  24. package/dist/runtime/opencode-session-executor.js +101 -44
  25. package/dist/runtime/parallel-node/parallel-node.js +24 -5
  26. package/dist/runtime/select-candidate/select-candidate.js +45 -29
  27. package/dist/runtime/services/agent-node-runtime-service.js +15 -0
  28. package/dist/runtime/services/command-executor-service.js +8 -0
  29. package/dist/runtime/services/config-io-service.js +42 -0
  30. package/dist/runtime/services/drain-merge-git-service.js +10 -0
  31. package/dist/runtime/services/git-porcelain-service.js +38 -0
  32. package/dist/runtime/services/kubernetes-argo-service.d.ts +13 -0
  33. package/dist/runtime/services/kubernetes-argo-service.js +81 -0
  34. package/dist/runtime/services/mcp-gateway-service.js +184 -0
  35. package/dist/runtime/services/opencode-sdk-service.js +27 -0
  36. package/dist/runtime/services/runner-event-sink-http-service.js +80 -0
  37. package/dist/runtime/services/select-candidate-service.js +13 -0
  38. package/package.json +1 -1
@@ -6,80 +6,89 @@ import { normalizeJsonSource, readJsonSchemaSource, validateJsonSchemaSource } f
6
6
  import "../json-validation/index.js";
7
7
  import { emit, emitAgentFinish, emitAgentStart } from "../events/events.js";
8
8
  import "../events/index.js";
9
- import { estimateTokens } from "../../token-estimator.js";
10
- import { buildRepoMapContext } from "../../context/repo-map.js";
11
9
  import { gatewayServerForProfile } from "../../mcp/gateway.js";
12
10
  import { selectNodeModel } from "../../model-resolver.js";
11
+ import { estimateTokens } from "../../token-estimator.js";
13
12
  import { handoffFinalizerPrompt, parseHandoff, renderHandoff, synthesizeMinimalHandoff } from "../handoff.js";
14
- import { readFileSync } from "node:fs";
13
+ import { AgentNodeRuntimeService, AgentNodeRuntimeServiceLive } from "../services/agent-node-runtime-service.js";
14
+ import { Effect } from "effect";
15
15
  //#region src/runtime/agent-node/agent-node.ts
16
- async function executeAgentNode(node, context, attempt) {
17
- if (!node.profile) return {
18
- evidence: [`node '${node.id}' has no profile`],
19
- exitCode: 1,
20
- output: ""
21
- };
22
- const prompt = await renderAgentPrompt(node, context);
23
- const decision = decideNodeModel(prompt, node, context.config.token_budget);
24
- if (decision.overBudget) return {
25
- evidence: [
26
- `agent boundary node=${node.id} profile=${node.profile}`,
27
- `over token budget: ${decision.selection.reason}`,
28
- ...decision.selection.skipped.length ? [`model fallbacks skipped: ${decision.selection.skipped.join(", ")}`] : []
29
- ],
30
- exitCode: 1,
31
- output: ""
32
- };
33
- const modelSelection = decision.selection;
34
- const plan = createRunnerLaunchPlan(context.config, {
35
- model: modelSelection.model,
36
- nodeId: node.id,
37
- profileId: node.profile,
38
- prompt,
39
- worktreePath: context.worktreePath
40
- });
41
- if (node.timeoutMs) plan.timeoutMs = node.timeoutMs;
42
- context.agentInvocations.push(plan);
43
- emitAgentStart(context, plan, attempt);
44
- const result = await context.executor(plan, {
45
- onOutput: (event) => {
46
- if (event.stream !== "stdout") return;
47
- emit(context, {
48
- attempt,
49
- format: "text",
50
- nodeId: node.id,
51
- output: event.chunk,
52
- ...node.profile ? { profile: node.profile } : {},
53
- type: "node.output.recorded"
54
- });
55
- },
56
- signal: context.signal
57
- });
58
- emitAgentFinish(context, plan, attempt, result);
59
- if (result.sessionId) context.nodeStateStore.recordSessionId(node.id, result.sessionId);
60
- const finalized = await finalizeAgentOutput({
61
- context,
62
- node,
63
- normalized: normalizeAgentOutput(plan, result.stdout),
64
- plan,
65
- result,
66
- attempt
16
+ function executeAgentNode(node, context, attempt) {
17
+ const program = executeAgentNodeEffect(node, context, attempt);
18
+ return Effect.runPromise(Effect.provide(program, AgentNodeRuntimeServiceLive));
19
+ }
20
+ function executeAgentNodeEffect(node, context, attempt) {
21
+ return Effect.gen(function* () {
22
+ if (!node.profile) return {
23
+ evidence: [`node '${node.id}' has no profile`],
24
+ exitCode: 1,
25
+ output: ""
26
+ };
27
+ const prompt = yield* renderAgentPromptEffect(node, context);
28
+ const decision = decideNodeModel(prompt, node, context.config.token_budget);
29
+ if (decision.overBudget) return {
30
+ evidence: [
31
+ `agent boundary node=${node.id} profile=${node.profile}`,
32
+ `over token budget: ${decision.selection.reason}`,
33
+ ...decision.selection.skipped.length ? [`model fallbacks skipped: ${decision.selection.skipped.join(", ")}`] : []
34
+ ],
35
+ exitCode: 1,
36
+ output: ""
37
+ };
38
+ const modelSelection = decision.selection;
39
+ const plan = createRunnerLaunchPlan(context.config, {
40
+ model: modelSelection.model,
41
+ nodeId: node.id,
42
+ profileId: node.profile,
43
+ prompt,
44
+ worktreePath: context.worktreePath
45
+ });
46
+ if (node.timeoutMs) plan.timeoutMs = node.timeoutMs;
47
+ context.agentInvocations.push(plan);
48
+ emitAgentStart(context, plan, attempt);
49
+ const result = yield* (yield* AgentNodeRuntimeService).executeRunner(context.executor, plan, {
50
+ onOutput: agentOutputRecorder(context, node, attempt),
51
+ signal: context.signal
52
+ });
53
+ emitAgentFinish(context, plan, attempt, result);
54
+ if (result.sessionId) context.nodeStateStore.recordSessionId(node.id, result.sessionId);
55
+ const finalized = yield* finalizeAgentOutputEffect({
56
+ context,
57
+ node,
58
+ normalized: normalizeAgentOutput(plan, result.stdout),
59
+ plan,
60
+ result,
61
+ attempt
62
+ });
63
+ const handoff = yield* maybeDeriveHandoffEffect(context, node, finalized.output, attempt);
64
+ return withOptionalHandoff({
65
+ evidence: [
66
+ `agent boundary node=${node.id} profile=${node.profile} runner=${plan.runnerId}`,
67
+ `estimated context tokens: ${decision.estimatedTokens}`,
68
+ `model selection: ${modelSelection.model ?? "profile/default"} (${modelSelection.reason})`,
69
+ ...modelSelection.skipped.length ? [`model fallbacks skipped: ${modelSelection.skipped.join(", ")}`] : [],
70
+ ...finalized.evidence,
71
+ ...result.stderr ? [`stderr: ${result.stderr}`] : [],
72
+ ...result.timedOut ? ["agent timed out"] : []
73
+ ],
74
+ exitCode: result.exitCode,
75
+ output: finalized.output,
76
+ timedOut: result.timedOut
77
+ }, handoff);
67
78
  });
68
- const handoff = await maybeDeriveHandoff(context, node, finalized.output, attempt);
69
- return withOptionalHandoff({
70
- evidence: [
71
- `agent boundary node=${node.id} profile=${node.profile} runner=${plan.runnerId}`,
72
- `estimated context tokens: ${decision.estimatedTokens}`,
73
- `model selection: ${modelSelection.model ?? "profile/default"} (${modelSelection.reason})`,
74
- ...modelSelection.skipped.length ? [`model fallbacks skipped: ${modelSelection.skipped.join(", ")}`] : [],
75
- ...finalized.evidence,
76
- ...result.stderr ? [`stderr: ${result.stderr}`] : [],
77
- ...result.timedOut ? ["agent timed out"] : []
78
- ],
79
- exitCode: result.exitCode,
80
- output: finalized.output,
81
- timedOut: result.timedOut
82
- }, handoff);
79
+ }
80
+ function agentOutputRecorder(context, node, attempt) {
81
+ return (event) => {
82
+ if (event.stream !== "stdout") return;
83
+ emit(context, {
84
+ attempt,
85
+ format: "text",
86
+ nodeId: node.id,
87
+ output: event.chunk,
88
+ ...node.profile ? { profile: node.profile } : {},
89
+ type: "node.output.recorded"
90
+ });
91
+ };
83
92
  }
84
93
  function withOptionalHandoff(result, handoff) {
85
94
  return handoff ? {
@@ -97,19 +106,22 @@ function profileRunner(context, node) {
97
106
  * raw output, falling back to a synthesized minimal handoff. Returns undefined
98
107
  * when disabled so behaviour is unchanged.
99
108
  */
100
- async function maybeDeriveHandoff(context, node, rawOutput, attempt) {
101
- if (!context.config.context_handoff?.enabled) return;
102
- return parseHandoff(rawOutput) ?? await runHandoffFinalizer(context, node, rawOutput, attempt);
103
- }
104
- async function runHandoffFinalizer(context, node, rawOutput, attempt) {
105
- const runner = profileRunner(context, node);
106
- if (!(runner && rawOutput.trim())) return synthesizeMinimalHandoff(rawOutput);
107
- const plan = createHandoffFinalizerPlan(context, node, runner, rawOutput);
108
- context.agentInvocations.push(plan);
109
- emitAgentStart(context, plan, attempt);
110
- const result = await context.executor(plan, { signal: context.signal });
111
- emitAgentFinish(context, plan, attempt, result);
112
- return parseHandoff(normalizeAgentOutput(plan, result.stdout).output) ?? synthesizeMinimalHandoff(rawOutput);
109
+ function maybeDeriveHandoffEffect(context, node, rawOutput, attempt) {
110
+ if (!context.config.context_handoff?.enabled) return Effect.succeed(void 0);
111
+ const handoff = parseHandoff(rawOutput);
112
+ return handoff ? Effect.succeed(handoff) : runHandoffFinalizerEffect(context, node, rawOutput, attempt);
113
+ }
114
+ function runHandoffFinalizerEffect(context, node, rawOutput, attempt) {
115
+ return Effect.gen(function* () {
116
+ const runner = profileRunner(context, node);
117
+ if (!(runner && rawOutput.trim())) return synthesizeMinimalHandoff(rawOutput);
118
+ const plan = createHandoffFinalizerPlan(context, node, runner, rawOutput);
119
+ context.agentInvocations.push(plan);
120
+ emitAgentStart(context, plan, attempt);
121
+ const result = yield* (yield* AgentNodeRuntimeService).executeRunner(context.executor, plan, { signal: context.signal });
122
+ emitAgentFinish(context, plan, attempt, result);
123
+ return parseHandoff(normalizeAgentOutput(plan, result.stdout).output) ?? synthesizeMinimalHandoff(rawOutput);
124
+ }).pipe(Effect.catchAll(() => Effect.succeed(synthesizeMinimalHandoff(rawOutput))));
113
125
  }
114
126
  function createHandoffFinalizerPlan(context, node, runner, rawOutput) {
115
127
  const finalizerProfileId = `${node.id}:handoff`;
@@ -160,13 +172,15 @@ function decideNodeModel(prompt, node, budget) {
160
172
  selection
161
173
  };
162
174
  }
163
- async function finalizeAgentOutput(inputs) {
164
- const { attempt, context, node, normalized, plan, result } = inputs;
165
- const validStructuredOutput = selectValidStructuredOutput(context, node, normalized, plan, result.stdout);
166
- if (validStructuredOutput) return validStructuredOutput;
167
- const repairContext = outputRepairContext(context, node, normalized, result);
168
- if (!repairContext) return normalized;
169
- return await runOutputRepair(context, node, normalized, repairContext, attempt);
175
+ function finalizeAgentOutputEffect(inputs) {
176
+ return Effect.gen(function* () {
177
+ const { attempt, context, node, normalized, plan, result } = inputs;
178
+ const validStructuredOutput = selectValidStructuredOutput(context, node, normalized, plan, result.stdout);
179
+ if (validStructuredOutput) return validStructuredOutput;
180
+ const repairContext = outputRepairContext(context, node, normalized, result);
181
+ if (!repairContext) return normalized;
182
+ return yield* runOutputRepairEffect(context, node, normalized, repairContext, attempt);
183
+ });
170
184
  }
171
185
  function selectValidStructuredOutput(context, node, normalized, plan, stdout) {
172
186
  const output = (node.profile ? context.config.profiles[node.profile] : void 0)?.output;
@@ -211,53 +225,56 @@ function outputRepairContext(context, node, normalized, result) {
211
225
  validation: firstValidation
212
226
  };
213
227
  }
214
- async function runOutputRepair(context, node, normalized, repairContext, nodeAttempt) {
215
- let latest = normalized;
216
- let latestValidation = repairContext.validation;
217
- const evidence = [...repairContext.evidence];
218
- for (let attempt = 1; attempt <= repairContext.maxAttempts; attempt += 1) {
219
- const repairPlan = createOutputRepairPlan({
220
- context,
221
- node,
222
- originalOutput: latest.output,
223
- repairRunner: repairContext.runner,
224
- schemaPath: repairContext.schemaPath,
225
- validation: latestValidation
226
- });
227
- context.agentInvocations.push(repairPlan);
228
- emitAgentStart(context, repairPlan, nodeAttempt);
229
- const repairResult = await context.executor(repairPlan, { signal: context.signal });
230
- emitAgentFinish(context, repairPlan, nodeAttempt, repairResult);
231
- const repaired = normalizeAgentOutput(repairPlan, repairResult.stdout);
232
- const repairedOutput = normalizeJsonSource(repaired.output);
233
- const repairedValidation = validateJsonSchemaSource(repairedOutput, repairContext.schemaPath, context.worktreePath);
234
- latest = {
235
- evidence: [
236
- ...repaired.evidence,
237
- ...repairResult.stderr ? [`repair stderr: ${repairResult.stderr}`] : [],
238
- ...repairResult.timedOut ? ["output repair timed out"] : []
239
- ],
240
- output: repairedOutput
241
- };
242
- latestValidation = repairedValidation;
243
- const passed = repairResult.exitCode === 0 && repairedValidation.passed;
244
- evidence.push(...repaired.evidence, passed ? `output repair passed for ${node.id} after attempt ${attempt}` : `output repair failed for ${node.id} after attempt ${attempt}`, ...repairedValidation.evidence.map((item) => `repaired output: ${item}`));
245
- emit(context, {
246
- attempt,
247
- nodeId: node.id,
248
- passed,
249
- type: "output.repair",
250
- ...passed ? {} : { reason: repairedValidation.reason ?? "repair failed" }
251
- });
252
- if (passed) return {
228
+ function runOutputRepairEffect(context, node, normalized, repairContext, nodeAttempt) {
229
+ return Effect.gen(function* () {
230
+ let latest = normalized;
231
+ let latestValidation = repairContext.validation;
232
+ const evidence = [...repairContext.evidence];
233
+ const service = yield* AgentNodeRuntimeService;
234
+ for (let attempt = 1; attempt <= repairContext.maxAttempts; attempt += 1) {
235
+ const repairPlan = createOutputRepairPlan({
236
+ context,
237
+ node,
238
+ originalOutput: latest.output,
239
+ repairRunner: repairContext.runner,
240
+ schemaPath: repairContext.schemaPath,
241
+ validation: latestValidation
242
+ });
243
+ context.agentInvocations.push(repairPlan);
244
+ emitAgentStart(context, repairPlan, nodeAttempt);
245
+ const repairResult = yield* service.executeRunner(context.executor, repairPlan, { signal: context.signal });
246
+ emitAgentFinish(context, repairPlan, nodeAttempt, repairResult);
247
+ const repaired = normalizeAgentOutput(repairPlan, repairResult.stdout);
248
+ const repairedOutput = normalizeJsonSource(repaired.output);
249
+ const repairedValidation = validateJsonSchemaSource(repairedOutput, repairContext.schemaPath, context.worktreePath);
250
+ latest = {
251
+ evidence: [
252
+ ...repaired.evidence,
253
+ ...repairResult.stderr ? [`repair stderr: ${repairResult.stderr}`] : [],
254
+ ...repairResult.timedOut ? ["output repair timed out"] : []
255
+ ],
256
+ output: repairedOutput
257
+ };
258
+ latestValidation = repairedValidation;
259
+ const passed = repairResult.exitCode === 0 && repairedValidation.passed;
260
+ evidence.push(...repaired.evidence, passed ? `output repair passed for ${node.id} after attempt ${attempt}` : `output repair failed for ${node.id} after attempt ${attempt}`, ...repairedValidation.evidence.map((item) => `repaired output: ${item}`));
261
+ emit(context, {
262
+ attempt,
263
+ nodeId: node.id,
264
+ passed,
265
+ type: "output.repair",
266
+ ...passed ? {} : { reason: repairedValidation.reason ?? "repair failed" }
267
+ });
268
+ if (passed) return {
269
+ evidence,
270
+ output: repairedOutput
271
+ };
272
+ }
273
+ return {
253
274
  evidence,
254
- output: repairedOutput
275
+ output: latest.output
255
276
  };
256
- }
257
- return {
258
- evidence,
259
- output: latest.output
260
- };
277
+ });
261
278
  }
262
279
  function outputRepairOptions(output) {
263
280
  const repair = output.repair;
@@ -308,24 +325,39 @@ function createOutputRepairPlan(inputs) {
308
325
  function normalizeAgentOutput(plan, stdout) {
309
326
  return normalizeRunnerOutput(plan, stdout);
310
327
  }
311
- async function repoMapSection(node, context) {
328
+ function repoMapSectionEffect(node, context) {
312
329
  const repoMap = context.config.repo_map;
313
- if (!repoMap?.enabled) return "";
314
- try {
315
- return (await buildRepoMapContext({
316
- artifacts: node.needs.flatMap((need) => context.nodeStateStore.handoff(need)?.artifacts ?? []),
317
- taskText: context.task,
318
- tokenBudget: repoMap.token_budget,
319
- worktreePath: context.worktreePath
330
+ if (!repoMap?.enabled) return Effect.succeed("");
331
+ return Effect.gen(function* () {
332
+ const service = yield* AgentNodeRuntimeService;
333
+ return (yield* Effect.tryPromise({
334
+ catch: () => "",
335
+ try: () => service.buildRepoMap({
336
+ artifacts: node.needs.flatMap((need) => context.nodeStateStore.handoff(need)?.artifacts ?? []),
337
+ taskText: context.task,
338
+ tokenBudget: repoMap.token_budget,
339
+ worktreePath: context.worktreePath
340
+ })
320
341
  })).context;
321
- } catch {
322
- return "";
323
- }
342
+ }).pipe(Effect.catchAll(() => Effect.succeed("")));
324
343
  }
325
- async function renderAgentPrompt(node, context) {
326
- const profile = node.profile ? context.config.profiles[node.profile] : void 0;
327
- const instructions = profile ? readInstructions(context.worktreePath, profile.instructions) : "";
328
- const repoMap = await repoMapSection(node, context);
344
+ function renderAgentPromptEffect(node, context) {
345
+ return Effect.gen(function* () {
346
+ const profile = node.profile ? context.config.profiles[node.profile] : void 0;
347
+ const instructions = profile ? yield* readInstructionsEffect(context.worktreePath, profile.instructions) : "";
348
+ const repoMap = yield* repoMapSectionEffect(node, context);
349
+ return agentPromptSections({
350
+ context,
351
+ instructions,
352
+ node,
353
+ pathReferences: yield* renderProfilePathReferences(profile, context),
354
+ profile,
355
+ repoMap
356
+ }).filter(Boolean).join("\n");
357
+ });
358
+ }
359
+ function agentPromptSections(inputs) {
360
+ const { context, instructions, node, pathReferences, profile, repoMap } = inputs;
329
361
  return [
330
362
  instructions.trim(),
331
363
  "",
@@ -343,14 +375,16 @@ async function renderAgentPrompt(node, context) {
343
375
  `- rules: ${(profile?.rules ?? []).join(", ") || "none"}`,
344
376
  `- skills: ${(profile?.skills ?? []).join(", ") || "none"}`,
345
377
  `- mcp_servers: ${(profile?.mcp_servers ?? []).join(", ") || "none"}`,
346
- renderPathReferences("Loaded rules", profile?.rules, context.config.rules, context.worktreePath),
347
- renderPathReferences("Loaded skills", profile?.skills, context.config.skills, context.worktreePath),
378
+ ...pathReferences,
348
379
  renderMcpReferences(context.config, profile),
349
380
  "",
350
381
  ...inheritedOutputSections(node, context),
351
382
  "Dependency outputs:",
352
383
  ...node.needs.map((need) => renderDependencySection(need, context))
353
- ].filter(Boolean).join("\n");
384
+ ];
385
+ }
386
+ function renderProfilePathReferences(profile, context) {
387
+ return Effect.all([renderPathReferencesEffect("Loaded rules", profile?.rules, context.config.rules, context.worktreePath), renderPathReferencesEffect("Loaded skills", profile?.skills, context.config.skills, context.worktreePath)]);
354
388
  }
355
389
  /**
356
390
  * PIPE-83.5: render a dependency's curated NodeHandoff when one was derived
@@ -420,31 +454,38 @@ function renderTaskContext(taskContext) {
420
454
  ...acceptance.map((criterion) => `- ${criterion.id}: ${criterion.text}`)
421
455
  ].filter(Boolean).join("\n");
422
456
  }
423
- function readInstructions(worktreePath, instructions) {
424
- if (instructions.inline) return instructions.inline;
425
- if (instructions.path) return readFileSync(resolveFileReference(worktreePath, instructions.path), "utf8");
426
- return "";
457
+ function readInstructionsEffect(worktreePath, instructions) {
458
+ if (instructions.inline) return Effect.succeed(instructions.inline);
459
+ if (instructions.path) {
460
+ const instructionPath = instructions.path;
461
+ return AgentNodeRuntimeService.pipe(Effect.flatMap((service) => service.readText(resolveFileReference(worktreePath, instructionPath))));
462
+ }
463
+ return Effect.succeed("");
427
464
  }
428
- function renderPathReferences(heading, ids, registry, worktreePath) {
429
- if (!ids?.length) return "";
430
- return [
465
+ function renderPathReferencesEffect(heading, ids, registry, worktreePath) {
466
+ if (!ids?.length) return Effect.succeed("");
467
+ return Effect.gen(function* () {
468
+ const sections = yield* Effect.all(ids.map((id) => renderPathReferenceEffect(id, registry, worktreePath)));
469
+ return [
470
+ "",
471
+ `${heading}:`,
472
+ ...sections
473
+ ].join("\n");
474
+ });
475
+ }
476
+ function renderPathReferenceEffect(id, registry, worktreePath) {
477
+ const ref = registry[id];
478
+ const path = ref?.path ?? "";
479
+ const resolved = resolveRuntimePathReference(worktreePath, ref);
480
+ return AgentNodeRuntimeService.pipe(Effect.flatMap((service) => service.readText(resolved)), Effect.map((content) => [
481
+ `## ${id}`,
482
+ `Path: ${path}`,
431
483
  "",
432
- `${heading}:`,
433
- ...ids.map((id) => {
434
- const ref = registry[id];
435
- const path = ref?.path ?? "";
436
- const content = readFileSync(resolveRuntimePathReference(worktreePath, ref), "utf8").trimEnd();
437
- return [
438
- `## ${id}`,
439
- `Path: ${path}`,
440
- "",
441
- content
442
- ].join("\n");
443
- })
444
- ].join("\n");
484
+ content.trimEnd()
485
+ ].join("\n")));
445
486
  }
446
487
  function resolveRuntimePathReference(worktreePath, ref) {
447
- if (ref?.source_root === "package") return resolvePackageAssetPath(ref.path);
488
+ if (ref?.source_root === "package") return resolvePackageAssetPath(ref.path ?? "");
448
489
  return resolveFileReference(worktreePath, ref?.path ?? "");
449
490
  }
450
491
  function renderMcpReferences(config, profile) {