@ordewell/core 0.4.10 → 0.4.12

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 (48) hide show
  1. package/dist/ApprovalPolicy-BVhGdECT.d.mts +79 -0
  2. package/dist/ApprovalPolicy-BVhGdECT.d.ts +79 -0
  3. package/dist/{ITerminalRunner-CbE6gKLI.d.ts → ITerminalRunner-D_RBDoHa.d.mts} +10 -81
  4. package/dist/{ITerminalRunner-1NAv8wCC.d.mts → ITerminalRunner-DqAs12sy.d.ts} +10 -81
  5. package/dist/{ModeResolver-F5wnlSz1.d.mts → ModeResolver-CCKYqdZC.d.mts} +1 -1
  6. package/dist/{ModeResolver-Dx5gzNPC.d.ts → ModeResolver-CzZd-i6T.d.ts} +1 -1
  7. package/dist/{Task-1NwmUImI.d.mts → Task-cGrfYRXK.d.mts} +8 -1
  8. package/dist/{Task-1NwmUImI.d.ts → Task-cGrfYRXK.d.ts} +8 -1
  9. package/dist/chunk-HPNUFBIT.mjs +19 -0
  10. package/dist/chunk-HPNUFBIT.mjs.map +1 -0
  11. package/dist/{chunk-IO6PEEN6.mjs → chunk-N6HTBUKG.mjs} +11 -1
  12. package/dist/chunk-N6HTBUKG.mjs.map +1 -0
  13. package/dist/{chunk-TUGJXD4G.mjs → chunk-UEJ3SZQ7.mjs} +2 -2
  14. package/dist/{chunk-YI4ZDZEE.mjs → chunk-YTJ7EOV6.mjs} +62 -3
  15. package/dist/chunk-YTJ7EOV6.mjs.map +1 -0
  16. package/dist/index.d.mts +187 -235
  17. package/dist/index.d.ts +187 -235
  18. package/dist/index.js +643 -439
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +611 -486
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/order-labels.d.mts +17 -0
  23. package/dist/order-labels.d.ts +17 -0
  24. package/dist/order-labels.js +44 -0
  25. package/dist/order-labels.js.map +1 -0
  26. package/dist/order-labels.mjs +9 -0
  27. package/dist/order-labels.mjs.map +1 -0
  28. package/dist/{parsing-BqbJySeo.d.mts → parsing-BT-dvFcu.d.mts} +2 -2
  29. package/dist/{parsing-X0Gvuz-q.d.ts → parsing-D87zKtTe.d.ts} +2 -2
  30. package/dist/parsing.d.mts +3 -3
  31. package/dist/parsing.d.ts +3 -3
  32. package/dist/parsing.js.map +1 -1
  33. package/dist/parsing.mjs +3 -2
  34. package/dist/{plan-utils-DtsfHnn_.d.ts → plan-utils-BbC9KdK4.d.ts} +139 -3
  35. package/dist/{plan-utils-B5rVXNVI.d.mts → plan-utils-DHBIvkiT.d.mts} +139 -3
  36. package/dist/plan-utils.d.mts +4 -3
  37. package/dist/plan-utils.d.ts +4 -3
  38. package/dist/plan-utils.js +14 -2
  39. package/dist/plan-utils.js.map +1 -1
  40. package/dist/plan-utils.mjs +9 -4
  41. package/dist/testing.d.mts +3 -2
  42. package/dist/testing.d.ts +3 -2
  43. package/package.json +12 -2
  44. package/skills/grilling/SKILL.md +31 -0
  45. package/skills/to-spec/SKILL.md +64 -0
  46. package/dist/chunk-IO6PEEN6.mjs.map +0 -1
  47. package/dist/chunk-YI4ZDZEE.mjs.map +0 -1
  48. /package/dist/{chunk-TUGJXD4G.mjs.map → chunk-UEJ3SZQ7.mjs.map} +0 -0
package/dist/index.js CHANGED
@@ -36,9 +36,11 @@ __export(index_exports, {
36
36
  AbstractRunner: () => AbstractRunner,
37
37
  AbstractTerminalSession: () => AbstractTerminalSession,
38
38
  ApprovalPolicy: () => ApprovalPolicy,
39
+ BUILTIN_SKILL_NAMES: () => BUILTIN_SKILL_NAMES,
39
40
  BaseAiService: () => BaseAiService,
40
41
  BaseConfig: () => BaseConfig,
41
42
  BaseFileSystem: () => BaseFileSystem,
43
+ CHECKPOINT_TRUNCATE_LENGTH: () => CHECKPOINT_TRUNCATE_LENGTH,
42
44
  CLAUDE_CODE_MANIFEST: () => CLAUDE_CODE_MANIFEST,
43
45
  CLI_PROVIDERS: () => CLI_PROVIDERS,
44
46
  CMD_EXE_MAX_COMMAND_LINE: () => CMD_EXE_MAX_COMMAND_LINE,
@@ -94,6 +96,7 @@ __export(index_exports, {
94
96
  SYMBOL_LANGUAGES: () => SYMBOL_LANGUAGES,
95
97
  Session: () => Session,
96
98
  SettingsService: () => SettingsService,
99
+ SkillsService: () => SkillsService,
97
100
  StdioAgentAdapter: () => StdioAgentAdapter,
98
101
  TASK_OPS_ENVELOPE_KEY: () => TASK_OPS_ENVELOPE_KEY,
99
102
  TASK_QUERY_ANSWER_OR_OPS: () => TASK_QUERY_ANSWER_OR_OPS,
@@ -159,6 +162,7 @@ __export(index_exports, {
159
162
  configuredProviders: () => configuredProviders,
160
163
  createAiService: () => createAiService,
161
164
  createEmptyPlan: () => createEmptyPlan,
165
+ createSkillsService: () => createSkillsService,
162
166
  createTask: () => createTask,
163
167
  daemonTokenPath: () => daemonTokenPath,
164
168
  defaultLogger: () => defaultLogger,
@@ -185,12 +189,14 @@ __export(index_exports, {
185
189
  filterModelsForPrompt: () => filterModelsForPrompt,
186
190
  filteredBuildModes: () => filteredBuildModes,
187
191
  flattenTasks: () => flattenTasks,
192
+ flattenTasksWithParents: () => flattenTasksWithParents,
188
193
  formatSearchOutput: () => formatSearchOutput,
189
194
  generatePlanWithRepair: () => generatePlanWithRepair,
190
195
  getLatestSession: () => getLatestSession,
191
196
  getProviderMeta: () => getProviderMeta,
192
197
  getSettingsPath: () => getSettingsPath,
193
198
  getStateDir: () => getStateDir,
199
+ globalDataDir: () => globalDataDir,
194
200
  grantScopeFor: () => grantScopeFor,
195
201
  hasTmux: () => hasTmux,
196
202
  includeGlobFor: () => includeGlobFor,
@@ -210,6 +216,7 @@ __export(index_exports, {
210
216
  looksLikePlanAttempt: () => looksLikePlanAttempt,
211
217
  mapAgentTool: () => mapAgentTool,
212
218
  migrateLegacyPlan: () => migrateLegacyPlan,
219
+ migrateOldConfigDir: () => migrateOldConfigDir,
213
220
  migratePlanState: () => migratePlanState,
214
221
  migrateTask: () => migrateTask,
215
222
  mintDaemonToken: () => mintDaemonToken,
@@ -244,10 +251,12 @@ __export(index_exports, {
244
251
  resolveArgs: () => resolveArgs,
245
252
  resolveDefaultMode: () => resolveDefaultMode,
246
253
  resolveModelShortcut: () => resolveModelShortcut,
254
+ resolveOrderLabel: () => resolveOrderLabel,
247
255
  resolvePluginInstallDir: () => resolvePluginInstallDir,
248
256
  resolveProvider: () => resolveProvider,
249
257
  resolveProviderFromPrefix: () => resolveProviderFromPrefix,
250
258
  resolveResearchShell: () => resolveResearchShell,
259
+ resolveSkillInvocation: () => resolveSkillInvocation,
251
260
  resolveTaskMode: () => resolveTaskMode,
252
261
  resolveWithin: () => resolveWithin,
253
262
  retargetTaskRunner: () => retargetTaskRunner,
@@ -270,6 +279,7 @@ __export(index_exports, {
270
279
  summarizeOutput: () => summarizeOutput,
271
280
  summarizeToolCall: () => summarizeToolCall,
272
281
  taskOpsRejectedPrompt: () => taskOpsRejectedPrompt,
282
+ taskOrderLabel: () => taskOrderLabel,
273
283
  taskQuerySignature: () => taskQuerySignature,
274
284
  textHasTaskOps: () => textHasTaskOps,
275
285
  textHasTaskQuery: () => textHasTaskQuery,
@@ -279,6 +289,7 @@ __export(index_exports, {
279
289
  toOrchestratorOptions: () => toOrchestratorOptions,
280
290
  tokenSubprotocols: () => tokenSubprotocols,
281
291
  tokensMatch: () => tokensMatch,
292
+ truncateCheckpointSummary: () => truncateCheckpointSummary,
282
293
  truncatedPlanReEmitPrompt: () => truncatedPlanReEmitPrompt,
283
294
  updateTaskInPlan: () => updateTaskInPlan,
284
295
  validateModifiedPlan: () => validateModifiedPlan,
@@ -293,6 +304,22 @@ module.exports = __toCommonJS(index_exports);
293
304
 
294
305
  // src/models/Task.ts
295
306
  var import_uuid = require("uuid");
307
+
308
+ // src/order-labels.ts
309
+ function taskOrderLabel(task, parent) {
310
+ return parent ? `${parent.order}.${task.order}` : String(task.order);
311
+ }
312
+ function resolveOrderLabel(tasks, label) {
313
+ const parts = label.split(".");
314
+ if (parts.length > 2 || parts.some((p) => !/^\d+$/.test(p))) return void 0;
315
+ const [topOrder, childOrder] = parts.map(Number);
316
+ const top = tasks.find((t) => t.order === topOrder);
317
+ if (!top) return void 0;
318
+ if (childOrder === void 0) return top;
319
+ return top.subtasks.find((s) => s.order === childOrder);
320
+ }
321
+
322
+ // src/models/Task.ts
296
323
  function emptyWarnings() {
297
324
  return { deletedCompleted: [], changedCompleted: [], deletedInProgress: [], modifiedInProgress: [], brokenDependencies: [] };
298
325
  }
@@ -477,6 +504,15 @@ function createEmptyPlan() {
477
504
  function flattenTasks(tasks) {
478
505
  return tasks.flatMap((t) => [t, ...flattenTasks(t.subtasks ?? [])]);
479
506
  }
507
+ function flattenTasksWithParents(tasks) {
508
+ const rows = [];
509
+ const walk = (t, parent) => {
510
+ rows.push({ task: t, parent });
511
+ for (const sub of t.subtasks ?? []) walk(sub, t);
512
+ };
513
+ for (const t of tasks) walk(t, null);
514
+ return rows;
515
+ }
480
516
  function migrateTask(task) {
481
517
  if (!task.assignedModel) {
482
518
  task.assignedModel = void 0;
@@ -3103,7 +3139,7 @@ function redactSecrets(text) {
3103
3139
  const { value, tail } = trimTrailingPunctuation(raw);
3104
3140
  if (value.length < 8) return full;
3105
3141
  if (looksLikeCode(value) || value.startsWith("$")) return full;
3106
- if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
3142
+ if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && (/\d/.test(value) || value.length >= 12)) {
3107
3143
  return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
3108
3144
  }
3109
3145
  if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
@@ -3142,33 +3178,33 @@ function grepOptions(args) {
3142
3178
  };
3143
3179
  }
3144
3180
  var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
3145
- async function executeTool(name, args, fs10, fetcher, signal) {
3146
- const outcome = await runTool(name, args, fs10, fetcher, signal);
3181
+ async function executeTool(name, args, fs12, fetcher, signal) {
3182
+ const outcome = await runTool(name, args, fs12, fetcher, signal);
3147
3183
  return { ...outcome, output: redactSecrets(outcome.output) };
3148
3184
  }
3149
- async function runTool(name, args, fs10, fetcher, signal) {
3185
+ async function runTool(name, args, fs12, fetcher, signal) {
3150
3186
  if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
3151
3187
  switch (name) {
3152
3188
  case "read_file": {
3153
3189
  const offset = num(args.offset);
3154
3190
  const limit = num(args.limit);
3155
3191
  const maxBytes = num(args.maxBytes);
3156
- return fs10.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
3192
+ return fs12.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
3157
3193
  }
3158
3194
  case "read_files": {
3159
3195
  const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
3160
- return fs10.readFiles(paths);
3196
+ return fs12.readFiles(paths);
3161
3197
  }
3162
3198
  case "glob":
3163
- return fs10.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
3199
+ return fs12.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
3164
3200
  case "grep":
3165
- return fs10.grep(String(args.pattern ?? ""), grepOptions(args));
3201
+ return fs12.grep(String(args.pattern ?? ""), grepOptions(args));
3166
3202
  case "find_symbol":
3167
- return fs10.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
3203
+ return fs12.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
3168
3204
  case "list_dir":
3169
- return fs10.listDir(String(args.path ?? ""), num(args.depth));
3205
+ return fs12.listDir(String(args.path ?? ""), num(args.depth));
3170
3206
  case "bash":
3171
- return fs10.bash(String(args.command ?? ""), signal);
3207
+ return fs12.bash(String(args.command ?? ""), signal);
3172
3208
  case "fetch": {
3173
3209
  if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
3174
3210
  const url = String(args.url ?? "");
@@ -4320,14 +4356,7 @@ function resolveTaskMode(emittedMode, runnerId, runnerModes, autonomousDefault =
4320
4356
 
4321
4357
  // src/services/plannerModes.ts
4322
4358
  var MODE_TOGGLES = [
4323
- // Both of these interview the user, and a one-shot run has nobody to ask —
4324
- // its prompt says so outright. Their absence there is a decision, not a gap.
4325
- { id: "grill-me", settingsKey: "grillMe", runtimeKey: "grillMeEnabled", scopes: ["chat"] },
4326
- { id: "prd", settingsKey: "prd", runtimeKey: "prdEnabled", scopes: ["chat"] },
4327
- // Structural: append a final review task. Nothing about it needs a dialogue.
4328
- { id: "review", settingsKey: "review", runtimeKey: "reviewEnabled", scopes: ["chat", "one-shot"] },
4329
4359
  { id: "verify", settingsKey: "verification", runtimeKey: "verificationEnabled", scopes: ["chat", "one-shot"] },
4330
- { id: "research-subagents", settingsKey: "researchSubagents", runtimeKey: "researchSubagentsEnabled", scopes: ["chat", "one-shot"] },
4331
4360
  { id: "tdd", settingsKey: "tdd", runtimeKey: "tddEnabled", scopes: ["task"] }
4332
4361
  ];
4333
4362
  function plannerRuntimeToggles(settings) {
@@ -4337,28 +4366,16 @@ function plannerRuntimeToggles(settings) {
4337
4366
  }
4338
4367
  var DEFAULT_PLANNER_MODES = {
4339
4368
  autonomousDefault: true,
4340
- grillMe: false,
4341
- prd: false,
4342
- review: false,
4343
- verification: false,
4344
- researchSubagents: false
4369
+ verification: false
4345
4370
  };
4346
4371
  function plannerModesFrom(settings, autonomousDefault) {
4347
4372
  return {
4348
4373
  autonomousDefault,
4349
- grillMe: settings.grillMeEnabled ?? false,
4350
- prd: settings.prdEnabled ?? false,
4351
- review: settings.reviewEnabled ?? false,
4352
- verification: settings.verificationEnabled ?? false,
4353
- researchSubagents: settings.researchSubagentsEnabled ?? false
4374
+ verification: settings.verificationEnabled ?? false
4354
4375
  };
4355
4376
  }
4356
4377
  var MODE_FIELD = {
4357
- "grill-me": "grillMe",
4358
- prd: "prd",
4359
- review: "review",
4360
4378
  verify: "verification",
4361
- "research-subagents": "researchSubagents",
4362
4379
  tdd: null
4363
4380
  };
4364
4381
  function modesFor(scope, modes) {
@@ -4686,7 +4703,7 @@ function renderTaskQueryAnswer(query, tasks, catalog) {
4686
4703
  }
4687
4704
 
4688
4705
  // src/services/PlanPrompts.ts
4689
- function buildResearchToolsPrompt(subagentsEnabled = false) {
4706
+ function buildResearchToolsPrompt() {
4690
4707
  const lines = [
4691
4708
  "You have access to the following tools to explore the workspace:",
4692
4709
  "",
@@ -4702,15 +4719,11 @@ function buildResearchToolsPrompt(subagentsEnabled = false) {
4702
4719
  "- glob: for finding files by name/path pattern",
4703
4720
  "- grep: for searching inside file contents",
4704
4721
  "",
4705
- "When multiple independent tool calls are needed, batch them in a single response to save round-trips. For example, if you need to read README.md and AGENTS.md, call read_files with both paths at once."
4722
+ "When multiple independent tool calls are needed, batch them in a single response to save round-trips. For example, if you need to read README.md and AGENTS.md, call read_files with both paths at once.",
4723
+ "",
4724
+ 'spawn_research_agent(prompt: string) - Launch a stateless read-only research agent that explores the workspace with its own tools and returns a digest. Use it to delegate an open-ended exploration thread \u2014 a subsystem to map, or a "how does X work here" question \u2014 whenever one emerges during research. To explore several independent areas, launch the agents CONCURRENTLY: put multiple spawn_research_agent calls in one reply and they run in parallel.',
4725
+ "The agent sees nothing of this conversation, so its prompt must be self-contained: the area or question, the paths/symbols to start from, and exactly what the digest must report back. Do NOT use it on small or single-subsystem repos, or to read one known file \u2014 direct tools are faster there."
4706
4726
  ];
4707
- if (subagentsEnabled) {
4708
- lines.push(
4709
- "",
4710
- 'spawn_research_agent(prompt: string) - Launch a stateless read-only research agent that explores the workspace with its own tools and returns a digest. Use it to delegate an open-ended exploration thread \u2014 a subsystem to map, or a "how does X work here" question \u2014 whenever one emerges during research. To explore several independent areas, launch the agents CONCURRENTLY: put multiple spawn_research_agent calls in one reply and they run in parallel.',
4711
- "The agent sees nothing of this conversation, so its prompt must be self-contained: the area or question, the paths/symbols to start from, and exactly what the digest must report back. Do NOT use it on small or single-subsystem repos, or to read one known file \u2014 direct tools are faster there."
4712
- );
4713
- }
4714
4727
  return lines.join("\n");
4715
4728
  }
4716
4729
  function buildSubagentSystemPrompt() {
@@ -4748,11 +4761,11 @@ function buildModeExamplesForRunners(runners) {
4748
4761
  return `${r}: default|plan`;
4749
4762
  }).join(", ");
4750
4763
  }
4751
- function verificationModeBlock(reviewAlsoEnabled = false) {
4764
+ function verificationModeBlock() {
4752
4765
  return [
4753
4766
  "",
4754
4767
  "VERIFICATION MODE:",
4755
- reviewAlsoEnabled ? "Add a FINAL verification task immediately before the review task. The review task keeps the highest order number and must list the verification task among its dependencies." : "Add a FINAL verification task to the end of the plan (highest order number).",
4768
+ "Add a FINAL verification task to the end of the plan (highest order number).",
4756
4769
  'This task closes the gap between "every task finished" and "the feature is correct": tasks executed in isolated sessions can each pass while the integrated feature is still short. Its outcome must come from commands and exit codes, never from judgement.',
4757
4770
  "This task must:",
4758
4771
  '- Have type "ai" and autonomy "AFK" \u2014 it needs no human input',
@@ -4788,35 +4801,14 @@ function researchPhaseBlock(harnessMode) {
4788
4801
  "- Research and questions can interleave \u2014 you can explore a file, then ask a question grounded in it, in one response"
4789
4802
  ].join("\n");
4790
4803
  }
4791
- function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, grillMeEnabled = false, prdEnabled = false, reviewEnabled = false, verificationEnabled = false, harnessMode = false) {
4804
+ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, verificationEnabled = false, harnessMode = false) {
4792
4805
  const modelsJson = modelsJsonFor(modelsByRunner, runners);
4793
4806
  const modeGuide = runnerModes ? buildModeGuide(runnerModes, autonomousDefault) : buildModeGuideForRunners(runners, autonomousDefault);
4794
4807
  const modeExamples = runnerModes ? Object.entries(runnerModes).map(([r, ms]) => {
4795
4808
  const filtered = filteredBuildModes(ms, autonomousDefault);
4796
4809
  return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
4797
4810
  }).join(", ") : buildModeExamplesForRunners(runners);
4798
- const grillMeBlock = grillMeEnabled ? [
4799
- "",
4800
- "INTERVIEW MODE \u2014 GRILL-ME:",
4801
- "Interview the user relentlessly about every aspect of their goal until you reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one.",
4802
- "Ask one question at a time, waiting for the user's answer before continuing. For each question, propose your recommended answer so they can confirm or correct it. Asking multiple questions at once is bewildering.",
4803
- "If a fact can be found by exploring the codebase, look it up instead of asking. The decisions, though, are the user's \u2014 put each one to them and wait.",
4804
- "Only propose an outline after the user has answered enough questions that you genuinely understand the goal. Then present the prose outline and wait for explicit confirmation before emitting the task plan JSON."
4805
- ].join("\n") : "";
4806
- const prdBlock = prdEnabled ? [
4807
- "",
4808
- "PRD MODE: Before producing the outline, generate a PRD in two steps.",
4809
- "Step 1 \u2014 Propose a short prose preview containing: the problem statement, the proposed approach, the testing seams, and the main risks. Prefer existing seams over new ones; propose new seams at the highest point possible. The fewer seams across the codebase, the better \u2014 the ideal number is one. Propose a feature-slug for saving the PRD (e.g. `my-feature`). Ask the user whether they agree.",
4810
- "Step 2 \u2014 Only after the user explicitly agrees, write the full markdown PRD using the standard PRD format (problem, user stories, implementation decisions, testing seams, out of scope). The user may edit the proposed feature-slug before you write the PRD; use the agreed slug in the final output.",
4811
- 'PRD content rules: user stories are an extensive numbered list, each "As an <actor>, I want <feature>, so that <benefit>". Record implementation decisions as decisions (modules, interfaces, schema changes, API contracts) \u2014 do NOT include specific file paths or code snippets; they go stale fast. Exception: a snippet from a prototype that encodes a decision more precisely than prose (state machine, reducer, schema, type shape) may be inlined, trimmed to the decision-rich parts.',
4812
- "When writing the full PRD, wrap it EXACTLY with these markers so the system can save it automatically:",
4813
- '<!-- ORDEWELL_PRD_START slug="<feature-slug>" -->',
4814
- "... full markdown PRD ...",
4815
- "<!-- ORDEWELL_PRD_END -->",
4816
- "Do NOT emit the full PRD before the user agrees to the preview. Do NOT emit task plan JSON before the PRD is agreed and the outline is confirmed."
4817
- ].join("\n") : "";
4818
- const reviewBlock = reviewEnabled ? reviewModeBlock() : "";
4819
- const verificationBlock = verificationEnabled ? verificationModeBlock(reviewEnabled) : "";
4811
+ const verificationBlock = verificationEnabled ? verificationModeBlock() : "";
4820
4812
  return buildConversationBody(
4821
4813
  goal,
4822
4814
  context,
@@ -4825,34 +4817,10 @@ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, r
4825
4817
  modeGuide,
4826
4818
  modeExamples,
4827
4819
  harnessMode,
4828
- grillMeBlock,
4829
- prdBlock,
4830
- reviewBlock,
4831
4820
  verificationBlock
4832
4821
  );
4833
4822
  }
4834
- function reviewModeBlock() {
4835
- return [
4836
- "",
4837
- "REVIEW MODE:",
4838
- "Add a FINAL review task to the end of the plan (highest order number).",
4839
- "This task must:",
4840
- '- Have type "ai" and use the STRONGEST available model',
4841
- "- Have dependencies on ALL other AI/user tasks in the plan",
4842
- "- Be assigned appropriately as HITL (human review) since the results need human sign-off",
4843
- "- Its prompt should instruct the agent to:",
4844
- " * Review every completed task's outputs and verify the original goal was met",
4845
- " * Check that each task delivers what its description promised",
4846
- " * Review the work on two axes, reported SEPARATELY so one cannot mask the other:",
4847
- " - SPEC axis: if a PRD exists for this plan (`.scratch/<slug>/PRD.md`), verify against it: (a) requirements asked for but missing or partial; (b) behaviour delivered that the PRD never asked for (scope creep); (c) requirements that look implemented but wrongly. Quote the PRD line for each finding. With no PRD, review against the original goal.",
4848
- " - STANDARDS axis: check the diff against the repo's documented coding standards (CONTRIBUTING.md, CLAUDE.md, etc.) plus this smell baseline: Mysterious Name (rename), Duplicated Code (extract shared shape), Feature Envy (move method to the data), Data Clumps (bundle into a type), Primitive Obsession (give the concept a type), Repeated Switches (polymorphism or shared map), Shotgun Surgery (gather into one module), Divergent Change (split by reason to change), Speculative Generality (delete it), Message Chains (hide the walk), Middle Man (call the target direct), Refused Bequest (composition over inheritance). Smell findings are always judgement calls, never hard failures; documented repo standards override the baseline; skip anything tooling already enforces.",
4849
- " * Produce a structured review report listing what was checked and whether each check passed",
4850
- " * Flag any gaps, bugs, or incomplete work",
4851
- " * Output a final PASS/FAIL conclusion with evidence (standards-axis judgement calls alone must not fail the verification)",
4852
- "- Use the same runner as the other tasks"
4853
- ].join("\n");
4854
- }
4855
- function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, grillMeBlock, prdBlock, reviewBlock, verificationBlock) {
4823
+ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, verificationBlock) {
4856
4824
  return [
4857
4825
  "You are Ordewell's project planner. You explore the codebase, ask concise clarifying questions grounded in your findings, and produce structured task plans as JSON. Be direct: avoid unnecessary preamble, summaries, or explanations unless the user asks for detail.",
4858
4826
  "",
@@ -4863,9 +4831,6 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
4863
4831
  '4. After the user confirms the outline, emit the final task plan as a JSON object with a "tasks" array.',
4864
4832
  "",
4865
4833
  researchPhaseBlock(harnessMode),
4866
- grillMeBlock,
4867
- prdBlock,
4868
- reviewBlock,
4869
4834
  verificationBlock,
4870
4835
  "",
4871
4836
  "OUTLINE PHASE:",
@@ -5052,7 +5017,6 @@ function getPlanTemplate() {
5052
5017
  "",
5053
5018
  "TASK MODE:",
5054
5019
  "{{MODE_GUIDE}}",
5055
- "{{REVIEW_BLOCK}}",
5056
5020
  "{{VERIFICATION_BLOCK}}",
5057
5021
  "",
5058
5022
  "CONTEXT:",
@@ -5099,10 +5063,8 @@ function buildPlanPromptBase(userGoal, context, researchResults, modelsByRunner,
5099
5063
  const filtered = filteredBuildModes(ms, autonomousDefault);
5100
5064
  return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
5101
5065
  }).join(", ") : buildModeExamplesForRunners(runners);
5102
- return template.replace("{{RESEARCH_SECTION}}", researchReplacement).replace("{{CLARIFICATION_INSTRUCTIONS}}", ONE_SHOT_INSTRUCTIONS).replace("{{MODELS_JSON}}", modelsJson).replace("{{RUNNER_CHOICES}}", runners.join("|")).replace("{{RUNNER_INSTRUCTION}}", runnerInstruction(runners)).replace("{{MODE_GUIDE}}", modeGuide).replace("{{REVIEW_BLOCK}}", scoped.review ? `
5103
- ${reviewModeBlock()}
5104
- ` : "").replace("{{VERIFICATION_BLOCK}}", scoped.verification ? `
5105
- ${verificationModeBlock(scoped.review)}
5066
+ return template.replace("{{RESEARCH_SECTION}}", researchReplacement).replace("{{CLARIFICATION_INSTRUCTIONS}}", ONE_SHOT_INSTRUCTIONS).replace("{{MODELS_JSON}}", modelsJson).replace("{{RUNNER_CHOICES}}", runners.join("|")).replace("{{RUNNER_INSTRUCTION}}", runnerInstruction(runners)).replace("{{MODE_GUIDE}}", modeGuide).replace("{{VERIFICATION_BLOCK}}", scoped.verification ? `
5067
+ ${verificationModeBlock()}
5106
5068
  ` : "").replace("{{MODE_EXAMPLES}}", modeExamples).replace("{{CONTEXT}}", context || "(no additional context)").replace("{{RESEARCH_RESULTS}}", researchResults ? `
5107
5069
 
5108
5070
  RESEARCH FINDINGS:
@@ -6465,8 +6427,8 @@ var SUBAGENT_EXCLUDED = /* @__PURE__ */ new Set(["fetch", "web_search"]);
6465
6427
  function subagentToolSpecs() {
6466
6428
  return RESEARCH_TOOLS.filter((t) => !SUBAGENT_EXCLUDED.has(t.name));
6467
6429
  }
6468
- function withSpawn(includeSubagents) {
6469
- return includeSubagents ? [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC] : RESEARCH_TOOLS;
6430
+ function withSpawn() {
6431
+ return [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC];
6470
6432
  }
6471
6433
  function projectOpenAi(specs) {
6472
6434
  return specs.map((t) => ({
@@ -6487,8 +6449,8 @@ function projectOpenAi(specs) {
6487
6449
  }
6488
6450
  }));
6489
6451
  }
6490
- function toOpenAiTools(includeSubagents = false) {
6491
- return projectOpenAi(withSpawn(includeSubagents));
6452
+ function toOpenAiTools() {
6453
+ return projectOpenAi(withSpawn());
6492
6454
  }
6493
6455
  function toOpenAiSubagentTools() {
6494
6456
  return projectOpenAi(subagentToolSpecs());
@@ -6499,8 +6461,8 @@ var SCHEMA_TYPE = {
6499
6461
  array: import_generative_ai.SchemaType.ARRAY,
6500
6462
  boolean: import_generative_ai.SchemaType.BOOLEAN
6501
6463
  };
6502
- function toGeminiToolDeclarations(includeSubagents = false) {
6503
- return withSpawn(includeSubagents).map((t) => {
6464
+ function toGeminiToolDeclarations() {
6465
+ return withSpawn().map((t) => {
6504
6466
  const properties = {};
6505
6467
  for (const [key, param] of Object.entries(t.properties)) {
6506
6468
  properties[key] = {
@@ -6597,33 +6559,10 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
6597
6559
  return (blocks[0].lines.length === 0 ? texts.slice(1) : texts).join("\n");
6598
6560
  }
6599
6561
 
6600
- // src/utils/prdStore.ts
6601
- var fs = __toESM(require("fs"));
6602
- var path4 = __toESM(require("path"));
6603
- var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
6604
- function extractPrdBlock(text) {
6605
- const match = PRD_BLOCK_RE.exec(text);
6606
- if (!match) return null;
6607
- const slug2 = sanitizeSlug(match[1]);
6608
- const markdown = match[2].trim();
6609
- if (!slug2 || !markdown) return null;
6610
- return { slug: slug2, markdown };
6611
- }
6612
- function sanitizeSlug(raw) {
6613
- return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
6614
- }
6615
- function savePrdMarkdown(workspace, slug2, markdown) {
6616
- const dir = path4.join(workspace, ".scratch", sanitizeSlug(slug2));
6617
- fs.mkdirSync(dir, { recursive: true });
6618
- const file = path4.join(dir, "PRD.md");
6619
- fs.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
6620
- return file;
6621
- }
6622
-
6623
6562
  // src/services/ContextCollector.ts
6624
6563
  var ORDEWELL_CONTEXT_MAX = 8e3;
6625
- async function collectResearchContext(fs10, runners) {
6626
- const collector = new ContextCollector(fs10);
6564
+ async function collectResearchContext(fs12, runners) {
6565
+ const collector = new ContextCollector(fs12);
6627
6566
  let contextStr = "";
6628
6567
  let firstContext = null;
6629
6568
  for (const r of runners) {
@@ -6650,8 +6589,8 @@ ${firstContext.dirStructure}
6650
6589
  return contextStr;
6651
6590
  }
6652
6591
  var ContextCollector = class {
6653
- constructor(fs10, registry) {
6654
- this.fs = fs10;
6592
+ constructor(fs12, registry) {
6593
+ this.fs = fs12;
6655
6594
  this.registry = registry;
6656
6595
  }
6657
6596
  fs;
@@ -6732,9 +6671,9 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
6732
6671
  const scope = args.include ? ` in ${args.include}` : "";
6733
6672
  return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
6734
6673
  }
6735
- const path17 = args.filePath || args.file_path || args.path;
6736
- if (path17) {
6737
- const basename = String(path17).split("/").pop() || String(path17);
6674
+ const path20 = args.filePath || args.file_path || args.path;
6675
+ if (path20) {
6676
+ const basename = String(path20).split("/").pop() || String(path20);
6738
6677
  return `${display} ${basename}`;
6739
6678
  }
6740
6679
  if (args.pattern) {
@@ -6768,22 +6707,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
6768
6707
  function refused(output) {
6769
6708
  return { success: false, output, truncated: false };
6770
6709
  }
6771
- function nonPromptingFs(fs10) {
6772
- const withinWorkspace = (p) => resolveWithin(fs10.getWorkspaceRoot(), p ?? ".").inside;
6710
+ function nonPromptingFs(fs12) {
6711
+ const withinWorkspace = (p) => resolveWithin(fs12.getWorkspaceRoot(), p ?? ".").inside;
6773
6712
  const outsideRefusal = (p) => refused(
6774
6713
  `Path "${p}" is outside the workspace. Research agents are confined to the workspace root \u2014 note the gap in your digest and let the planner request it directly.`
6775
6714
  );
6776
6715
  return {
6777
- getWorkspaceRoot: () => fs10.getWorkspaceRoot(),
6778
- readFile: (p, opts) => withinWorkspace(p) ? fs10.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
6716
+ getWorkspaceRoot: () => fs12.getWorkspaceRoot(),
6717
+ readFile: (p, opts) => withinWorkspace(p) ? fs12.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
6779
6718
  readFiles: (paths) => {
6780
6719
  const escaping = paths.find((p) => !withinWorkspace(p));
6781
- return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs10.readFiles(paths);
6720
+ return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs12.readFiles(paths);
6782
6721
  },
6783
- glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs10.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6784
- grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs10.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6785
- findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs10.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
6786
- listDir: (p, depth) => withinWorkspace(p) ? fs10.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
6722
+ glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6723
+ grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6724
+ findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs12.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
6725
+ listDir: (p, depth) => withinWorkspace(p) ? fs12.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
6787
6726
  bash: async (command, signal) => {
6788
6727
  const dialect = resolveResearchShell().dialect;
6789
6728
  const { tier, reason } = classifyCommand(command, { dialect });
@@ -6794,13 +6733,13 @@ function nonPromptingFs(fs10) {
6794
6733
  }
6795
6734
  const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
6796
6735
  if (escaping) return outsideRefusal(escaping);
6797
- return fs10.bash(command, signal);
6736
+ return fs12.bash(command, signal);
6798
6737
  }
6799
6738
  };
6800
6739
  }
6801
6740
  async function runLoop(prompt, deps) {
6802
6741
  if (deps.signal?.aborted) return "[research agent aborted before starting]";
6803
- const fs10 = nonPromptingFs(deps.fs);
6742
+ const fs12 = nonPromptingFs(deps.fs);
6804
6743
  const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
6805
6744
  let turn = await chat.sendMessage(prompt, deps.signal);
6806
6745
  for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
@@ -6817,7 +6756,7 @@ ${turn.text}`;
6817
6756
  if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
6818
6757
  const toolArgs = JSON.stringify(tc.args);
6819
6758
  deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
6820
- const res = await executeTool(tc.name, args, fs10, void 0, deps.signal);
6759
+ const res = await executeTool(tc.name, args, fs12, void 0, deps.signal);
6821
6760
  const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
6822
6761
  [... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
6823
6762
  const stepEntry = {
@@ -6875,8 +6814,6 @@ var BaseAiService = class _BaseAiService {
6875
6814
  config;
6876
6815
  conversation = null;
6877
6816
  activeAbort = null;
6878
- /** researchSubagents toggle (issue #34): set per operation from the live settings snapshot; off means bit-for-bit sequential behavior. */
6879
- researchSubagentsEnabled = false;
6880
6817
  hasActiveConversation() {
6881
6818
  return this.conversation !== null;
6882
6819
  }
@@ -6932,10 +6869,7 @@ var BaseAiService = class _BaseAiService {
6932
6869
  * steering/failed result and the loop continues sequentially: a subagent can
6933
6870
  * never fail a plan or a turn.
6934
6871
  */
6935
- async executeSpawnAgent(args, fs10, onProgress, signal, subagentId) {
6936
- if (!this.researchSubagentsEnabled) {
6937
- return { success: false, output: "spawn_research_agent is not available in this session. Continue researching sequentially with your other tools (read_file, glob, grep, list_dir, bash).", truncated: false };
6938
- }
6872
+ async executeSpawnAgent(args, fs12, onProgress, signal, subagentId) {
6939
6873
  const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
6940
6874
  if (!prompt) {
6941
6875
  return { success: false, output: 'spawn_research_agent requires a non-empty "prompt" string: a self-contained task for the agent, including what its digest must report back.', truncated: false };
@@ -6946,20 +6880,20 @@ var BaseAiService = class _BaseAiService {
6946
6880
  if (!chat) throw new Error("subagent chats are not available for this provider");
6947
6881
  return chat;
6948
6882
  },
6949
- fs: fs10,
6883
+ fs: fs12,
6950
6884
  signal,
6951
6885
  onProgress: (progress) => onProgress({ ...progress, subagentId })
6952
6886
  });
6953
6887
  }
6954
6888
  /** Collect project context for the planning phase. Shared with the harness backend. */
6955
- static async collectResearchContext(fs10, runners) {
6956
- return collectResearchContext(fs10, runners);
6889
+ static async collectResearchContext(fs12, runners) {
6890
+ return collectResearchContext(fs12, runners);
6957
6891
  }
6958
6892
  /**
6959
6893
  * Execute one round of tool calls and report each through onProgress.
6960
6894
  * Returns the results to feed back plus the log entries produced.
6961
6895
  */
6962
- async executeToolCalls(toolCalls, fs10, onProgress, fetcher, stepIndex, thinking, signal) {
6896
+ async executeToolCalls(toolCalls, fs12, onProgress, fetcher, stepIndex, thinking, signal) {
6963
6897
  const toolResults = [];
6964
6898
  const logEntries = [];
6965
6899
  let resultsText = "";
@@ -6978,18 +6912,18 @@ var BaseAiService = class _BaseAiService {
6978
6912
  const subagentId = (0, import_uuid3.v4)();
6979
6913
  spawnSubagentIds.set(i, subagentId);
6980
6914
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
6981
- precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs10, onProgress, signal, subagentId));
6915
+ precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs12, onProgress, signal, subagentId));
6982
6916
  }) : Promise.resolve(),
6983
6917
  parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
6984
6918
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
6985
- precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs10, fetcher, signal));
6919
+ precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs12, fetcher, signal));
6986
6920
  }) : Promise.resolve()
6987
6921
  ]);
6988
6922
  for (const [index, tc] of toolCalls.entries()) {
6989
6923
  const toolArgs = normalizedArgs[index];
6990
6924
  const preResult = precomputed.get(index);
6991
6925
  if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
6992
- const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs10, fetcher, signal);
6926
+ const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs12, fetcher, signal);
6993
6927
  const MAX_RESPONSE_CHARS = 5e4;
6994
6928
  const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
6995
6929
  const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
@@ -7121,7 +7055,6 @@ ${llmOutput}
7121
7055
  researchLog
7122
7056
  };
7123
7057
  }
7124
- if (extractPrdBlock(turn.text)) ctx.prdCaptured = true;
7125
7058
  const reply = classifyPlannerReply(turn.text, {
7126
7059
  runners: ctx.runners,
7127
7060
  runnerModes: ctx.runnerModes,
@@ -7135,16 +7068,6 @@ ${llmOutput}
7135
7068
  case "task_query":
7136
7069
  return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
7137
7070
  case "plan":
7138
- if (ctx.prdEnabled && !ctx.prdCaptured && !ctx.prdNudgeSent && !signal?.aborted) {
7139
- ctx.prdNudgeSent = true;
7140
- pending = [
7141
- "PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
7142
- "In your next reply: first output the full markdown PRD wrapped EXACTLY in",
7143
- '<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
7144
- "then re-emit the exact same task plan JSON after the PRD block."
7145
- ].join(" ");
7146
- continue;
7147
- }
7148
7071
  return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
7149
7072
  // Botched attempts get a bounded corrective retry — otherwise the
7150
7073
  // broken JSON would surface as a prose bubble and the edit or plan
@@ -7186,7 +7109,7 @@ ${llmOutput}
7186
7109
  * parsed tasks if a plan was emitted mid-loop, or null if the loop
7187
7110
  * exhausted without a valid plan.
7188
7111
  */
7189
- async runResearchLoop(chat, firstMessage, fs10, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
7112
+ async runResearchLoop(chat, firstMessage, fs12, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
7190
7113
  const researchLog = initialLog ? [...initialLog] : [];
7191
7114
  if (!initialLog && userGoal) {
7192
7115
  researchLog.push({
@@ -7213,7 +7136,7 @@ ${llmOutput}
7213
7136
  if (!turn.hasToolCalls) break;
7214
7137
  const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
7215
7138
  turn.toolCalls,
7216
- fs10,
7139
+ fs12,
7217
7140
  onProgress,
7218
7141
  fetcher,
7219
7142
  step,
@@ -7349,9 +7272,6 @@ var GeminiService = class extends BaseAiService {
7349
7272
  req.runners,
7350
7273
  req.runnerModes,
7351
7274
  req.autonomousDefault ?? true,
7352
- req.grillMeEnabled ?? false,
7353
- req.prdEnabled ?? false,
7354
- req.reviewEnabled ?? false,
7355
7275
  req.verificationEnabled ?? false
7356
7276
  );
7357
7277
  const seededHistory = [
@@ -7390,8 +7310,7 @@ ${m.content}` });
7390
7310
  runners: req.runners,
7391
7311
  runnerModes: req.runnerModes,
7392
7312
  autonomousDefault: req.autonomousDefault,
7393
- fetcher: req.fetcher,
7394
- prdEnabled: req.prdEnabled ?? false
7313
+ fetcher: req.fetcher
7395
7314
  };
7396
7315
  this.conversation = { ctx, setProgress: (onProgress) => {
7397
7316
  currentProgress = onProgress;
@@ -7449,11 +7368,11 @@ ${repairHint}` }] : parts;
7449
7368
  return this.consumePlanStream(result, onToken);
7450
7369
  }
7451
7370
  // --- IAiService implementation ---
7452
- async researchAndPlan(userDescription, runners, modelsByRunner, fs10, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7371
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7453
7372
  this.ensureInit();
7454
7373
  const { autonomousDefault } = modes;
7455
7374
  const genModel = this.getPlanningModel();
7456
- const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
7375
+ const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
7457
7376
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
7458
7377
  const firstMessage = `${buildResearchToolsPrompt()}
7459
7378
 
@@ -7464,17 +7383,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
7464
7383
  generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
7465
7384
  });
7466
7385
  const researchChat = new GeminiResearchChat(chat);
7467
- const result = await this.runResearchLoop(researchChat, firstMessage, fs10, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
7386
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
7468
7387
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
7469
7388
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
7470
7389
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
7471
7390
  }
7472
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
7391
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
7473
7392
  this.ensureInit();
7474
7393
  const { autonomousDefault } = modes;
7475
7394
  let ctx = "";
7476
- if (fs10) {
7477
- const collected = await new ContextCollector(fs10).collect(runners[0] ?? "claude-code");
7395
+ if (fs12) {
7396
+ const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
7478
7397
  if (collected.aiflowContext) ctx = `
7479
7398
  <aiflow_context>
7480
7399
  ${collected.aiflowContext}
@@ -7490,12 +7409,12 @@ ${collected.aiflowContext}
7490
7409
  autonomousDefault
7491
7410
  );
7492
7411
  }
7493
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
7412
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
7494
7413
  this.ensureInit();
7495
7414
  const { autonomousDefault } = modes;
7496
7415
  let aiflowContext;
7497
- if (fs10) {
7498
- const collected = await new ContextCollector(fs10).collect(existingPlan.runners[0] ?? "claude-code");
7416
+ if (fs12) {
7417
+ const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
7499
7418
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
7500
7419
  }
7501
7420
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -7678,7 +7597,6 @@ var OpenAiService = class extends BaseAiService {
7678
7597
  async startConversation(req) {
7679
7598
  this.ensureInit();
7680
7599
  const client = this.getClient();
7681
- this.researchSubagentsEnabled = req.researchSubagentsEnabled ?? false;
7682
7600
  const contextStr = await BaseAiService.collectResearchContext(req.fs, req.runners);
7683
7601
  const systemPrompt = buildConversationSystemPrompt(
7684
7602
  req.goal,
@@ -7687,16 +7605,13 @@ var OpenAiService = class extends BaseAiService {
7687
7605
  req.runners,
7688
7606
  req.runnerModes,
7689
7607
  req.autonomousDefault ?? true,
7690
- req.grillMeEnabled ?? false,
7691
- req.prdEnabled ?? false,
7692
- req.reviewEnabled ?? false,
7693
7608
  req.verificationEnabled ?? false
7694
7609
  );
7695
7610
  const userText = req.goal;
7696
7611
  const messages = [
7697
7612
  { role: "system", content: `${systemPrompt}
7698
7613
 
7699
- ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
7614
+ ${buildResearchToolsPrompt()}` }
7700
7615
  ];
7701
7616
  for (const m of req.priorHistory ?? []) {
7702
7617
  messages.push({ role: m.role, content: m.content });
@@ -7706,7 +7621,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
7706
7621
  messages,
7707
7622
  client,
7708
7623
  this.requireModel("orchestratorModel", this.config.orchestratorModel),
7709
- toOpenAiTools(this.researchSubagentsEnabled),
7624
+ toOpenAiTools(),
7710
7625
  (delta) => currentProgress({ type: "thinking", text: delta }),
7711
7626
  (delta) => currentProgress({ type: "plan_token", planToken: delta })
7712
7627
  );
@@ -7716,8 +7631,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
7716
7631
  runners: req.runners,
7717
7632
  runnerModes: req.runnerModes,
7718
7633
  autonomousDefault: req.autonomousDefault,
7719
- fetcher: req.fetcher,
7720
- prdEnabled: req.prdEnabled ?? false
7634
+ fetcher: req.fetcher
7721
7635
  };
7722
7636
  this.conversation = {
7723
7637
  ctx,
@@ -7735,15 +7649,14 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
7735
7649
  }
7736
7650
  }
7737
7651
  // --- IAiService implementation ---
7738
- async researchAndPlan(userDescription, runners, modelsByRunner, fs10, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7652
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7739
7653
  this.ensureInit();
7740
7654
  const client = this.getClient();
7741
- const { autonomousDefault, researchSubagents: researchSubagentsEnabled } = modes;
7742
- this.researchSubagentsEnabled = researchSubagentsEnabled;
7743
- const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
7655
+ const { autonomousDefault } = modes;
7656
+ const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
7744
7657
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
7745
7658
  const userText = `User goal: ${userDescription}`;
7746
- const firstMessage = `${buildResearchToolsPrompt(researchSubagentsEnabled)}
7659
+ const firstMessage = `${buildResearchToolsPrompt()}
7747
7660
 
7748
7661
  Explore the workspace to understand the codebase, then generate the plan.
7749
7662
 
@@ -7755,20 +7668,20 @@ ${userText}`;
7755
7668
  messages,
7756
7669
  client,
7757
7670
  this.requireModel("orchestratorModel", this.config.orchestratorModel),
7758
- toOpenAiTools(researchSubagentsEnabled),
7671
+ toOpenAiTools(),
7759
7672
  (delta) => onProgress({ type: "thinking", text: delta })
7760
7673
  );
7761
- const result = await this.runResearchLoop(researchChat, firstMessage, fs10, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
7674
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
7762
7675
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
7763
7676
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
7764
7677
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
7765
7678
  }
7766
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7679
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7767
7680
  this.ensureInit();
7768
7681
  const { autonomousDefault } = modes;
7769
7682
  let ctx = "";
7770
- if (fs10) {
7771
- const collected = await new ContextCollector(fs10).collect(runners[0] ?? "claude-code");
7683
+ if (fs12) {
7684
+ const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
7772
7685
  if (collected.aiflowContext) ctx = `
7773
7686
  <aiflow_context>
7774
7687
  ${collected.aiflowContext}
@@ -7784,12 +7697,12 @@ ${collected.aiflowContext}
7784
7697
  autonomousDefault
7785
7698
  );
7786
7699
  }
7787
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7700
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7788
7701
  this.ensureInit();
7789
7702
  const { autonomousDefault } = modes;
7790
7703
  let aiflowContext;
7791
- if (fs10) {
7792
- const collected = await new ContextCollector(fs10).collect(existingPlan.runners[0] ?? "claude-code");
7704
+ if (fs12) {
7705
+ const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
7793
7706
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
7794
7707
  }
7795
7708
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -7878,21 +7791,28 @@ function pickWindow(sorted, currentIdx, max) {
7878
7791
  const window = sorted.slice(start, start + max);
7879
7792
  return { window, omitted: sorted.length - window.length };
7880
7793
  }
7881
- function renderPlanMap(allTasks, currentTaskId, opts) {
7882
- if (allTasks.length < PLAN_MAP_MIN_TASKS) return "";
7883
- const sorted = [...allTasks].sort((a, b) => a.order - b.order);
7794
+ function renderPlanMap(planTasks, currentTaskId, opts) {
7795
+ const rows = flattenTasksWithParents(planTasks);
7796
+ if (rows.length < PLAN_MAP_MIN_TASKS) return "";
7797
+ const sorted = rows.map((r) => ({
7798
+ ...r,
7799
+ label: taskOrderLabel(r.task, r.parent ?? void 0)
7800
+ })).sort((a, b) => {
7801
+ const parentDiff = (a.parent?.order ?? -1) - (b.parent?.order ?? -1);
7802
+ if (parentDiff !== 0) return parentDiff;
7803
+ return a.task.order - b.task.order;
7804
+ });
7884
7805
  const max = opts?.maxEntries ?? DEFAULT_PLAN_MAP_MAX_ENTRIES;
7885
- const currentIdx = sorted.findIndex((t) => t.id === currentTaskId);
7806
+ const currentIdx = sorted.findIndex((r) => r.task.id === currentTaskId);
7886
7807
  const { window, omitted } = pickWindow(sorted, currentIdx, max);
7887
- const currentTask = sorted.find((t) => t.id === currentTaskId);
7888
- const widthOrder = Math.max(2, String(sorted[sorted.length - 1]?.order ?? sorted.length).length);
7889
- const lines = window.map((t) => {
7890
- const isCurrent = t.id === currentTaskId;
7891
- const tag = planMapStatus(t, isCurrent);
7892
- const order = String(t.order).padStart(widthOrder, " ");
7808
+ const currentTask = sorted.find((r) => r.task.id === currentTaskId)?.task;
7809
+ const lines = window.map((row) => {
7810
+ const isCurrent = row.task.id === currentTaskId;
7811
+ const tag = planMapStatus(row.task, isCurrent);
7812
+ const order = row.parent ? row.label : row.label.padStart(2, " ");
7893
7813
  const arrow = isCurrent ? " \u2190 you are here" : "";
7894
- const runner = t.assignedRunner ? ` (${t.assignedRunner})` : "";
7895
- return `${order}. [${tag.padEnd(7, " ")}] ${t.title}${runner}${arrow}`;
7814
+ const runner = row.task.assignedRunner ? ` (${row.task.assignedRunner})` : "";
7815
+ return `${order}. [${tag.padEnd(7, " ")}] ${row.task.title}${runner}${arrow}`;
7896
7816
  });
7897
7817
  const footer = omitted > 0 ? `
7898
7818
  (${omitted} task${omitted === 1 ? "" : "s"} omitted from this view)` : "";
@@ -7957,7 +7877,8 @@ function composeAugmentedPrompt(task, allTasks, opts) {
7957
7877
  const map = renderPlanMap(allTasks, task.id, { maxEntries: opts?.planMapMaxEntries });
7958
7878
  if (map) blocks.push(map);
7959
7879
  }
7960
- const outputs = collectDirectDependencyOutputs(task, allTasks);
7880
+ const flat = flattenTasks(allTasks);
7881
+ const outputs = collectDirectDependencyOutputs(task, flat);
7961
7882
  if (outputs.length > 0) blocks.push(renderPriorOutputs(outputs));
7962
7883
  if (opts?.tddEnabled) {
7963
7884
  blocks.push(renderTddInstruction());
@@ -8136,6 +8057,7 @@ function renderTerminalOutput(raw) {
8136
8057
  return populated.map((r) => cells(r).join("")).join("\n");
8137
8058
  }
8138
8059
  var MARKER_SCAN_TAIL = 16384;
8060
+ var IDLE_TIMEOUT_MS = 6e4;
8139
8061
  var VerdictEngine = class {
8140
8062
  markerSeen = /* @__PURE__ */ new Set();
8141
8063
  buffers = /* @__PURE__ */ new Map();
@@ -8143,6 +8065,7 @@ var VerdictEngine = class {
8143
8065
  pausedSessions = /* @__PURE__ */ new Map();
8144
8066
  listeners = [];
8145
8067
  checkpointListeners = [];
8068
+ idleListeners = [];
8146
8069
  /**
8147
8070
  * Per-task generation counter. Incremented on every watch() and clear().
8148
8071
  * Stale callbacks (from a prior session whose generation doesn't match
@@ -8150,12 +8073,47 @@ var VerdictEngine = class {
8150
8073
  * wrong session.
8151
8074
  */
8152
8075
  generations = /* @__PURE__ */ new Map();
8076
+ idleTimers = /* @__PURE__ */ new Map();
8077
+ idleSince = /* @__PURE__ */ new Map();
8153
8078
  onVerdict(listener) {
8154
8079
  this.listeners.push(listener);
8155
8080
  }
8156
8081
  onCheckpoint(listener) {
8157
8082
  this.checkpointListeners.push(listener);
8158
8083
  }
8084
+ onIdleChange(listener) {
8085
+ this.idleListeners.push(listener);
8086
+ }
8087
+ /** Advisory silence timestamp for a task, or null if it isn't idle. */
8088
+ getIdleSince(taskId) {
8089
+ return this.idleSince.get(taskId) ?? null;
8090
+ }
8091
+ /** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
8092
+ touchIdle(taskId, gen) {
8093
+ const existing = this.idleTimers.get(taskId);
8094
+ if (existing) clearTimeout(existing);
8095
+ if (this.idleSince.get(taskId)) {
8096
+ this.idleSince.set(taskId, null);
8097
+ for (const l of this.idleListeners) l(taskId, null);
8098
+ }
8099
+ this.idleTimers.set(taskId, setTimeout(() => {
8100
+ if (this.generations.get(taskId) !== gen) return;
8101
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8102
+ this.idleSince.set(taskId, now);
8103
+ for (const l of this.idleListeners) l(taskId, now);
8104
+ }, IDLE_TIMEOUT_MS));
8105
+ }
8106
+ /** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
8107
+ clearIdle(taskId) {
8108
+ const timer = this.idleTimers.get(taskId);
8109
+ if (timer) clearTimeout(timer);
8110
+ this.idleTimers.delete(taskId);
8111
+ const wasIdle = this.idleSince.get(taskId);
8112
+ this.idleSince.delete(taskId);
8113
+ if (wasIdle) {
8114
+ for (const l of this.idleListeners) l(taskId, null);
8115
+ }
8116
+ }
8159
8117
  approveCheckpoint(taskId) {
8160
8118
  const session = this.pausedSessions.get(taskId);
8161
8119
  if (session) {
@@ -8186,6 +8144,7 @@ ORDEWELL_REJECT: ${reason}
8186
8144
  this.checkpointCounts.set(task.id, 0);
8187
8145
  session.onOutput((text) => {
8188
8146
  if (this.generations.get(task.id) !== gen) return;
8147
+ this.touchIdle(task.id, gen);
8189
8148
  if (this.markerSeen.has(task.id)) return;
8190
8149
  const buf = (this.buffers.get(task.id) ?? "") + text;
8191
8150
  this.buffers.set(task.id, buf);
@@ -8197,6 +8156,7 @@ ORDEWELL_REJECT: ${reason}
8197
8156
  this.buffers.delete(task.id);
8198
8157
  this.checkpointCounts.delete(task.id);
8199
8158
  this.pausedSessions.delete(task.id);
8159
+ this.clearIdle(task.id);
8200
8160
  this.generations.set(task.id, gen + 1);
8201
8161
  const verdict = this.decide(task, 0);
8202
8162
  for (const l of this.listeners) l(task.id, verdict, output);
@@ -8221,6 +8181,7 @@ ORDEWELL_REJECT: ${reason}
8221
8181
  this.buffers.delete(task.id);
8222
8182
  this.checkpointCounts.delete(task.id);
8223
8183
  this.pausedSessions.delete(task.id);
8184
+ this.clearIdle(task.id);
8224
8185
  const verdict = this.decide(task, exitCode);
8225
8186
  for (const l of this.listeners) l(task.id, verdict, output);
8226
8187
  });
@@ -8231,6 +8192,7 @@ ORDEWELL_REJECT: ${reason}
8231
8192
  this.buffers.delete(task.id);
8232
8193
  this.checkpointCounts.delete(task.id);
8233
8194
  this.pausedSessions.delete(task.id);
8195
+ this.clearIdle(task.id);
8234
8196
  this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
8235
8197
  return {
8236
8198
  outcome: "pass",
@@ -8252,6 +8214,7 @@ ORDEWELL_REJECT: ${reason}
8252
8214
  this.buffers.delete(task.id);
8253
8215
  this.checkpointCounts.delete(task.id);
8254
8216
  this.pausedSessions.delete(task.id);
8217
+ this.clearIdle(task.id);
8255
8218
  this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
8256
8219
  }
8257
8220
  /** Drop all tracking state (used on stop / loadPlan). */
@@ -8260,6 +8223,9 @@ ORDEWELL_REJECT: ${reason}
8260
8223
  this.buffers.clear();
8261
8224
  this.checkpointCounts.clear();
8262
8225
  this.pausedSessions.clear();
8226
+ for (const timer of this.idleTimers.values()) clearTimeout(timer);
8227
+ this.idleTimers.clear();
8228
+ this.idleSince.clear();
8263
8229
  this.generations.clear();
8264
8230
  }
8265
8231
  decide(task, exitCode) {
@@ -8665,6 +8631,7 @@ var TaskOrchestrator = class {
8665
8631
  this.store.markAwaitingUser(taskId);
8666
8632
  this.emit("onCheckpoint", { taskId, taskTitle: task.title, summary });
8667
8633
  });
8634
+ this.verifier.onIdleChange(() => this.emit("onTaskChanged"));
8668
8635
  }
8669
8636
  config;
8670
8637
  notifications;
@@ -8689,6 +8656,10 @@ var TaskOrchestrator = class {
8689
8656
  workspaceRootFn = () => process.cwd();
8690
8657
  observers = [];
8691
8658
  tddEnabled = () => false;
8659
+ /** Advisory silence timestamp for a task's live runner, or null if not idle. */
8660
+ getIdleSince(taskId) {
8661
+ return this.verifier.getIdleSince(taskId);
8662
+ }
8692
8663
  get storeInstance() {
8693
8664
  return this.store;
8694
8665
  }
@@ -9076,8 +9047,10 @@ ${output.slice(-3e3)}`);
9076
9047
  if (ready.length === 0 && this.activeTaskSessions.size === 0) {
9077
9048
  const remaining = this.store.allTasks.filter((t) => t.status !== "completed");
9078
9049
  console.log(`[TaskOrchestrator] tick(): no work. remaining=${remaining.length}, completed=${this.store.isAllComplete()}`);
9079
- for (const t of remaining) {
9080
- console.log(` - ${t.order}. ${t.title} [${t.type}/${t.status}] prompt=${!!t.prompt}`);
9050
+ const remainingById = new Map(remaining.map((t) => [t.id, t]));
9051
+ for (const { task, parent } of flattenTasksWithParents(this.store.planTasks)) {
9052
+ if (!remainingById.has(task.id)) continue;
9053
+ console.log(` - ${taskOrderLabel(task, parent ?? void 0)}. ${task.title} [${task.type}/${task.status}] prompt=${!!task.prompt}`);
9081
9054
  }
9082
9055
  if (this.store.isAllComplete()) {
9083
9056
  this.running = false;
@@ -9104,7 +9077,7 @@ ${output.slice(-3e3)}`);
9104
9077
  try {
9105
9078
  const cwd = this.workspaceRootFn();
9106
9079
  const runner = this.store.resolveTaskRunner(task);
9107
- const finalPrompt = composeAugmentedPrompt(task, this.store.allTasks, {
9080
+ const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
9108
9081
  planMapEnabled: this.config.planMapEnabled,
9109
9082
  tddEnabled: this.tddEnabled()
9110
9083
  });
@@ -9278,7 +9251,7 @@ var osMod = __toESM(require("os"));
9278
9251
  var import_child_process = require("child_process");
9279
9252
  var import_util = require("util");
9280
9253
  var os2 = __toESM(require("os"));
9281
- var path5 = __toESM(require("path"));
9254
+ var path4 = __toESM(require("path"));
9282
9255
  var execAsync = (0, import_util.promisify)(import_child_process.exec);
9283
9256
  var LOGIN_SHELL_TIMEOUT_MS = 5e3;
9284
9257
  var PATH_MARKER = "__ORDEWELL_PATH__";
@@ -9289,40 +9262,40 @@ var defaultExec = async (command, options) => {
9289
9262
  function posixBinDirs() {
9290
9263
  const home = os2.homedir();
9291
9264
  return [
9292
- path5.join(home, ".local", "bin"),
9293
- path5.join(home, ".opencode", "bin"),
9294
- path5.join(home, ".claude", "bin"),
9295
- path5.join(home, ".bun", "bin"),
9296
- path5.join(home, ".volta", "bin"),
9297
- path5.join(home, ".npm-global", "bin"),
9298
- path5.join(home, "bin"),
9265
+ path4.join(home, ".local", "bin"),
9266
+ path4.join(home, ".opencode", "bin"),
9267
+ path4.join(home, ".claude", "bin"),
9268
+ path4.join(home, ".bun", "bin"),
9269
+ path4.join(home, ".volta", "bin"),
9270
+ path4.join(home, ".npm-global", "bin"),
9271
+ path4.join(home, "bin"),
9299
9272
  "/usr/local/bin",
9300
9273
  "/opt/homebrew/bin"
9301
9274
  ];
9302
9275
  }
9303
9276
  function windowsBinDirs(env, home) {
9304
- const join16 = (...parts) => path5.win32.join(...parts);
9305
- const under = (base, ...parts) => base ? [join16(base, ...parts)] : [];
9277
+ const join19 = (...parts) => path4.win32.join(...parts);
9278
+ const under = (base, ...parts) => base ? [join19(base, ...parts)] : [];
9306
9279
  const appData = env.APPDATA;
9307
9280
  const localAppData = env.LOCALAPPDATA;
9308
9281
  const programData = env.ProgramData;
9309
9282
  return [
9310
9283
  // Native per-user installers, PowerShell-driven and otherwise.
9311
- join16(home, ".local", "bin"),
9312
- join16(home, ".opencode", "bin"),
9313
- join16(home, ".claude", "bin"),
9314
- join16(home, ".codex", "bin"),
9284
+ join19(home, ".local", "bin"),
9285
+ join19(home, ".opencode", "bin"),
9286
+ join19(home, ".claude", "bin"),
9287
+ join19(home, ".codex", "bin"),
9315
9288
  ...under(localAppData, "Programs", "claude"),
9316
9289
  // Node package managers. npm's global prefix is `%APPDATA%\npm`; the
9317
9290
  // literal spelling is kept as a fallback for a host that does not export
9318
9291
  // APPDATA (a service account, a stripped CI container).
9319
9292
  ...under(appData, "npm"),
9320
- join16(home, "AppData", "Roaming", "npm"),
9293
+ join19(home, "AppData", "Roaming", "npm"),
9321
9294
  ...under(localAppData, "pnpm"),
9322
9295
  ...under(localAppData, "Yarn", "bin"),
9323
- join16(home, ".bun", "bin"),
9296
+ join19(home, ".bun", "bin"),
9324
9297
  // Windows package managers.
9325
- join16(home, "scoop", "shims"),
9298
+ join19(home, "scoop", "shims"),
9326
9299
  ...under(env.SCOOP, "shims"),
9327
9300
  ...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
9328
9301
  ...under(programData, "chocolatey", "bin"),
@@ -9332,7 +9305,7 @@ function windowsBinDirs(env, home) {
9332
9305
  ...under(localAppData, "Microsoft", "WindowsApps"),
9333
9306
  // Version managers. Volta's Windows home is not the POSIX `~/.volta`.
9334
9307
  ...under(localAppData, "Volta", "bin"),
9335
- join16(home, ".volta", "bin")
9308
+ join19(home, ".volta", "bin")
9336
9309
  ];
9337
9310
  }
9338
9311
  function wellKnownBinDirs(deps = {}) {
@@ -9361,14 +9334,14 @@ function augmentedPath(execImpl = defaultExec) {
9361
9334
  const segments = [];
9362
9335
  const push = (p) => {
9363
9336
  if (!p) return;
9364
- for (const seg of p.split(path5.delimiter)) {
9337
+ for (const seg of p.split(path4.delimiter)) {
9365
9338
  if (seg && !segments.includes(seg)) segments.push(seg);
9366
9339
  }
9367
9340
  };
9368
9341
  push(process.env.PATH);
9369
9342
  push(await loginShellPath(execImpl));
9370
9343
  for (const dir of wellKnownBinDirs()) push(dir);
9371
- return segments.join(path5.delimiter);
9344
+ return segments.join(path4.delimiter);
9372
9345
  })();
9373
9346
  }
9374
9347
  return cachedPath;
@@ -9388,6 +9361,50 @@ function withPath(base, resolvedPath, overrides = {}) {
9388
9361
  return env;
9389
9362
  }
9390
9363
 
9364
+ // src/utils/globalDataDir.ts
9365
+ var fs = __toESM(require("fs"));
9366
+ var path5 = __toESM(require("path"));
9367
+ var os3 = __toESM(require("os"));
9368
+ function globalDataDir() {
9369
+ return path5.join(os3.homedir(), ".ordewell");
9370
+ }
9371
+ function copyDirSync(src, dest) {
9372
+ fs.mkdirSync(dest, { recursive: true });
9373
+ const entries = fs.readdirSync(src, { withFileTypes: true });
9374
+ for (const entry of entries) {
9375
+ const srcPath = path5.join(src, entry.name);
9376
+ const destPath = path5.join(dest, entry.name);
9377
+ if (entry.isDirectory()) {
9378
+ copyDirSync(srcPath, destPath);
9379
+ } else {
9380
+ fs.copyFileSync(srcPath, destPath);
9381
+ }
9382
+ }
9383
+ }
9384
+ var migrated = false;
9385
+ function migrateOldConfigDir() {
9386
+ if (migrated) return;
9387
+ migrated = true;
9388
+ const oldDir = path5.join(os3.homedir(), ".config", "ordewell");
9389
+ if (!fs.existsSync(oldDir)) return;
9390
+ const newDir = globalDataDir();
9391
+ const liftFile = (name) => {
9392
+ const oldFile = path5.join(oldDir, name);
9393
+ const newFile = path5.join(newDir, name);
9394
+ if (fs.existsSync(oldFile) && !fs.existsSync(newFile)) {
9395
+ fs.mkdirSync(newDir, { recursive: true });
9396
+ fs.copyFileSync(oldFile, newFile);
9397
+ }
9398
+ };
9399
+ liftFile("settings.json");
9400
+ liftFile(".env");
9401
+ const oldPlugins = path5.join(oldDir, "plugins");
9402
+ const newPlugins = path5.join(newDir, "plugins");
9403
+ if (fs.existsSync(oldPlugins) && !fs.existsSync(newPlugins)) {
9404
+ copyDirSync(oldPlugins, newPlugins);
9405
+ }
9406
+ }
9407
+
9391
9408
  // src/utils/launch.ts
9392
9409
  var fsSync2 = __toESM(require("fs"));
9393
9410
  var path6 = __toESM(require("path"));
@@ -9404,9 +9421,11 @@ function buildShellInvocation(command, args) {
9404
9421
  const inner = [command, ...args].map(posixShellQuote).join(" ");
9405
9422
  return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
9406
9423
  }
9407
- function wrapWithPty(command, args) {
9424
+ function wrapWithPty(command, args, opts = {}) {
9408
9425
  const inner = [command, ...args].map(posixShellQuote).join(" ");
9409
- return { command: "script", args: ["-q", "-e", "-f", "-c", inner, "/dev/null"] };
9426
+ const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
9427
+ const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
9428
+ return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
9410
9429
  }
9411
9430
 
9412
9431
  // src/utils/launch.ts
@@ -9713,7 +9732,7 @@ function parseCodexModelsCache(content) {
9713
9732
  }
9714
9733
  var GEMINI_MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
9715
9734
  function geminiDiscoveryCacheKey(apiKey) {
9716
- const dir = path7.join(osMod.homedir(), ".config", "ordewell");
9735
+ const dir = globalDataDir();
9717
9736
  const hash = apiKey.slice(-8);
9718
9737
  return {
9719
9738
  key: `gemini-models:${hash}`,
@@ -10243,12 +10262,14 @@ var ModelDiscovery = class {
10243
10262
  // src/services/ModelCatalog.ts
10244
10263
  var fs2 = __toESM(require("fs"));
10245
10264
  var path8 = __toESM(require("path"));
10246
- var os3 = __toESM(require("os"));
10247
10265
  var CACHE_KEY = "model-catalog";
10248
10266
  var CACHE_TTL_MS = 60 * 60 * 1e3;
10249
10267
  function storageKey(baseUrl) {
10250
10268
  return `${CACHE_KEY}:${baseUrl}`;
10251
10269
  }
10270
+ function globalCacheDir() {
10271
+ return globalDataDir();
10272
+ }
10252
10273
  var ModelCatalog = class {
10253
10274
  static async fetchModels(apiKey, baseUrl, fetchImpl) {
10254
10275
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
@@ -10291,7 +10312,7 @@ var ModelCatalog = class {
10291
10312
  try {
10292
10313
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
10293
10314
  const key = storageKey(url);
10294
- const cacheDir = path8.join(os3.homedir(), ".config", "ordewell");
10315
+ const cacheDir = globalCacheDir();
10295
10316
  const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10296
10317
  if (fs2.existsSync(cacheFile)) fs2.rmSync(cacheFile);
10297
10318
  } catch {
@@ -10299,7 +10320,7 @@ var ModelCatalog = class {
10299
10320
  }
10300
10321
  static readCache(key) {
10301
10322
  try {
10302
- const cacheDir = path8.join(os3.homedir(), ".config", "ordewell");
10323
+ const cacheDir = globalCacheDir();
10303
10324
  const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10304
10325
  if (!fs2.existsSync(cacheFile)) return null;
10305
10326
  const raw = fs2.readFileSync(cacheFile, "utf8");
@@ -10312,7 +10333,7 @@ var ModelCatalog = class {
10312
10333
  }
10313
10334
  static writeCache(key, models) {
10314
10335
  try {
10315
- const cacheDir = path8.join(os3.homedir(), ".config", "ordewell");
10336
+ const cacheDir = globalCacheDir();
10316
10337
  fs2.mkdirSync(cacheDir, { recursive: true });
10317
10338
  const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10318
10339
  fs2.writeFileSync(
@@ -11754,14 +11775,14 @@ ${part.text}` : part.text });
11754
11775
  }
11755
11776
  }
11756
11777
  }
11757
- async json(method, path17, body, signal) {
11758
- const response = await this.deps.fetch(`${this.baseUrl}${path17}`, {
11778
+ async json(method, path20, body, signal) {
11779
+ const response = await this.deps.fetch(`${this.baseUrl}${path20}`, {
11759
11780
  method,
11760
11781
  headers: { "content-type": "application/json" },
11761
11782
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
11762
11783
  ...signal ? { signal } : {}
11763
11784
  });
11764
- if (!response.ok) throw new Error(`${method} ${path17} failed: ${response.status} ${response.statusText}`);
11785
+ if (!response.ok) throw new Error(`${method} ${path20} failed: ${response.status} ${response.statusText}`);
11765
11786
  return await response.json().catch(() => null);
11766
11787
  }
11767
11788
  nativeSessionId() {
@@ -11916,9 +11937,6 @@ var CliAgentAiService = class {
11916
11937
  req.runners,
11917
11938
  req.runnerModes,
11918
11939
  req.autonomousDefault ?? true,
11919
- req.grillMeEnabled ?? false,
11920
- req.prdEnabled ?? false,
11921
- req.reviewEnabled ?? false,
11922
11940
  req.verificationEnabled ?? false,
11923
11941
  true
11924
11942
  );
@@ -11933,10 +11951,7 @@ var CliAgentAiService = class {
11933
11951
  startOptions,
11934
11952
  runners: req.runners,
11935
11953
  runnerModes: req.runnerModes,
11936
- autonomousDefault: req.autonomousDefault,
11937
- prdEnabled: req.prdEnabled ?? false,
11938
- prdCaptured: false,
11939
- prdNudgeSent: false
11954
+ autonomousDefault: req.autonomousDefault
11940
11955
  };
11941
11956
  const opening = this.openingMessage(req);
11942
11957
  return this.runConversation(opening, req.onProgress, req.signal);
@@ -11964,8 +11979,8 @@ var CliAgentAiService = class {
11964
11979
  /**
11965
11980
  * Drive one user message to a settled planner turn. Same shape as the API
11966
11981
  * backend's conversation loop minus the tool rounds — those belong to the
11967
- * agent now — and with the same three policies layered on top: the PRD gate,
11968
- * the empty-reply nudge, and a bounded corrective re-emit for botched JSON.
11982
+ * agent now — and with the same two policies layered on top: the
11983
+ * empty-reply nudge, and a bounded corrective re-emit for botched JSON.
11969
11984
  */
11970
11985
  async runConversation(message, onProgress, signal) {
11971
11986
  const conversation = this.conversation;
@@ -12013,7 +12028,6 @@ var CliAgentAiService = class {
12013
12028
  ].join(" ");
12014
12029
  continue;
12015
12030
  }
12016
- if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
12017
12031
  const reply = classifyPlannerReply(turn.text, {
12018
12032
  runners: conversation.runners,
12019
12033
  runnerModes: conversation.runnerModes,
@@ -12027,16 +12041,6 @@ var CliAgentAiService = class {
12027
12041
  case "task_query":
12028
12042
  return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
12029
12043
  case "plan":
12030
- if (conversation.prdEnabled && !conversation.prdCaptured && !conversation.prdNudgeSent && !combined?.aborted) {
12031
- conversation.prdNudgeSent = true;
12032
- pending = [
12033
- "PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
12034
- "In your next reply: first output the full markdown PRD wrapped EXACTLY in",
12035
- '<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
12036
- "then re-emit the exact same task plan JSON after the PRD block."
12037
- ].join(" ");
12038
- continue;
12039
- }
12040
12044
  this.conversation = null;
12041
12045
  return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
12042
12046
  case "broken_task_ops":
@@ -12215,10 +12219,7 @@ var CliAgentAiService = class {
12215
12219
  oneShotAdapter = await this.startAdapter(startOptions);
12216
12220
  this.conversation = {
12217
12221
  startOptions,
12218
- runners: [],
12219
- prdEnabled: false,
12220
- prdCaptured: false,
12221
- prdNudgeSent: false
12222
+ runners: []
12222
12223
  };
12223
12224
  const turn = await this.runTurn(
12224
12225
  "Follow the instructions in your system prompt and produce the plan now.",
@@ -12235,8 +12236,8 @@ var CliAgentAiService = class {
12235
12236
  this.lastNativeSessionId = previousSessionId;
12236
12237
  }
12237
12238
  }
12238
- async researchAndPlan(userDescription, runners, modelsByRunner, fs10, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
12239
- const contextStr = await collectResearchContext(fs10, runners);
12239
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
12240
+ const contextStr = await collectResearchContext(fs12, runners);
12240
12241
  const researchLog = [
12241
12242
  { id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
12242
12243
  ];
@@ -12559,6 +12560,181 @@ function retargetTaskRunner(task, runner, catalog) {
12559
12560
  return { assignedRunner: runner, ...runnerAssignment(catalog, task) };
12560
12561
  }
12561
12562
 
12563
+ // src/services/SkillsService.ts
12564
+ var fs4 = __toESM(require("fs"));
12565
+ var path11 = __toESM(require("path"));
12566
+
12567
+ // src/services/builtinSkills.ts
12568
+ var path10 = __toESM(require("path"));
12569
+ var import_url = require("url");
12570
+ var import_meta = {};
12571
+ function builtinSkillsDir() {
12572
+ const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path10.dirname((0, import_url.fileURLToPath)(
12573
+ // @ts-expect-error - import.meta is only valid under an ESM module setting
12574
+ import_meta.url
12575
+ ));
12576
+ return path10.join(dir, "..", "skills");
12577
+ }
12578
+
12579
+ // src/services/SkillsService.ts
12580
+ var BUILTIN_SKILL_NAMES = ["grilling", "to-spec"];
12581
+ var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
12582
+ function parseSkillFile(filePath, name, source) {
12583
+ const raw = fs4.readFileSync(filePath, "utf8");
12584
+ const frontmatter = {};
12585
+ let content = raw;
12586
+ if (raw.startsWith("---\n")) {
12587
+ const end = raw.indexOf("\n---", 4);
12588
+ if (end !== -1) {
12589
+ const fmText = raw.slice(4, end);
12590
+ for (const line of fmText.split("\n")) {
12591
+ const idx = line.indexOf(":");
12592
+ if (idx === -1) continue;
12593
+ const key = line.slice(0, idx).trim();
12594
+ const value = line.slice(idx + 1).trim();
12595
+ if (key === "disable-model-invocation") {
12596
+ frontmatter[key] = value === "true";
12597
+ } else if (key === "name" || key === "description") {
12598
+ frontmatter[key] = value.replace(/^"|"$/g, "");
12599
+ }
12600
+ }
12601
+ content = raw.slice(end + 4).replace(/^\n+/, "");
12602
+ }
12603
+ }
12604
+ const metadata = {
12605
+ name: frontmatter.name ?? name,
12606
+ description: frontmatter.description ?? "",
12607
+ ...frontmatter["disable-model-invocation"] !== void 0 ? { disableModelInvocation: frontmatter["disable-model-invocation"] } : {}
12608
+ };
12609
+ return { name, description: metadata.description, metadata, content, path: filePath, source };
12610
+ }
12611
+ function copyDirSync2(src, dest) {
12612
+ fs4.mkdirSync(dest, { recursive: true });
12613
+ const entries = fs4.readdirSync(src, { withFileTypes: true });
12614
+ for (const entry of entries) {
12615
+ const srcPath = path11.join(src, entry.name);
12616
+ const destPath = path11.join(dest, entry.name);
12617
+ if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
12618
+ else fs4.copyFileSync(srcPath, destPath);
12619
+ }
12620
+ }
12621
+ function readDir(dir, source) {
12622
+ if (!fs4.existsSync(dir)) return [];
12623
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
12624
+ const skills = [];
12625
+ for (const entry of entries) {
12626
+ if (!entry.isDirectory()) continue;
12627
+ const skillFile = path11.join(dir, entry.name, "SKILL.md");
12628
+ if (!fs4.existsSync(skillFile)) continue;
12629
+ const parsed = parseSkillFile(skillFile, entry.name, source);
12630
+ if (parsed) skills.push(parsed);
12631
+ }
12632
+ return skills;
12633
+ }
12634
+ var SkillsService = class {
12635
+ constructor(workspaceRoot) {
12636
+ this.workspaceRoot = workspaceRoot;
12637
+ }
12638
+ workspaceRoot;
12639
+ localDir() {
12640
+ return this.workspaceRoot ? path11.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
12641
+ }
12642
+ globalDir() {
12643
+ return path11.join(globalDataDir(), "skills");
12644
+ }
12645
+ seed(name) {
12646
+ const dest = path11.join(this.globalDir(), name);
12647
+ if (fs4.existsSync(dest)) return false;
12648
+ let srcDir;
12649
+ try {
12650
+ srcDir = path11.join(builtinSkillsDir(), name);
12651
+ } catch (err) {
12652
+ console.warn(`Could not locate built-in skills dir: ${String(err)}`);
12653
+ return false;
12654
+ }
12655
+ if (!fs4.existsSync(srcDir)) {
12656
+ console.warn(`Built-in skill not found in package: ${name}`);
12657
+ return false;
12658
+ }
12659
+ copyDirSync2(srcDir, dest);
12660
+ return true;
12661
+ }
12662
+ seedBuiltinSkill(name) {
12663
+ return this.seed(name);
12664
+ }
12665
+ /** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
12666
+ isUnmodifiedRetiredSeed(dir, expectedName) {
12667
+ let entries;
12668
+ try {
12669
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
12670
+ } catch {
12671
+ return false;
12672
+ }
12673
+ if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
12674
+ let raw;
12675
+ try {
12676
+ raw = fs4.readFileSync(path11.join(dir, "SKILL.md"), "utf8");
12677
+ } catch {
12678
+ return false;
12679
+ }
12680
+ if (!raw.startsWith("---\n")) return false;
12681
+ const end = raw.indexOf("\n---", 4);
12682
+ if (end === -1) return false;
12683
+ for (const line of raw.slice(4, end).split("\n")) {
12684
+ const idx = line.indexOf(":");
12685
+ if (idx === -1) continue;
12686
+ const key = line.slice(0, idx).trim();
12687
+ if (key === "name") return line.slice(idx + 1).trim().replace(/^"|"$/g, "") === expectedName;
12688
+ }
12689
+ return false;
12690
+ }
12691
+ pruneRetired() {
12692
+ for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
12693
+ const dest = path11.join(this.globalDir(), name);
12694
+ if (!fs4.existsSync(dest)) continue;
12695
+ if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
12696
+ try {
12697
+ fs4.rmSync(dest, { recursive: true, force: true });
12698
+ } catch (err) {
12699
+ console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
12700
+ }
12701
+ }
12702
+ }
12703
+ findSkill(name) {
12704
+ this.pruneRetired();
12705
+ if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
12706
+ const localDir = this.localDir();
12707
+ if (localDir) {
12708
+ const localFile = path11.join(localDir, name, "SKILL.md");
12709
+ if (fs4.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
12710
+ }
12711
+ const globalFile = path11.join(this.globalDir(), name, "SKILL.md");
12712
+ if (fs4.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
12713
+ return void 0;
12714
+ }
12715
+ listSkills() {
12716
+ this.pruneRetired();
12717
+ for (const name of BUILTIN_SKILL_NAMES) this.seed(name);
12718
+ const byName = /* @__PURE__ */ new Map();
12719
+ for (const skill of readDir(this.globalDir(), "global")) {
12720
+ byName.set(skill.name, skill);
12721
+ }
12722
+ const localDir = this.localDir();
12723
+ if (localDir) {
12724
+ for (const skill of readDir(localDir, "local")) {
12725
+ byName.set(skill.name, skill);
12726
+ }
12727
+ }
12728
+ return [...byName.values()];
12729
+ }
12730
+ getSkillContent(name) {
12731
+ return this.findSkill(name)?.content;
12732
+ }
12733
+ };
12734
+ function createSkillsService(workspaceRoot) {
12735
+ return new SkillsService(workspaceRoot);
12736
+ }
12737
+
12562
12738
  // src/services/SessionMessage.ts
12563
12739
  function serializeTask(t) {
12564
12740
  return {
@@ -12580,11 +12756,12 @@ function serializeTask(t) {
12580
12756
  userStoriesCovered: t.userStoriesCovered || void 0
12581
12757
  };
12582
12758
  }
12583
- function serializeTaskStatus(t) {
12759
+ function serializeTaskStatus(t, idleSince = null) {
12584
12760
  return {
12585
12761
  id: t.id,
12586
12762
  status: t.status,
12587
- verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null
12763
+ verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null,
12764
+ idleSince
12588
12765
  };
12589
12766
  }
12590
12767
  function serializePlan(plan) {
@@ -12604,29 +12781,35 @@ function executionSummary(tasks) {
12604
12781
  failed: tasks.filter((t) => t.status === "failed").length
12605
12782
  };
12606
12783
  }
12784
+ var CHECKPOINT_TRUNCATE_LENGTH = 120;
12785
+ function truncateCheckpointSummary(summary) {
12786
+ const firstLine = summary.split("\n")[0].trim();
12787
+ if (firstLine.length <= CHECKPOINT_TRUNCATE_LENGTH) return firstLine;
12788
+ return firstLine.slice(0, CHECKPOINT_TRUNCATE_LENGTH - 1) + "\u2026";
12789
+ }
12607
12790
 
12608
12791
  // src/utils/sessionStore.ts
12609
- var fs5 = __toESM(require("fs"));
12610
- var path11 = __toESM(require("path"));
12792
+ var fs6 = __toESM(require("fs"));
12793
+ var path13 = __toESM(require("path"));
12611
12794
 
12612
12795
  // src/utils/fsHelpers.ts
12613
- var fs4 = __toESM(require("fs"));
12614
- var path10 = __toESM(require("path"));
12796
+ var fs5 = __toESM(require("fs"));
12797
+ var path12 = __toESM(require("path"));
12615
12798
  var STATE_DIR = ".ordewell";
12616
12799
  function ensureDir(dir) {
12617
- if (!fs4.existsSync(dir)) {
12618
- fs4.mkdirSync(dir, { recursive: true });
12800
+ if (!fs5.existsSync(dir)) {
12801
+ fs5.mkdirSync(dir, { recursive: true });
12619
12802
  }
12620
12803
  }
12621
12804
  function getStateDir(baseDir) {
12622
- return path10.join(baseDir ?? process.cwd(), STATE_DIR);
12805
+ return path12.join(baseDir ?? process.cwd(), STATE_DIR);
12623
12806
  }
12624
12807
  var IGNORE_FILE = ".gitignore";
12625
12808
  function ensureStateDirIgnored(baseDir) {
12626
12809
  const dir = getStateDir(baseDir);
12627
12810
  ensureDir(dir);
12628
12811
  try {
12629
- fs4.writeFileSync(path10.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
12812
+ fs5.writeFileSync(path12.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
12630
12813
  } catch (err) {
12631
12814
  if (err.code !== "EEXIST") throw err;
12632
12815
  }
@@ -12641,7 +12824,7 @@ function mintSessionId() {
12641
12824
  // src/utils/sessionStore.ts
12642
12825
  var SESSIONS_SUBDIR = "sessions";
12643
12826
  function getSessionsDir(baseDir) {
12644
- return path11.join(getStateDir(baseDir), SESSIONS_SUBDIR);
12827
+ return path13.join(getStateDir(baseDir), SESSIONS_SUBDIR);
12645
12828
  }
12646
12829
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
12647
12830
  function hasScalarRunner(parsed) {
@@ -12676,17 +12859,17 @@ function saveSession(plan, goal, baseDir, id) {
12676
12859
  plan: JSON.parse(JSON.stringify(plan))
12677
12860
  };
12678
12861
  const filename = sessionFilename(meta);
12679
- const filePath = path11.join(sessionsDir, filename);
12680
- fs5.writeFileSync(filePath, JSON.stringify(session, null, 2));
12862
+ const filePath = path13.join(sessionsDir, filename);
12863
+ fs6.writeFileSync(filePath, JSON.stringify(session, null, 2));
12681
12864
  return meta;
12682
12865
  }
12683
12866
  function listSessions(baseDir, logger = defaultLogger) {
12684
12867
  const sessionsDir = getSessionsDir(baseDir);
12685
- if (!fs5.existsSync(sessionsDir)) return [];
12686
- const files = fs5.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
12868
+ if (!fs6.existsSync(sessionsDir)) return [];
12869
+ const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
12687
12870
  return files.map((f) => {
12688
12871
  try {
12689
- const raw = fs5.readFileSync(path11.join(sessionsDir, f), "utf-8");
12872
+ const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
12690
12873
  const parsed = JSON.parse(raw);
12691
12874
  if (hasScalarRunner(parsed)) {
12692
12875
  logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
@@ -12701,10 +12884,10 @@ function listSessions(baseDir, logger = defaultLogger) {
12701
12884
  }).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
12702
12885
  }
12703
12886
  function findSessionFile(sessionsDir, sessionId, logger) {
12704
- const files = fs5.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
12887
+ const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
12705
12888
  for (const f of files) {
12706
12889
  try {
12707
- const raw = fs5.readFileSync(path11.join(sessionsDir, f), "utf-8");
12890
+ const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
12708
12891
  const session = JSON.parse(raw);
12709
12892
  if (session.meta.id === sessionId) return f;
12710
12893
  } catch (err) {
@@ -12715,11 +12898,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
12715
12898
  }
12716
12899
  function loadSession(sessionId, baseDir, logger = defaultLogger) {
12717
12900
  const sessionsDir = getSessionsDir(baseDir);
12718
- if (!fs5.existsSync(sessionsDir)) return null;
12901
+ if (!fs6.existsSync(sessionsDir)) return null;
12719
12902
  try {
12720
12903
  const file = findSessionFile(sessionsDir, sessionId, logger);
12721
12904
  if (file) {
12722
- const raw = fs5.readFileSync(path11.join(sessionsDir, file), "utf-8");
12905
+ const raw = fs6.readFileSync(path13.join(sessionsDir, file), "utf-8");
12723
12906
  const parsed = JSON.parse(raw);
12724
12907
  if (hasScalarRunner(parsed)) {
12725
12908
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -12749,11 +12932,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
12749
12932
  }
12750
12933
  function deleteSession(sessionId, baseDir, logger = defaultLogger) {
12751
12934
  const sessionsDir = getSessionsDir(baseDir);
12752
- if (!fs5.existsSync(sessionsDir)) return false;
12935
+ if (!fs6.existsSync(sessionsDir)) return false;
12753
12936
  try {
12754
12937
  const file = findSessionFile(sessionsDir, sessionId, logger);
12755
12938
  if (file) {
12756
- fs5.unlinkSync(path11.join(sessionsDir, file));
12939
+ fs6.unlinkSync(path13.join(sessionsDir, file));
12757
12940
  return true;
12758
12941
  }
12759
12942
  } catch (err) {
@@ -12762,6 +12945,29 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
12762
12945
  return false;
12763
12946
  }
12764
12947
 
12948
+ // src/utils/prdStore.ts
12949
+ var fs7 = __toESM(require("fs"));
12950
+ var path14 = __toESM(require("path"));
12951
+ var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
12952
+ function extractPrdBlock(text) {
12953
+ const match = PRD_BLOCK_RE.exec(text);
12954
+ if (!match) return null;
12955
+ const slug2 = sanitizeSlug(match[1]);
12956
+ const markdown = match[2].trim();
12957
+ if (!slug2 || !markdown) return null;
12958
+ return { slug: slug2, markdown };
12959
+ }
12960
+ function sanitizeSlug(raw) {
12961
+ return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
12962
+ }
12963
+ function savePrdMarkdown(workspace, slug2, markdown) {
12964
+ const dir = path14.join(workspace, ".scratch", sanitizeSlug(slug2));
12965
+ fs7.mkdirSync(dir, { recursive: true });
12966
+ const file = path14.join(dir, "PRD.md");
12967
+ fs7.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
12968
+ return file;
12969
+ }
12970
+
12765
12971
  // src/services/createSession.ts
12766
12972
  var CATALOG_MODEL_CAP = 100;
12767
12973
  var MAX_TASK_QUERIES = 3;
@@ -12778,6 +12984,17 @@ var PlanEditError = class extends Error {
12778
12984
  function sessionRuntimeSettings(settings) {
12779
12985
  return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
12780
12986
  }
12987
+ function resolveSkillInvocation(text, skillsService) {
12988
+ const match = text.trim().match(/^\/([a-z][a-z0-9_-]*)$/im);
12989
+ if (!match) return text;
12990
+ const skillName = match[1].toLowerCase();
12991
+ const skill = skillsService.findSkill(skillName);
12992
+ if (!skill) {
12993
+ const available = typeof skillsService.listSkills === "function" ? skillsService.listSkills().map((s) => s.name).join(", ") : "";
12994
+ return `Unknown skill: ${skillName}. Available skills: ${available}`;
12995
+ }
12996
+ return skill.content;
12997
+ }
12781
12998
  var Session = class {
12782
12999
  /** Injected by a test; when present it is the service, forever. */
12783
13000
  pinnedAiService;
@@ -12805,6 +13022,7 @@ var Session = class {
12805
13022
  unsubObserver = null;
12806
13023
  hostSessionId;
12807
13024
  currentSessionId;
13025
+ skillsService;
12808
13026
  constructor(deps) {
12809
13027
  this.config = deps.config;
12810
13028
  this.registry = deps.registry;
@@ -12823,6 +13041,7 @@ var Session = class {
12823
13041
  this.settingsFn = deps.settings;
12824
13042
  this.hostSessionId = deps.sessionId;
12825
13043
  this.currentSessionId = deps.sessionId ?? mintSessionId();
13044
+ this.skillsService = deps.skillsService ?? new SkillsService(this.workspaceRootFn());
12826
13045
  this.approvals = new PendingApprovals({
12827
13046
  onRequest: ({ id, request }) => this.broadcast({
12828
13047
  type: "approval_request",
@@ -12917,7 +13136,7 @@ var Session = class {
12917
13136
  onTaskChanged: () => {
12918
13137
  if (!this.plan) return;
12919
13138
  const tasks = this.store.allTasks;
12920
- this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
13139
+ this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
12921
13140
  },
12922
13141
  onQueueReady: () => {
12923
13142
  this.broadcast({ type: "queue_ready" });
@@ -12935,7 +13154,7 @@ var Session = class {
12935
13154
  onTick: () => {
12936
13155
  if (!this.plan) return;
12937
13156
  const tasks = this.store.allTasks;
12938
- this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
13157
+ this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
12939
13158
  },
12940
13159
  onExecutionComplete: () => {
12941
13160
  if (!this.plan) return;
@@ -13147,7 +13366,7 @@ var Session = class {
13147
13366
  */
13148
13367
  async startPlanning(goal, runners, options) {
13149
13368
  this.plan = null;
13150
- this.goal = goal;
13369
+ this.goal = this.resolveSkillInvocation(goal);
13151
13370
  this.remintSessionId();
13152
13371
  this.beginFreshPlan();
13153
13372
  const enabled = this.config.enabledRunners;
@@ -13156,7 +13375,7 @@ var Session = class {
13156
13375
  const modelsByRunner = await this.modelResolver.modelsForRunners(chosenRunners);
13157
13376
  this.modelsCache = modelsByRunner;
13158
13377
  const runnerModes = this.runnerModesFor(chosenRunners);
13159
- const { grillMeEnabled, prdEnabled, reviewEnabled, verificationEnabled, researchSubagentsEnabled, modelAllowlist } = this.settingsFn();
13378
+ const { verificationEnabled, modelAllowlist } = this.settingsFn();
13160
13379
  this.currentAllowlist = modelAllowlist ?? {};
13161
13380
  const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
13162
13381
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -13166,22 +13385,18 @@ var Session = class {
13166
13385
  status: "draft",
13167
13386
  runners: chosenRunners,
13168
13387
  lastUpdated: now,
13169
- researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: goal, timestamp: now }],
13170
- conversationHistory: [{ role: "user", content: goal, timestamp: now }]
13388
+ researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: this.goal, timestamp: now }],
13389
+ conversationHistory: [{ role: "user", content: this.goal, timestamp: now }]
13171
13390
  };
13172
13391
  const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
13173
13392
  try {
13174
13393
  const turn = await this.aiService.startConversation({
13175
- goal,
13394
+ goal: this.goal,
13176
13395
  runners: chosenRunners,
13177
13396
  modelsByRunner: filteredModels,
13178
13397
  runnerModes,
13179
13398
  autonomousDefault: this.config.autonomousMode,
13180
- grillMeEnabled: grillMeEnabled ?? false,
13181
- prdEnabled: prdEnabled ?? false,
13182
- reviewEnabled: reviewEnabled ?? false,
13183
13399
  verificationEnabled: verificationEnabled ?? false,
13184
- researchSubagentsEnabled: researchSubagentsEnabled ?? false,
13185
13400
  fs: this.fsAdapter,
13186
13401
  fetcher: this.fetcher,
13187
13402
  onProgress: (p) => this.translateProgress(p),
@@ -13193,21 +13408,22 @@ var Session = class {
13193
13408
  }
13194
13409
  }
13195
13410
  /**
13196
- * Every subsequent user reply in the planning conversation — grill-me
13197
- * answers, PRD accept/adjust, outline confirm. One branch, no phase ladder.
13411
+ * Every subsequent user reply in the planning conversation — clarifying
13412
+ * answers, outline confirm. One branch, no phase ladder.
13198
13413
  */
13199
13414
  async continueConversation(userMessage, options) {
13200
13415
  if (!this.plan) throw new Error("No planning conversation to continue");
13201
13416
  const priorHistory = this.plan.conversationHistory ?? [];
13202
13417
  const priorResearchLog = this.plan.researchLog ?? [];
13203
13418
  const now = (/* @__PURE__ */ new Date()).toISOString();
13204
- this.appendTranscript("user", userMessage, now);
13419
+ const resolved = this.resolveSkillInvocation(userMessage);
13420
+ this.appendTranscript("user", resolved, now);
13205
13421
  const historyAfterOwnAppend = this.plan.conversationHistory;
13206
- this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: userMessage, timestamp: now }];
13422
+ this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: resolved, timestamp: now }];
13207
13423
  const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
13208
13424
  const outgoing = contextBlock ? `${contextBlock}
13209
13425
 
13210
- ${userMessage}` : userMessage;
13426
+ ${resolved}` : resolved;
13211
13427
  const aiService = this.aiService;
13212
13428
  const canContinueLive = aiService.hasActiveConversation() && (aiService.conversationMatchesConfig?.() ?? true);
13213
13429
  const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
@@ -13347,8 +13563,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
13347
13563
  * The catalog the planner may actually draw from — model ids and task-mode
13348
13564
  * ids, per selected runner — emitted on EVERY turn, before any plan exists
13349
13565
  * or after. The system prompt shows this once at conversation start; a long
13350
- * grill-me interview or PRD negotiation outlives that single showing and the
13351
- * planner starts misquoting it, so this re-states it per turn instead.
13566
+ * clarifying conversation outlives that single showing and the planner
13567
+ * starts misquoting it, so this re-states it per turn instead.
13352
13568
  *
13353
13569
  * Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
13354
13570
  * one the system prompt used — a restricted allowlist stays a hard bound on
@@ -13479,7 +13695,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
13479
13695
  const modelsByRunner = await this.modelResolver.modelsForRunners(runners);
13480
13696
  this.modelsCache = modelsByRunner;
13481
13697
  const runnerModes = this.runnerModesFor(runners);
13482
- const { grillMeEnabled, prdEnabled, reviewEnabled, verificationEnabled, researchSubagentsEnabled, modelAllowlist } = this.settingsFn();
13698
+ const { verificationEnabled, modelAllowlist } = this.settingsFn();
13483
13699
  this.currentAllowlist = modelAllowlist ?? {};
13484
13700
  const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
13485
13701
  const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
@@ -13489,11 +13705,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
13489
13705
  modelsByRunner: filteredModels,
13490
13706
  runnerModes,
13491
13707
  autonomousDefault: this.config.autonomousMode,
13492
- grillMeEnabled: grillMeEnabled ?? false,
13493
- prdEnabled: prdEnabled ?? false,
13494
- reviewEnabled: reviewEnabled ?? false,
13495
13708
  verificationEnabled: verificationEnabled ?? false,
13496
- researchSubagentsEnabled: researchSubagentsEnabled ?? false,
13497
13709
  fs: this.fsAdapter,
13498
13710
  fetcher: this.fetcher,
13499
13711
  onProgress: (p) => this.translateProgress(p),
@@ -13506,6 +13718,17 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
13506
13718
  get isConversationActive() {
13507
13719
  return this.aiService.hasActiveConversation();
13508
13720
  }
13721
+ /**
13722
+ * Intercept a skill invocation (/skill-name) and substitute the skill's
13723
+ * markdown content BEFORE the message reaches the planner. Prevents runners
13724
+ * (Claude Code, OpenCode) from trying to resolve the skill in their own
13725
+ * directory instead of .ordewell/skills/. An unknown skill is surfaced to
13726
+ * the planner as a notice naming what IS available, rather than passing a
13727
+ * bare /unknown through to be mis-resolved.
13728
+ */
13729
+ resolveSkillInvocation(text) {
13730
+ return resolveSkillInvocation(text, this.skillsService);
13731
+ }
13509
13732
  /** Append one dialogue entry to the persisted transcript. Callers persist via the mutatePlan ritual. */
13510
13733
  appendTranscript(role, content, timestamp, kind) {
13511
13734
  if (!this.plan) return;
@@ -14314,17 +14537,25 @@ var HeadlessSession = class extends AbstractTerminalSession {
14314
14537
  spawnImpl;
14315
14538
  process = null;
14316
14539
  outputBuffer = "";
14540
+ controlStream = null;
14317
14541
  get isStarted() {
14318
14542
  return this.process !== null;
14319
14543
  }
14320
- start(launch, cwd, resolvedPath, env) {
14544
+ start(launch, cwd, resolvedPath, env, options) {
14321
14545
  if (this.exited || this.process) return;
14546
+ const stdio = ["pipe", "pipe", "pipe"];
14547
+ if (options?.controlChannel) stdio.push("pipe");
14322
14548
  this.process = this.spawnImpl(launch.file, launch.args, {
14323
14549
  env: withPath(process.env, resolvedPath, env),
14324
- stdio: ["pipe", "pipe", "pipe"],
14550
+ stdio,
14325
14551
  cwd,
14326
14552
  windowsVerbatimArguments: launch.verbatim
14327
14553
  });
14554
+ if (options?.controlChannel) {
14555
+ this.controlStream = this.process.stdio?.[3] ?? null;
14556
+ this.controlStream?.on("error", () => {
14557
+ });
14558
+ }
14328
14559
  const onData = (data) => {
14329
14560
  const raw = data.toString();
14330
14561
  this.outputBuffer += stripAnsi(raw);
@@ -14357,6 +14588,12 @@ Process error: ${err.message}
14357
14588
  this.process.stdin.write(text);
14358
14589
  }
14359
14590
  }
14591
+ /** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
14592
+ writeControl(text) {
14593
+ if (this.controlStream && !this.controlStream.destroyed) {
14594
+ this.controlStream.write(text);
14595
+ }
14596
+ }
14360
14597
  };
14361
14598
  var HeadlessRunner = class extends AbstractRunner {
14362
14599
  spawnImpl;
@@ -14382,7 +14619,7 @@ var HeadlessRunner = class extends AbstractRunner {
14382
14619
  return new HeadlessSession(id, taskId, this.spawnImpl);
14383
14620
  }
14384
14621
  /** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
14385
- async prepareLaunch(opts) {
14622
+ async prepareLaunch(opts, ptyOptions) {
14386
14623
  const interactive = this.defaultInteractive;
14387
14624
  const invocation = buildRunnerInvocation({
14388
14625
  runner: opts.runner,
@@ -14399,7 +14636,7 @@ var HeadlessRunner = class extends AbstractRunner {
14399
14636
  const manifest = opts.registry?.get(opts.runner)?.manifest;
14400
14637
  const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
14401
14638
  const resolvedPath = await this.resolvePath();
14402
- const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args) : { command: invocation.command, args: invocation.args };
14639
+ const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
14403
14640
  return {
14404
14641
  launch: await planDirectLaunch(command, args, this.launchDeps),
14405
14642
  resolvedPath,
@@ -14721,13 +14958,12 @@ var TmuxRunner = class extends AbstractRunner {
14721
14958
  // src/utils/daemonToken.ts
14722
14959
  var import_crypto2 = require("crypto");
14723
14960
  var import_fs2 = require("fs");
14724
- var import_os2 = require("os");
14725
14961
  var import_path2 = require("path");
14726
14962
  var BEARER_PREFIX = "bearer ";
14727
14963
  var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
14728
14964
  var DAEMON_SUBPROTOCOL = "ordewell.v1";
14729
14965
  function configDir() {
14730
- return (0, import_path2.join)((0, import_os2.homedir)(), ".config", "ordewell");
14966
+ return globalDataDir();
14731
14967
  }
14732
14968
  function daemonTokenPath(port) {
14733
14969
  return (0, import_path2.join)(configDir(), `server-${port}.token`);
@@ -14791,19 +15027,18 @@ function tokensMatch(presented, expected) {
14791
15027
  }
14792
15028
 
14793
15029
  // src/plugins/RunnerRegistry.ts
14794
- var fs7 = __toESM(require("fs"));
14795
- var path14 = __toESM(require("path"));
14796
- var os5 = __toESM(require("os"));
15030
+ var fs9 = __toESM(require("fs"));
15031
+ var path17 = __toESM(require("path"));
15032
+ var os4 = __toESM(require("os"));
14797
15033
  var import_child_process9 = require("child_process");
14798
15034
  var import_crypto3 = require("crypto");
14799
15035
 
14800
15036
  // src/plugins/FsPluginStore.ts
14801
- var fs6 = __toESM(require("fs"));
14802
- var path13 = __toESM(require("path"));
14803
- var os4 = __toESM(require("os"));
15037
+ var fs8 = __toESM(require("fs"));
15038
+ var path16 = __toESM(require("path"));
14804
15039
 
14805
15040
  // src/plugins/pluginNames.ts
14806
- var path12 = __toESM(require("path"));
15041
+ var path15 = __toESM(require("path"));
14807
15042
  var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
14808
15043
  var MAX_PLUGIN_NAME_LENGTH = 64;
14809
15044
  function isPlainPluginName(name) {
@@ -14818,10 +15053,10 @@ function assertPlainPluginName(name) {
14818
15053
  }
14819
15054
  function resolvePluginInstallDir(pluginsDir, name) {
14820
15055
  assertPlainPluginName(name);
14821
- const root = path12.resolve(pluginsDir);
14822
- const dest = path12.resolve(root, name);
14823
- const relative3 = path12.relative(root, dest);
14824
- if (relative3 !== name || relative3 === "" || path12.isAbsolute(relative3)) {
15056
+ const root = path15.resolve(pluginsDir);
15057
+ const dest = path15.resolve(root, name);
15058
+ const relative3 = path15.relative(root, dest);
15059
+ if (relative3 !== name || relative3 === "" || path15.isAbsolute(relative3)) {
14825
15060
  throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
14826
15061
  }
14827
15062
  return dest;
@@ -14836,13 +15071,13 @@ function isValidManifest(obj) {
14836
15071
 
14837
15072
  // src/plugins/FsPluginStore.ts
14838
15073
  function userPluginsDir() {
14839
- return path13.join(os4.homedir(), ".config", "ordewell", "plugins");
15074
+ return path16.join(globalDataDir(), "plugins");
14840
15075
  }
14841
15076
  function loadManifestFromDir(dir) {
14842
- const manifestPath = path13.join(dir, "manifest.json");
14843
- if (!fs6.existsSync(manifestPath)) return null;
15077
+ const manifestPath = path16.join(dir, "manifest.json");
15078
+ if (!fs8.existsSync(manifestPath)) return null;
14844
15079
  try {
14845
- const raw = fs6.readFileSync(manifestPath, "utf8");
15080
+ const raw = fs8.readFileSync(manifestPath, "utf8");
14846
15081
  const parsed = JSON.parse(raw);
14847
15082
  if (!isValidManifest(parsed)) return null;
14848
15083
  return parsed;
@@ -14850,31 +15085,31 @@ function loadManifestFromDir(dir) {
14850
15085
  return null;
14851
15086
  }
14852
15087
  }
14853
- function copyDirSync(src, dest) {
15088
+ function copyDirSync3(src, dest) {
14854
15089
  ensureDir(dest);
14855
- const entries = fs6.readdirSync(src, { withFileTypes: true });
15090
+ const entries = fs8.readdirSync(src, { withFileTypes: true });
14856
15091
  for (const entry of entries) {
14857
- const srcPath = path13.join(src, entry.name);
14858
- const destPath = path13.join(dest, entry.name);
15092
+ const srcPath = path16.join(src, entry.name);
15093
+ const destPath = path16.join(dest, entry.name);
14859
15094
  if (entry.isDirectory()) {
14860
- copyDirSync(srcPath, destPath);
15095
+ copyDirSync3(srcPath, destPath);
14861
15096
  } else {
14862
- fs6.copyFileSync(srcPath, destPath);
15097
+ fs8.copyFileSync(srcPath, destPath);
14863
15098
  }
14864
15099
  }
14865
15100
  }
14866
15101
  function rmDirSync(dir) {
14867
- if (!fs6.existsSync(dir)) return;
14868
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
15102
+ if (!fs8.existsSync(dir)) return;
15103
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
14869
15104
  for (const entry of entries) {
14870
- const p = path13.join(dir, entry.name);
15105
+ const p = path16.join(dir, entry.name);
14871
15106
  if (entry.isDirectory()) {
14872
15107
  rmDirSync(p);
14873
15108
  } else {
14874
- fs6.unlinkSync(p);
15109
+ fs8.unlinkSync(p);
14875
15110
  }
14876
15111
  }
14877
- fs6.rmdirSync(dir);
15112
+ fs8.rmdirSync(dir);
14878
15113
  }
14879
15114
  var FsPluginStore = class {
14880
15115
  getUserPluginsDir() {
@@ -14882,10 +15117,10 @@ var FsPluginStore = class {
14882
15117
  }
14883
15118
  listUserPluginDirs() {
14884
15119
  const dir = userPluginsDir();
14885
- if (!fs6.existsSync(dir)) return [];
15120
+ if (!fs8.existsSync(dir)) return [];
14886
15121
  let entries;
14887
15122
  try {
14888
- entries = fs6.readdirSync(dir, { withFileTypes: true });
15123
+ entries = fs8.readdirSync(dir, { withFileTypes: true });
14889
15124
  } catch {
14890
15125
  return [];
14891
15126
  }
@@ -14893,7 +15128,7 @@ var FsPluginStore = class {
14893
15128
  }
14894
15129
  listDir(dir) {
14895
15130
  try {
14896
- return fs6.readdirSync(dir);
15131
+ return fs8.readdirSync(dir);
14897
15132
  } catch {
14898
15133
  return [];
14899
15134
  }
@@ -14902,7 +15137,7 @@ var FsPluginStore = class {
14902
15137
  return loadManifestFromDir(pluginDir);
14903
15138
  }
14904
15139
  copyDir(sourceDir, destDir) {
14905
- copyDirSync(sourceDir, destDir);
15140
+ copyDirSync3(sourceDir, destDir);
14906
15141
  }
14907
15142
  removeDir(dir) {
14908
15143
  rmDirSync(dir);
@@ -14911,20 +15146,20 @@ var FsPluginStore = class {
14911
15146
  ensureDir(dir);
14912
15147
  }
14913
15148
  writeFile(filePath, content) {
14914
- fs6.writeFileSync(filePath, content);
15149
+ fs8.writeFileSync(filePath, content);
14915
15150
  }
14916
15151
  readFile(filePath) {
14917
15152
  try {
14918
- return fs6.readFileSync(filePath, "utf8");
15153
+ return fs8.readFileSync(filePath, "utf8");
14919
15154
  } catch {
14920
15155
  return null;
14921
15156
  }
14922
15157
  }
14923
- dirExists(path17) {
14924
- return fs6.existsSync(path17) && fs6.statSync(path17).isDirectory();
15158
+ dirExists(path20) {
15159
+ return fs8.existsSync(path20) && fs8.statSync(path20).isDirectory();
14925
15160
  }
14926
- exists(path17) {
14927
- return fs6.existsSync(path17);
15161
+ exists(path20) {
15162
+ return fs8.existsSync(path20);
14928
15163
  }
14929
15164
  };
14930
15165
 
@@ -15351,7 +15586,7 @@ var RunnerRegistry = class {
15351
15586
  return entry?.source === "builtin";
15352
15587
  }
15353
15588
  installFromPath(sourcePath) {
15354
- const absPath = path14.resolve(sourcePath);
15589
+ const absPath = path17.resolve(sourcePath);
15355
15590
  if (!this.store.dirExists(absPath)) {
15356
15591
  throw new Error(`Plugin source must be a directory: ${absPath}`);
15357
15592
  }
@@ -15363,7 +15598,7 @@ var RunnerRegistry = class {
15363
15598
  }
15364
15599
  installFromGit(url) {
15365
15600
  const validated = assertInstallablePluginUrl(url);
15366
- const tmpDir = path14.join(os5.tmpdir(), `ordewell-plugin-${(0, import_crypto3.randomBytes)(12).toString("hex")}`);
15601
+ const tmpDir = path17.join(os4.tmpdir(), `ordewell-plugin-${(0, import_crypto3.randomBytes)(12).toString("hex")}`);
15367
15602
  this.store.ensureDir(tmpDir);
15368
15603
  try {
15369
15604
  try {
@@ -15376,7 +15611,7 @@ var RunnerRegistry = class {
15376
15611
  const hasManifest = contents.some((f) => f === "manifest.json");
15377
15612
  let sourceDir = tmpDir;
15378
15613
  if (!hasManifest) {
15379
- const onlyEntry = contents.length === 1 ? path14.join(tmpDir, contents[0]) : void 0;
15614
+ const onlyEntry = contents.length === 1 ? path17.join(tmpDir, contents[0]) : void 0;
15380
15615
  if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
15381
15616
  throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
15382
15617
  }
@@ -15424,7 +15659,7 @@ var RunnerRegistry = class {
15424
15659
  }
15425
15660
  createSkeleton(name, outputDir) {
15426
15661
  assertPlainPluginName(name);
15427
- const dir = path14.resolve(outputDir, name);
15662
+ const dir = path17.resolve(outputDir, name);
15428
15663
  this.store.ensureDir(dir);
15429
15664
  const manifest = {
15430
15665
  name,
@@ -15456,8 +15691,8 @@ var RunnerRegistry = class {
15456
15691
  ]
15457
15692
  }
15458
15693
  };
15459
- this.store.writeFile(path14.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
15460
- const runSh = path14.join(dir, "run.sh");
15694
+ this.store.writeFile(path17.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
15695
+ const runSh = path17.join(dir, "run.sh");
15461
15696
  const scriptContent = `#!/usr/bin/env bash
15462
15697
  # Custom runner script for ${name}
15463
15698
  # Receives:
@@ -15483,30 +15718,30 @@ echo "Running ${name} with prompt: $prompt"
15483
15718
  };
15484
15719
  function fsChmodSync(fpath, mode) {
15485
15720
  try {
15486
- fs7.chmodSync(fpath, mode);
15721
+ fs9.chmodSync(fpath, mode);
15487
15722
  } catch {
15488
15723
  }
15489
15724
  }
15490
15725
 
15491
15726
  // src/utils/stateStore.ts
15492
- var fs8 = __toESM(require("fs"));
15493
- var path15 = __toESM(require("path"));
15727
+ var fs10 = __toESM(require("fs"));
15728
+ var path18 = __toESM(require("path"));
15494
15729
  var STATE_FILE = "state.json";
15495
15730
  function getStatePath(baseDir) {
15496
- return path15.join(getStateDir(baseDir), STATE_FILE);
15731
+ return path18.join(getStateDir(baseDir), STATE_FILE);
15497
15732
  }
15498
15733
  function saveState(plan, baseDir) {
15499
15734
  const statePath = getStatePath(baseDir ?? process.cwd());
15500
- ensureDir(path15.dirname(statePath));
15735
+ ensureDir(path18.dirname(statePath));
15501
15736
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
15502
- fs8.writeFileSync(statePath, JSON.stringify(plan, null, 2));
15737
+ fs10.writeFileSync(statePath, JSON.stringify(plan, null, 2));
15503
15738
  }
15504
15739
  function loadState(baseDir, logger = defaultLogger) {
15505
15740
  const dir = baseDir ?? process.cwd();
15506
15741
  const statePath = getStatePath(dir);
15507
- if (!fs8.existsSync(statePath)) return null;
15742
+ if (!fs10.existsSync(statePath)) return null;
15508
15743
  try {
15509
- const raw = fs8.readFileSync(statePath, "utf-8");
15744
+ const raw = fs10.readFileSync(statePath, "utf-8");
15510
15745
  const parsed = JSON.parse(raw);
15511
15746
  if (!Array.isArray(parsed.runners)) {
15512
15747
  logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
@@ -15521,29 +15756,24 @@ function loadState(baseDir, logger = defaultLogger) {
15521
15756
  function clearState(baseDir) {
15522
15757
  const dir = baseDir ?? process.cwd();
15523
15758
  const statePath = getStatePath(dir);
15524
- if (fs8.existsSync(statePath)) fs8.unlinkSync(statePath);
15759
+ if (fs10.existsSync(statePath)) fs10.unlinkSync(statePath);
15525
15760
  }
15526
15761
  function stateExists(baseDir) {
15527
15762
  const dir = baseDir ?? process.cwd();
15528
- return fs8.existsSync(getStatePath(dir));
15763
+ return fs10.existsSync(getStatePath(dir));
15529
15764
  }
15530
15765
 
15531
15766
  // src/services/SettingsService.ts
15532
- var fs9 = __toESM(require("fs"));
15533
- var path16 = __toESM(require("path"));
15534
- var os6 = __toESM(require("os"));
15767
+ var fs11 = __toESM(require("fs"));
15768
+ var path19 = __toESM(require("path"));
15535
15769
  var DEFAULTS = {
15536
- grillMe: { enabled: false },
15537
15770
  tdd: { enabled: true },
15538
- prd: { enabled: false },
15539
- review: { enabled: false },
15540
- verification: { enabled: false },
15541
- researchSubagents: { enabled: false }
15771
+ verification: { enabled: false }
15542
15772
  };
15543
15773
  function getSettingsPath() {
15544
15774
  const override = process.env.ORDEWELL_SETTINGS_PATH;
15545
15775
  if (override && override.trim()) return override.trim();
15546
- return path16.join(os6.homedir(), ".config", "ordewell", "settings.json");
15776
+ return path19.join(globalDataDir(), "settings.json");
15547
15777
  }
15548
15778
  function readPlannerModels(raw) {
15549
15779
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -15576,43 +15806,19 @@ var SettingsService = class {
15576
15806
  }
15577
15807
  fileMtimeMs() {
15578
15808
  try {
15579
- return fs9.statSync(this.filePath).mtimeMs;
15809
+ return fs11.statSync(this.filePath).mtimeMs;
15580
15810
  } catch {
15581
15811
  return null;
15582
15812
  }
15583
15813
  }
15584
- getGrillMe() {
15585
- return this.getAll().grillMe.enabled;
15586
- }
15587
15814
  getTdd() {
15588
15815
  return this.getAll().tdd.enabled;
15589
15816
  }
15590
- getPrd() {
15591
- return this.getAll().prd.enabled;
15592
- }
15593
- setGrillMe(enabled) {
15594
- this.getAll();
15595
- this.cache.grillMe.enabled = enabled;
15596
- this.persist();
15597
- }
15598
15817
  setTdd(enabled) {
15599
15818
  this.getAll();
15600
15819
  this.cache.tdd.enabled = enabled;
15601
15820
  this.persist();
15602
15821
  }
15603
- setPrd(enabled) {
15604
- this.getAll();
15605
- this.cache.prd.enabled = enabled;
15606
- this.persist();
15607
- }
15608
- getReview() {
15609
- return this.getAll().review.enabled;
15610
- }
15611
- setReview(enabled) {
15612
- this.getAll();
15613
- this.cache.review.enabled = enabled;
15614
- this.persist();
15615
- }
15616
15822
  getVerification() {
15617
15823
  return this.getAll().verification.enabled;
15618
15824
  }
@@ -15621,14 +15827,6 @@ var SettingsService = class {
15621
15827
  this.cache.verification.enabled = enabled;
15622
15828
  this.persist();
15623
15829
  }
15624
- getResearchSubagents() {
15625
- return this.getAll().researchSubagents.enabled;
15626
- }
15627
- setResearchSubagents(enabled) {
15628
- this.getAll();
15629
- this.cache.researchSubagents.enabled = enabled;
15630
- this.persist();
15631
- }
15632
15830
  getModelAllowlist(runner) {
15633
15831
  return this.getAll().modelAllowlist?.[runner];
15634
15832
  }
@@ -15678,18 +15876,13 @@ var SettingsService = class {
15678
15876
  this.persist();
15679
15877
  }
15680
15878
  load() {
15879
+ migrateOldConfigDir();
15681
15880
  try {
15682
- if (fs9.existsSync(this.filePath)) {
15683
- const raw = JSON.parse(fs9.readFileSync(this.filePath, "utf-8"));
15881
+ if (fs11.existsSync(this.filePath)) {
15882
+ const raw = JSON.parse(fs11.readFileSync(this.filePath, "utf-8"));
15684
15883
  const settings = {
15685
- grillMe: { enabled: raw.grillMe?.enabled ?? DEFAULTS.grillMe.enabled },
15686
15884
  tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
15687
- prd: { enabled: raw.prd?.enabled ?? DEFAULTS.prd.enabled },
15688
- // `verify` is the legacy key for review mode; keep reading it so old settings files survive the rename.
15689
- // The evidence-based verification mode uses the distinct `verification` key to avoid colliding with it.
15690
- review: { enabled: raw.review?.enabled ?? raw.verify?.enabled ?? DEFAULTS.review.enabled },
15691
- verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled },
15692
- researchSubagents: { enabled: raw.researchSubagents?.enabled ?? DEFAULTS.researchSubagents.enabled }
15885
+ verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
15693
15886
  };
15694
15887
  if (raw.modelAllowlist !== void 0) {
15695
15888
  settings.modelAllowlist = raw.modelAllowlist;
@@ -15705,14 +15898,14 @@ var SettingsService = class {
15705
15898
  }
15706
15899
  } catch {
15707
15900
  }
15708
- return { ...DEFAULTS, grillMe: { ...DEFAULTS.grillMe }, tdd: { ...DEFAULTS.tdd }, prd: { ...DEFAULTS.prd }, review: { ...DEFAULTS.review }, verification: { ...DEFAULTS.verification }, researchSubagents: { ...DEFAULTS.researchSubagents } };
15901
+ return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
15709
15902
  }
15710
15903
  persist() {
15711
- const dir = path16.dirname(this.filePath);
15712
- if (!fs9.existsSync(dir)) {
15713
- fs9.mkdirSync(dir, { recursive: true });
15904
+ const dir = path19.dirname(this.filePath);
15905
+ if (!fs11.existsSync(dir)) {
15906
+ fs11.mkdirSync(dir, { recursive: true });
15714
15907
  }
15715
- fs9.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
15908
+ fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
15716
15909
  this.cachedMtimeMs = this.fileMtimeMs();
15717
15910
  }
15718
15911
  };
@@ -15759,9 +15952,11 @@ var PlannerModelMemory = class {
15759
15952
  AbstractRunner,
15760
15953
  AbstractTerminalSession,
15761
15954
  ApprovalPolicy,
15955
+ BUILTIN_SKILL_NAMES,
15762
15956
  BaseAiService,
15763
15957
  BaseConfig,
15764
15958
  BaseFileSystem,
15959
+ CHECKPOINT_TRUNCATE_LENGTH,
15765
15960
  CLAUDE_CODE_MANIFEST,
15766
15961
  CLI_PROVIDERS,
15767
15962
  CMD_EXE_MAX_COMMAND_LINE,
@@ -15817,6 +16012,7 @@ var PlannerModelMemory = class {
15817
16012
  SYMBOL_LANGUAGES,
15818
16013
  Session,
15819
16014
  SettingsService,
16015
+ SkillsService,
15820
16016
  StdioAgentAdapter,
15821
16017
  TASK_OPS_ENVELOPE_KEY,
15822
16018
  TASK_QUERY_ANSWER_OR_OPS,
@@ -15882,6 +16078,7 @@ var PlannerModelMemory = class {
15882
16078
  configuredProviders,
15883
16079
  createAiService,
15884
16080
  createEmptyPlan,
16081
+ createSkillsService,
15885
16082
  createTask,
15886
16083
  daemonTokenPath,
15887
16084
  defaultLogger,
@@ -15908,12 +16105,14 @@ var PlannerModelMemory = class {
15908
16105
  filterModelsForPrompt,
15909
16106
  filteredBuildModes,
15910
16107
  flattenTasks,
16108
+ flattenTasksWithParents,
15911
16109
  formatSearchOutput,
15912
16110
  generatePlanWithRepair,
15913
16111
  getLatestSession,
15914
16112
  getProviderMeta,
15915
16113
  getSettingsPath,
15916
16114
  getStateDir,
16115
+ globalDataDir,
15917
16116
  grantScopeFor,
15918
16117
  hasTmux,
15919
16118
  includeGlobFor,
@@ -15933,6 +16132,7 @@ var PlannerModelMemory = class {
15933
16132
  looksLikePlanAttempt,
15934
16133
  mapAgentTool,
15935
16134
  migrateLegacyPlan,
16135
+ migrateOldConfigDir,
15936
16136
  migratePlanState,
15937
16137
  migrateTask,
15938
16138
  mintDaemonToken,
@@ -15967,10 +16167,12 @@ var PlannerModelMemory = class {
15967
16167
  resolveArgs,
15968
16168
  resolveDefaultMode,
15969
16169
  resolveModelShortcut,
16170
+ resolveOrderLabel,
15970
16171
  resolvePluginInstallDir,
15971
16172
  resolveProvider,
15972
16173
  resolveProviderFromPrefix,
15973
16174
  resolveResearchShell,
16175
+ resolveSkillInvocation,
15974
16176
  resolveTaskMode,
15975
16177
  resolveWithin,
15976
16178
  retargetTaskRunner,
@@ -15993,6 +16195,7 @@ var PlannerModelMemory = class {
15993
16195
  summarizeOutput,
15994
16196
  summarizeToolCall,
15995
16197
  taskOpsRejectedPrompt,
16198
+ taskOrderLabel,
15996
16199
  taskQuerySignature,
15997
16200
  textHasTaskOps,
15998
16201
  textHasTaskQuery,
@@ -16002,6 +16205,7 @@ var PlannerModelMemory = class {
16002
16205
  toOrchestratorOptions,
16003
16206
  tokenSubprotocols,
16004
16207
  tokensMatch,
16208
+ truncateCheckpointSummary,
16005
16209
  truncatedPlanReEmitPrompt,
16006
16210
  updateTaskInPlan,
16007
16211
  validateModifiedPlan,