poe-code 3.0.182 → 3.0.184

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 (92) hide show
  1. package/dist/bin/poe-agent.js +1 -1
  2. package/dist/cli/commands/dashboard-loop-shared.d.ts +2 -11
  3. package/dist/cli/commands/dashboard-loop-shared.js +2 -10
  4. package/dist/cli/commands/dashboard-loop-shared.js.map +1 -1
  5. package/dist/cli/commands/memory.d.ts +3 -0
  6. package/dist/cli/commands/memory.js +181 -0
  7. package/dist/cli/commands/memory.js.map +1 -0
  8. package/dist/cli/commands/pipeline-init.d.ts +16 -0
  9. package/dist/cli/commands/pipeline-init.js +168 -0
  10. package/dist/cli/commands/pipeline-init.js.map +1 -0
  11. package/dist/cli/commands/pipeline.js +264 -89
  12. package/dist/cli/commands/pipeline.js.map +1 -1
  13. package/dist/cli/commands/plan.d.ts +1 -0
  14. package/dist/cli/commands/plan.js +1 -1
  15. package/dist/cli/commands/plan.js.map +1 -1
  16. package/dist/cli/poe-agent-main.d.ts +10 -1
  17. package/dist/cli/poe-agent-main.js +89 -47
  18. package/dist/cli/poe-agent-main.js.map +1 -1
  19. package/dist/cli/program.js +5 -0
  20. package/dist/cli/program.js.map +1 -1
  21. package/dist/corpus/001-archaeoastronomy.md +479 -0
  22. package/dist/corpus/002-magnetohydrodynamics.md +475 -0
  23. package/dist/corpus/003-biosemiotics.md +483 -0
  24. package/dist/corpus/004-cryopedology.md +483 -0
  25. package/dist/corpus/005-geomicrobiology.md +479 -0
  26. package/dist/corpus/006-aeronomy.md +487 -0
  27. package/dist/corpus/007-paleoclimatology.md +479 -0
  28. package/dist/corpus/008-hydrogeophysics.md +479 -0
  29. package/dist/corpus/009-magnetostratigraphy.md +475 -0
  30. package/dist/corpus/010-isotope-hydrology.md +481 -0
  31. package/dist/corpus/011-speleothem-geochemistry.md +474 -0
  32. package/dist/corpus/012-astrobiogeochemistry.md +475 -0
  33. package/dist/corpus/013-neuroethology.md +483 -0
  34. package/dist/corpus/014-chronophysiology.md +483 -0
  35. package/dist/corpus/015-limnogeochemistry.md +475 -0
  36. package/dist/corpus/016-palynology.md +483 -0
  37. package/dist/corpus/017-volcanotectonics.md +473 -0
  38. package/dist/corpus/018-seismotectonics.md +473 -0
  39. package/dist/corpus/019-biogeomorphology.md +475 -0
  40. package/dist/corpus/020-geobiophysics.md +479 -0
  41. package/dist/corpus/021-phytolith-analysis.md +481 -0
  42. package/dist/corpus/022-archaeometallurgy.md +479 -0
  43. package/dist/corpus/023-paleomagnetism.md +479 -0
  44. package/dist/corpus/024-biocalorimetry.md +475 -0
  45. package/dist/corpus/025-atmospheric-chemiluminescence.md +473 -0
  46. package/dist/corpus/026-cryoseismology.md +479 -0
  47. package/dist/corpus/027-extremophile-radiobiology.md +475 -0
  48. package/dist/corpus/028-heliophysics.md +479 -0
  49. package/dist/corpus/029-astroparticle-geophysics.md +474 -0
  50. package/dist/corpus/030-glaciohydrology.md +479 -0
  51. package/dist/corpus/031-permafrost-microbiology.md +477 -0
  52. package/dist/corpus/032-ecoacoustics.md +479 -0
  53. package/dist/corpus/033-dendroclimatology.md +473 -0
  54. package/dist/corpus/034-ionospheric-tomography.md +477 -0
  55. package/dist/corpus/035-marine-geodesy.md +481 -0
  56. package/dist/corpus/036-sedimentary-ancient-dna.md +481 -0
  57. package/dist/corpus/037-myrmecochory-dynamics.md +474 -0
  58. package/dist/corpus/038-chemosensory-ecology.md +477 -0
  59. package/dist/corpus/039-spintronics-materials.md +479 -0
  60. package/dist/corpus/040-nanotoxicology.md +483 -0
  61. package/dist/corpus/041-cosmochemistry.md +483 -0
  62. package/dist/corpus/042-quaternary-geochronology.md +471 -0
  63. package/dist/corpus/043-biophotonics.md +479 -0
  64. package/dist/corpus/044-evolutionary-morphometrics.md +481 -0
  65. package/dist/corpus/045-cryovolcanology.md +475 -0
  66. package/dist/corpus/046-exoplanet-atmospheric-dynamics.md +479 -0
  67. package/dist/corpus/047-microbial-electrosynthesis.md +477 -0
  68. package/dist/corpus/048-paleoseismology.md +479 -0
  69. package/dist/corpus/049-actinide-geochemistry.md +477 -0
  70. package/dist/corpus/050-quantum-biology.md +489 -0
  71. package/dist/index.d.ts +2 -2
  72. package/dist/index.js +28342 -24810
  73. package/dist/index.js.map +4 -4
  74. package/dist/providers/poe-agent.js +3716 -1863
  75. package/dist/providers/poe-agent.js.map +4 -4
  76. package/dist/sdk/pipeline.d.ts +29 -2
  77. package/dist/sdk/pipeline.js +209 -2
  78. package/dist/sdk/pipeline.js.map +1 -1
  79. package/dist/services/config.d.ts +30 -1
  80. package/dist/services/config.js +11 -0
  81. package/dist/services/config.js.map +1 -1
  82. package/dist/templates/pipeline/SKILL_plan.md +26 -13
  83. package/dist/templates/pipeline/steps.yaml.mustache +5 -2
  84. package/package.json +4 -1
  85. package/packages/design-system/dist/dashboard/components/stats-pane.js +2 -1
  86. package/packages/design-system/dist/dashboard/index.d.ts +1 -0
  87. package/packages/design-system/dist/dashboard/index.js +1 -0
  88. package/packages/design-system/dist/dashboard/should-use-dashboard.d.ts +10 -0
  89. package/packages/design-system/dist/dashboard/should-use-dashboard.js +7 -0
  90. package/packages/design-system/dist/dashboard/types.d.ts +1 -0
  91. package/packages/design-system/dist/index.d.ts +1 -1
  92. package/packages/design-system/dist/index.js +1 -1
@@ -3,15 +3,16 @@ import { readFile, stat } from "node:fs/promises";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { acp, cancel, createDashboard, isCancel, multiselect, promptText, select } from "@poe-code/design-system";
5
5
  import { resolveAgentId, parseAgentSpecifier, formatAgentSpecifier, allAgents } from "@poe-code/agent-defs";
6
- import { isActivityTimeoutError, renderAcpEvent } from "@poe-code/agent-spawn";
6
+ import { renderAcpEvent } from "@poe-code/agent-spawn";
7
7
  import { installSkill, resolveAgentSupport, supportedAgents } from "@poe-code/agent-skill-config";
8
8
  import { readMergedDocument, resolveScope } from "@poe-code/poe-code-config";
9
9
  import { pipelineConfigScope, planConfigScope } from "../../services/config.js";
10
10
  import { ValidationError } from "../errors.js";
11
+ import { discoverPipelineInitSources } from "./pipeline-init.js";
11
12
  import { createExecutionResources, resolveCommandFlags, resolveDefaultAgent } from "./shared.js";
12
- import { runPipeline as sdkRunPipeline } from "../../sdk/pipeline.js";
13
+ import { runPipelineInit as sdkRunPipelineInit, runPipeline as sdkRunPipeline } from "../../sdk/pipeline.js";
13
14
  import { spawn as sdkSpawn } from "../../sdk/spawn.js";
14
- import { buildExecutionPrompt, interpolate, loadResolvedSteps, parsePlan, resolveAbsolutePlanPath, resolveFileIncludes, resolvePlanDirectory, resolvePlanPaths } from "@poe-code/pipeline";
15
+ import { buildExecutionPrompt, interpolatePipelineVars, loadResolvedSteps, parsePlan, resolveAbsolutePlanPath, resolveFileIncludes, resolvePipelineVars, resolvePlanDirectory, resolvePlanPaths, validateResolvedPromptVars } from "@poe-code/pipeline";
15
16
  import { createDashboardLineBuffer, formatDashboardDuration, formatDashboardTimestamp, registerDashboardQuitCommands, shouldUseInteractiveDashboard } from "./dashboard-loop-shared.js";
16
17
  async function resolvePipelineCommandConfig(container) {
17
18
  const configDoc = await readMergedDocument(container.fs, container.env.configPath, container.env.projectConfigPath);
@@ -47,11 +48,24 @@ function resolveMaxRuns(value) {
47
48
  }
48
49
  return parsed;
49
50
  }
51
+ function resolvePipelineInitSourcePath(container, sourcePath) {
52
+ const absolutePath = sourcePath.startsWith("~/")
53
+ ? path.join(container.env.homeDir, sourcePath.slice(2))
54
+ : path.isAbsolute(sourcePath)
55
+ ? sourcePath
56
+ : path.resolve(container.env.cwd, sourcePath);
57
+ return {
58
+ absolutePath,
59
+ relativePath: sourcePath,
60
+ title: path.basename(sourcePath, path.extname(sourcePath))
61
+ };
62
+ }
50
63
  function formatRunSummary(result) {
51
64
  const metrics = result.metrics;
52
65
  return [
53
66
  `Runs: ${result.runsCompleted}`,
54
- `tasksCompleted: ${metrics.tasksCompleted}, tasksFailed: ${metrics.tasksFailed}, stepsCompleted: ${metrics.stepsCompleted}`,
67
+ `Tasks: ${metrics.tasksCompleted} completed, ${metrics.tasksFailed} failed`,
68
+ `Steps: ${metrics.stepsCompleted} completed`,
55
69
  `Total tokens: ${metrics.totalInputTokens} input, ${metrics.totalOutputTokens} output, ${metrics.totalCachedTokens} cached`,
56
70
  `Duration: ${formatDashboardDuration(result.totalDurationMs)}`
57
71
  ].join("\n ");
@@ -96,6 +110,14 @@ function formatTaskCompleteMessage(progress) {
96
110
  if (progress.phase) {
97
111
  return `${progress.taskTitle} ${status} in ${duration}${usage}`;
98
112
  }
113
+ if (progress.stepName) {
114
+ if (progress.success && !progress.taskCompleted) {
115
+ return `Task ${progress.taskId} (${progress.stepName}) step done in ${duration}${usage}`;
116
+ }
117
+ if (!progress.success) {
118
+ return `Task ${progress.taskId} (${progress.stepName}) failed in ${duration}${usage}`;
119
+ }
120
+ }
99
121
  return `Task ${progress.taskId} ${status} in ${duration}${usage}`;
100
122
  }
101
123
  function formatDashboardCurrentAction(progress) {
@@ -174,69 +196,64 @@ async function streamAcpEventsToDashboard(options) {
174
196
  }
175
197
  function createPipelineDashboardRunAgent(options) {
176
198
  return async (input) => {
177
- for (let attempt = 1; attempt <= 3; attempt += 1) {
178
- const toolBuffer = createDashboardLineBuffer((line) => {
179
- options.appendOutput("tool", `[${options.activeStage()}] ${line}`);
180
- });
181
- const errorBuffer = createDashboardLineBuffer((line) => {
182
- options.appendOutput("error", `[${options.activeStage()}] ${line}`);
183
- });
184
- let sawStdout = false;
185
- let sawStderr = false;
186
- try {
187
- const { events, result } = sdkSpawn(input.agent, {
188
- prompt: input.prompt,
189
- cwd: input.cwd,
190
- logDir: input.logDir,
191
- model: input.model,
192
- mode: input.mode,
193
- ...(input.mcpServers ? { mcpServers: input.mcpServers } : {}),
194
- ...(input.signal ? { signal: input.signal } : {}),
195
- tee: {
196
- stdout: {
197
- write(chunk) {
198
- sawStdout = true;
199
- toolBuffer.push(chunk);
200
- }
201
- },
202
- stderr: {
203
- write(chunk) {
204
- sawStderr = true;
205
- errorBuffer.push(chunk);
206
- }
199
+ const toolBuffer = createDashboardLineBuffer((line) => {
200
+ options.appendOutput("tool", `[${options.activeStage()}] ${line}`);
201
+ });
202
+ const errorBuffer = createDashboardLineBuffer((line) => {
203
+ options.appendOutput("error", `[${options.activeStage()}] ${line}`);
204
+ });
205
+ let sawStdout = false;
206
+ let sawStderr = false;
207
+ try {
208
+ const { events, result } = sdkSpawn(input.agent, {
209
+ prompt: input.prompt,
210
+ cwd: input.cwd,
211
+ logDir: input.logDir,
212
+ model: input.model,
213
+ mode: input.mode,
214
+ ...(input.mcpServers ? { mcpServers: input.mcpServers } : {}),
215
+ ...(input.signal ? { signal: input.signal } : {}),
216
+ tee: {
217
+ stdout: {
218
+ write(chunk) {
219
+ sawStdout = true;
220
+ toolBuffer.push(chunk);
207
221
  }
208
222
  },
209
- activityTimeoutMs: 10 * 60 * 1000
210
- });
211
- const eventStream = streamAcpEventsToDashboard({
212
- events,
213
- onToolOutput(chunk) {
214
- toolBuffer.push(chunk);
215
- },
216
- onErrorOutput(chunk) {
217
- errorBuffer.push(chunk);
223
+ stderr: {
224
+ write(chunk) {
225
+ sawStderr = true;
226
+ errorBuffer.push(chunk);
227
+ }
218
228
  }
219
- });
220
- const [spawnResult, sawEvents] = await Promise.all([result, eventStream]);
221
- if (!sawEvents && !sawStdout && spawnResult.stdout.length > 0) {
222
- toolBuffer.push(spawnResult.stdout);
223
- }
224
- if (!sawStderr && spawnResult.stderr.length > 0) {
225
- errorBuffer.push(spawnResult.stderr);
229
+ },
230
+ activityTimeoutMs: 10 * 60 * 1000
231
+ });
232
+ const eventStream = streamAcpEventsToDashboard({
233
+ events,
234
+ onToolOutput(chunk) {
235
+ toolBuffer.push(chunk);
236
+ },
237
+ onErrorOutput(chunk) {
238
+ errorBuffer.push(chunk);
226
239
  }
227
- toolBuffer.flush();
228
- errorBuffer.flush();
229
- return spawnResult;
240
+ });
241
+ const [spawnResult, sawEvents] = await Promise.all([result, eventStream]);
242
+ if (!sawEvents && !sawStdout && spawnResult.stdout.length > 0) {
243
+ toolBuffer.push(spawnResult.stdout);
230
244
  }
231
- catch (error) {
232
- toolBuffer.flush();
233
- errorBuffer.flush();
234
- if (!isActivityTimeoutError(error) || attempt === 3) {
235
- throw error;
236
- }
245
+ if (!sawStderr && spawnResult.stderr.length > 0) {
246
+ errorBuffer.push(spawnResult.stderr);
237
247
  }
248
+ toolBuffer.flush();
249
+ errorBuffer.flush();
250
+ return spawnResult;
251
+ }
252
+ catch (error) {
253
+ toolBuffer.flush();
254
+ errorBuffer.flush();
255
+ throw error;
238
256
  }
239
- throw new Error("Unreachable");
240
257
  };
241
258
  }
242
259
  function dashboardStatusForResult(result) {
@@ -261,15 +278,18 @@ async function runPipelineWithDashboard(options) {
261
278
  let currentAction;
262
279
  let currentStage = "pipeline";
263
280
  let status = "running";
281
+ let waitingForLock = false;
264
282
  const syncStats = () => {
265
- dashboard.updateStats({
283
+ const stats = {
266
284
  status,
267
285
  iterations,
286
+ iterationsLabel: "Tasks",
268
287
  tokensIn,
269
288
  tokensOut,
270
289
  elapsedMs: Math.max(0, Date.now() - startedAt),
271
290
  ...(currentAction ? { currentAction } : {})
272
- });
291
+ };
292
+ dashboard.updateStats(stats);
273
293
  };
274
294
  const appendOutput = (kind, message) => {
275
295
  dashboard.appendOutput({
@@ -312,6 +332,18 @@ async function runPipelineWithDashboard(options) {
312
332
  onPlanReloadError(error) {
313
333
  appendOutput("error", `Plan reload failed, using last good state: ${error.message}`);
314
334
  },
335
+ onLockStatusChange(lockStatus) {
336
+ appendOutput("status", lockStatus.message);
337
+ if (lockStatus.state === "waiting") {
338
+ waitingForLock = true;
339
+ currentAction = lockStatus.message;
340
+ }
341
+ else if (waitingForLock) {
342
+ waitingForLock = false;
343
+ currentAction = undefined;
344
+ }
345
+ syncStats();
346
+ },
315
347
  onPlanResolved(summary) {
316
348
  appendOutput("info", `Config · ${formatPipelineConfigSummary({
317
349
  agent: options.agent,
@@ -330,7 +362,9 @@ async function runPipelineWithDashboard(options) {
330
362
  syncStats();
331
363
  },
332
364
  onTaskComplete(progress) {
333
- iterations += 1;
365
+ if (progress.taskCompleted) {
366
+ iterations += 1;
367
+ }
334
368
  if (progress.usage) {
335
369
  tokensIn += progress.usage.inputTokens;
336
370
  tokensOut += progress.usage.outputTokens;
@@ -362,11 +396,16 @@ function resolvePipelinePaths(scope, cwd, homeDir) {
362
396
  ? path.join(homeDir, ".poe-code", "pipeline")
363
397
  : path.join(cwd, ".poe-code", "pipeline");
364
398
  const displayRoot = scope === "global" ? "~/.poe-code/pipeline" : ".poe-code/pipeline";
399
+ const stepsDirectoryPath = path.join(rootPath, "steps");
365
400
  return {
366
401
  plansPath: path.join(rootPath, "plans"),
367
- stepsPath: path.join(rootPath, "steps.yaml"),
402
+ stepsDirectoryPath,
403
+ defaultStepsPath: path.join(stepsDirectoryPath, "default.yaml"),
404
+ legacyStepsPath: path.join(rootPath, "steps.yaml"),
368
405
  displayPlansPath: `${displayRoot}/plans`,
369
- displayStepsPath: `${displayRoot}/steps.yaml`
406
+ displayStepsDirectoryPath: `${displayRoot}/steps`,
407
+ displayDefaultStepsPath: `${displayRoot}/steps/default.yaml`,
408
+ displayLegacyStepsPath: `${displayRoot}/steps.yaml`
370
409
  };
371
410
  }
372
411
  async function loadPipelineTemplates() {
@@ -541,6 +580,9 @@ export function registerPipelineCommand(program, container) {
541
580
  onPlanReloadError(error) {
542
581
  resources.logger.warn(`Plan reload failed, using last good state: ${error.message}`);
543
582
  },
583
+ onLockStatusChange(lockStatus) {
584
+ resources.logger.info(lockStatus.message);
585
+ },
544
586
  onPlanResolved(summary) {
545
587
  resources.logger.resolved("Config", formatPipelineConfigSummary({
546
588
  agent,
@@ -589,6 +631,122 @@ export function registerPipelineCommand(program, container) {
589
631
  resources.context.finalize();
590
632
  }
591
633
  });
634
+ pipeline
635
+ .command("init")
636
+ .description("Initialize pipeline plans from source Markdown docs.")
637
+ .argument("[question]", "Optional user question to forward to the plan generator")
638
+ .option("--agent <name>", "Agent to generate the plan with")
639
+ .option("--model <model>", "Model override passed to the agent")
640
+ .option("--source <path>", "Single source Markdown doc to convert")
641
+ .option("--sources <paths...>", "Multiple source Markdown docs to convert")
642
+ .action(async function (question) {
643
+ const flags = resolveCommandFlags(program);
644
+ const resources = createExecutionResources(container, flags, "pipeline:init");
645
+ const options = this.opts();
646
+ resources.logger.intro("pipeline init");
647
+ try {
648
+ let sourcePaths = options.sources;
649
+ let resolvedQuestion = question;
650
+ if (!resolvedQuestion && sourcePaths && sourcePaths.length > 0) {
651
+ const trailingArgument = sourcePaths[sourcePaths.length - 1];
652
+ if (trailingArgument && !trailingArgument.toLowerCase().endsWith(".md")) {
653
+ resolvedQuestion = trailingArgument;
654
+ sourcePaths = sourcePaths.slice(0, -1);
655
+ }
656
+ }
657
+ let agent;
658
+ if (options.agent) {
659
+ agent = resolvePipelineAgent(options.agent);
660
+ }
661
+ else if (flags.assumeYes) {
662
+ agent = DEFAULT_PIPELINE_AGENT;
663
+ }
664
+ else {
665
+ const selected = await select({
666
+ message: "Select agent to generate pipeline plans with:",
667
+ options: supportedAgents.map((value) => ({
668
+ value,
669
+ label: value
670
+ }))
671
+ });
672
+ if (isCancel(selected)) {
673
+ cancel("Pipeline init cancelled.");
674
+ return;
675
+ }
676
+ agent = resolvePipelineAgent(selected);
677
+ }
678
+ if (options.source && sourcePaths && sourcePaths.length > 0) {
679
+ throw new ValidationError("Use either --source or --sources, not both.");
680
+ }
681
+ let sources;
682
+ if (options.source) {
683
+ sources = [resolvePipelineInitSourcePath(container, options.source)];
684
+ }
685
+ else if (sourcePaths && sourcePaths.length > 0) {
686
+ sources = sourcePaths.map((sourcePath) => resolvePipelineInitSourcePath(container, sourcePath));
687
+ }
688
+ else {
689
+ if (flags.assumeYes) {
690
+ throw new ValidationError("Provide --source or --sources when using --yes.");
691
+ }
692
+ const discoveredSources = await discoverPipelineInitSources({ container });
693
+ if (discoveredSources.length === 0) {
694
+ resources.logger.info("No source documents available to initialize.");
695
+ return;
696
+ }
697
+ const selected = await multiselect({
698
+ message: "Select source Markdown docs to convert:",
699
+ options: discoveredSources.map((source) => ({
700
+ value: source.relativePath,
701
+ label: `${source.title} (${source.relativePath})`
702
+ })),
703
+ required: true
704
+ });
705
+ if (isCancel(selected)) {
706
+ cancel("Pipeline init cancelled.");
707
+ return;
708
+ }
709
+ const selectedSourcePaths = Array.isArray(selected)
710
+ ? new Set(selected)
711
+ : new Set();
712
+ sources = discoveredSources.filter((source) => selectedSourcePaths.has(source.relativePath));
713
+ if (sources.length === 0) {
714
+ return;
715
+ }
716
+ }
717
+ const result = await sdkRunPipelineInit({
718
+ agent,
719
+ cwd: container.env.cwd,
720
+ homeDir: container.env.homeDir,
721
+ ...(options.model ? { model: options.model } : {}),
722
+ ...(resolvedQuestion ? { question: resolvedQuestion } : {}),
723
+ sources,
724
+ assumeYes: flags.assumeYes,
725
+ onSourceStart(source, index, total) {
726
+ resources.logger.info(`Source ${index}/${total}: ${source.relativePath}`);
727
+ },
728
+ onSourceComplete(source, index, total) {
729
+ resources.logger.success(`Completed ${index}/${total}: ${source.relativePath}`);
730
+ }
731
+ });
732
+ if (result.stopReason === "failed") {
733
+ process.exitCode = 1;
734
+ resources.logger.error(result.failedSource
735
+ ? `Pipeline init failed at ${result.failedSource}.`
736
+ : "Pipeline init failed.");
737
+ return;
738
+ }
739
+ if (result.stopReason === "cancelled") {
740
+ process.exitCode = 130;
741
+ resources.logger.warn("Pipeline init cancelled.");
742
+ return;
743
+ }
744
+ resources.logger.success("Pipeline init finished.");
745
+ }
746
+ finally {
747
+ resources.context.finalize();
748
+ }
749
+ });
592
750
  pipeline
593
751
  .command("validate")
594
752
  .description("Validate a pipeline plan markdown file without running it.")
@@ -601,38 +759,43 @@ export function registerPipelineCommand(program, container) {
601
759
  resources.logger.intro("pipeline validate");
602
760
  const absolutePath = resolveAbsolutePlanPath(file, container.env.cwd, container.env.homeDir);
603
761
  const content = await container.fs.readFile(absolutePath, "utf8");
762
+ const draftPlan = parsePlan(content);
604
763
  const steps = await loadResolvedSteps({
605
764
  cwd: container.env.cwd,
606
765
  homeDir: container.env.homeDir,
607
- fs: container.fs
766
+ fs: container.fs,
767
+ name: draftPlan.extends,
768
+ stepOverrides: draftPlan.stepOverrides
608
769
  });
609
- const hasSteps = Object.keys(steps.steps).length > 0;
610
- const plan = parsePlan(content, hasSteps ? { availableSteps: steps.steps } : {});
770
+ const plan = parsePlan(content, { availableSteps: steps.steps });
611
771
  const total = plan.tasks.length;
612
772
  const done = plan.tasks.filter((t) => {
613
773
  if (typeof t.status === "string")
614
774
  return t.status === "done";
615
775
  return Object.values(t.status).every((s) => s === "done");
616
776
  }).length;
777
+ const readFile = container.fs.readFile.bind(container.fs);
778
+ const resolvedVars = await resolvePipelineVars(plan.vars ?? {}, container.env.cwd, readFile);
779
+ const resolvedSetup = plan.setup === null ? undefined : (plan.setup ?? steps.setup);
780
+ const resolvedTeardown = plan.teardown === null ? undefined : (plan.teardown ?? steps.teardown);
781
+ validateResolvedPromptVars({
782
+ plan,
783
+ steps: steps.steps,
784
+ planPath: file,
785
+ vars: resolvedVars,
786
+ setup: resolvedSetup,
787
+ teardown: resolvedTeardown
788
+ });
617
789
  resources.logger.resolved("Plan", file);
618
790
  resources.logger.resolved("Tasks", `${total} tasks (${done} done)`);
619
- if (hasSteps) {
791
+ if (Object.keys(steps.steps).length > 0) {
620
792
  resources.logger.resolved("Steps", Object.keys(steps.steps).join(", "));
621
793
  }
622
794
  resources.logger.success("Plan is valid.");
623
795
  const opts = this.opts();
624
796
  if (opts.preview) {
625
- const readFile = container.fs.readFile.bind(container.fs);
626
- const resolvedVars = {};
627
- for (const [key, value] of Object.entries(plan.vars ?? {})) {
628
- resolvedVars[key] = await resolveFileIncludes(value, container.env.cwd, readFile);
629
- }
630
- const resolvedSetup = plan.setup === null ? undefined : (plan.setup ?? steps.setup);
631
- const resolvedTeardown = plan.teardown === null ? undefined : (plan.teardown ?? steps.teardown);
632
797
  if (resolvedSetup) {
633
- const raw = Object.keys(resolvedVars).length > 0
634
- ? interpolate(resolvedSetup.prompt, resolvedVars)
635
- : resolvedSetup.prompt;
798
+ const raw = interpolatePipelineVars(resolvedSetup.prompt, resolvedVars, "setup");
636
799
  const expanded = await resolveFileIncludes(raw, container.env.cwd, readFile);
637
800
  resources.logger.resolved("setup", expanded);
638
801
  }
@@ -659,9 +822,7 @@ export function registerPipelineCommand(program, container) {
659
822
  }
660
823
  }
661
824
  if (resolvedTeardown) {
662
- const raw = Object.keys(resolvedVars).length > 0
663
- ? interpolate(resolvedTeardown.prompt, resolvedVars)
664
- : resolvedTeardown.prompt;
825
+ const raw = interpolatePipelineVars(resolvedTeardown.prompt, resolvedVars, "teardown");
665
826
  const expanded = await resolveFileIncludes(raw, container.env.cwd, readFile);
666
827
  resources.logger.resolved("teardown", expanded);
667
828
  }
@@ -689,7 +850,7 @@ export function registerPipelineCommand(program, container) {
689
850
  .option("--agent <name>", "Agent to install the Pipeline skill for")
690
851
  .option("--local", "Install project-local skill and pipeline files")
691
852
  .option("--global", "Install user-global skill and pipeline files")
692
- .option("--force", "Overwrite an existing steps.yaml scaffold")
853
+ .option("--force", "Overwrite an existing default step config scaffold")
693
854
  .action(async function () {
694
855
  const flags = resolveCommandFlags(program);
695
856
  const resources = createExecutionResources(container, flags, "pipeline:install");
@@ -778,21 +939,35 @@ export function registerPipelineCommand(program, container) {
778
939
  resources.logger.info(`Create: ${pipelinePaths.displayPlansPath}`);
779
940
  }
780
941
  }
781
- const stepsExists = await pathExists(container.fs, pipelinePaths.stepsPath);
942
+ const legacyStepsExists = await pathExists(container.fs, pipelinePaths.legacyStepsPath);
943
+ let stepsExists = await pathExists(container.fs, pipelinePaths.defaultStepsPath);
944
+ if (legacyStepsExists && !stepsExists) {
945
+ if (flags.dryRun) {
946
+ resources.logger.dryRun(`Would rename: ${pipelinePaths.displayLegacyStepsPath} -> ${pipelinePaths.displayDefaultStepsPath}`);
947
+ }
948
+ else {
949
+ await container.fs.mkdir(pipelinePaths.stepsDirectoryPath, {
950
+ recursive: true
951
+ });
952
+ await container.fs.rename(pipelinePaths.legacyStepsPath, pipelinePaths.defaultStepsPath);
953
+ resources.logger.info(`Rename: ${pipelinePaths.displayLegacyStepsPath} -> ${pipelinePaths.displayDefaultStepsPath}`);
954
+ }
955
+ stepsExists = true;
956
+ }
782
957
  if (stepsExists && !options.force) {
783
- resources.logger.info(`Skip: ${pipelinePaths.displayStepsPath} (already exists)`);
958
+ resources.logger.info(`Skip: ${pipelinePaths.displayDefaultStepsPath} (already exists)`);
784
959
  }
785
960
  else if (flags.dryRun) {
786
- resources.logger.dryRun(`Would ${stepsExists ? "overwrite" : "create"}: ${pipelinePaths.displayStepsPath}`);
961
+ resources.logger.dryRun(`Would ${stepsExists ? "overwrite" : "create"}: ${pipelinePaths.displayDefaultStepsPath}`);
787
962
  }
788
963
  else {
789
- await container.fs.mkdir(path.dirname(pipelinePaths.stepsPath), {
964
+ await container.fs.mkdir(path.dirname(pipelinePaths.defaultStepsPath), {
790
965
  recursive: true
791
966
  });
792
- await container.fs.writeFile(pipelinePaths.stepsPath, templates.steps, {
967
+ await container.fs.writeFile(pipelinePaths.defaultStepsPath, templates.steps, {
793
968
  encoding: "utf8"
794
969
  });
795
- resources.logger.info(`${stepsExists ? "Overwrite" : "Create"}: ${pipelinePaths.displayStepsPath}`);
970
+ resources.logger.info(`${stepsExists ? "Overwrite" : "Create"}: ${pipelinePaths.displayDefaultStepsPath}`);
796
971
  }
797
972
  resources.context.complete({
798
973
  success: `Installed Pipeline skill for ${support.id} and scaffolded ${scope} pipeline files`,