@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.mjs CHANGED
@@ -13,8 +13,9 @@ import {
13
13
  resolveTaskMode,
14
14
  runnerModesFrom,
15
15
  validatePlanModification
16
- } from "./chunk-TUGJXD4G.mjs";
16
+ } from "./chunk-UEJ3SZQ7.mjs";
17
17
  import {
18
+ CHECKPOINT_TRUNCATE_LENGTH,
18
19
  UPDATABLE_FIELDS,
19
20
  applyTaskOps,
20
21
  canMergeTasks,
@@ -26,12 +27,17 @@ import {
26
27
  dependencyCandidates,
27
28
  dependentsOf,
28
29
  effectiveAllowlist,
30
+ executionSummary,
29
31
  filterModelsForPrompt,
30
32
  parseTaskOpsJson,
33
+ serializePlan,
34
+ serializeTask,
35
+ serializeTaskStatus,
31
36
  summarizeToolCall,
32
37
  textHasTaskOps,
38
+ truncateCheckpointSummary,
33
39
  validateTaskEdit
34
- } from "./chunk-YI4ZDZEE.mjs";
40
+ } from "./chunk-YTJ7EOV6.mjs";
35
41
  import {
36
42
  PLAN_ENVELOPE_KEY,
37
43
  PlanParseError,
@@ -46,6 +52,7 @@ import {
46
52
  extractObjectWithBalance,
47
53
  extractObjectsWithKey,
48
54
  flattenTasks,
55
+ flattenTasksWithParents,
49
56
  migrateLegacyPlan,
50
57
  migratePlanState,
51
58
  migrateTask,
@@ -56,7 +63,11 @@ import {
56
63
  updateTaskInPlan,
57
64
  validateModifiedPlan,
58
65
  warningsText
59
- } from "./chunk-IO6PEEN6.mjs";
66
+ } from "./chunk-N6HTBUKG.mjs";
67
+ import {
68
+ resolveOrderLabel,
69
+ taskOrderLabel
70
+ } from "./chunk-HPNUFBIT.mjs";
60
71
 
61
72
  // src/interfaces/IFileSystem.ts
62
73
  var GREP_DEFAULT_HEAD_LIMIT = 100;
@@ -2575,7 +2586,7 @@ function redactSecrets(text) {
2575
2586
  const { value, tail } = trimTrailingPunctuation(raw);
2576
2587
  if (value.length < 8) return full;
2577
2588
  if (looksLikeCode(value) || value.startsWith("$")) return full;
2578
- if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
2589
+ if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && (/\d/.test(value) || value.length >= 12)) {
2579
2590
  return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
2580
2591
  }
2581
2592
  if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
@@ -2614,33 +2625,33 @@ function grepOptions(args) {
2614
2625
  };
2615
2626
  }
2616
2627
  var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
2617
- async function executeTool(name, args, fs10, fetcher, signal) {
2618
- const outcome = await runTool(name, args, fs10, fetcher, signal);
2628
+ async function executeTool(name, args, fs12, fetcher, signal) {
2629
+ const outcome = await runTool(name, args, fs12, fetcher, signal);
2619
2630
  return { ...outcome, output: redactSecrets(outcome.output) };
2620
2631
  }
2621
- async function runTool(name, args, fs10, fetcher, signal) {
2632
+ async function runTool(name, args, fs12, fetcher, signal) {
2622
2633
  if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
2623
2634
  switch (name) {
2624
2635
  case "read_file": {
2625
2636
  const offset = num(args.offset);
2626
2637
  const limit = num(args.limit);
2627
2638
  const maxBytes = num(args.maxBytes);
2628
- return fs10.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
2639
+ return fs12.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
2629
2640
  }
2630
2641
  case "read_files": {
2631
2642
  const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
2632
- return fs10.readFiles(paths);
2643
+ return fs12.readFiles(paths);
2633
2644
  }
2634
2645
  case "glob":
2635
- return fs10.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
2646
+ return fs12.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
2636
2647
  case "grep":
2637
- return fs10.grep(String(args.pattern ?? ""), grepOptions(args));
2648
+ return fs12.grep(String(args.pattern ?? ""), grepOptions(args));
2638
2649
  case "find_symbol":
2639
- return fs10.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
2650
+ return fs12.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
2640
2651
  case "list_dir":
2641
- return fs10.listDir(String(args.path ?? ""), num(args.depth));
2652
+ return fs12.listDir(String(args.path ?? ""), num(args.depth));
2642
2653
  case "bash":
2643
- return fs10.bash(String(args.command ?? ""), signal);
2654
+ return fs12.bash(String(args.command ?? ""), signal);
2644
2655
  case "fetch": {
2645
2656
  if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
2646
2657
  const url = String(args.url ?? "");
@@ -3723,14 +3734,7 @@ import { v4 as uuidv4 } from "uuid";
3723
3734
 
3724
3735
  // src/services/plannerModes.ts
3725
3736
  var MODE_TOGGLES = [
3726
- // Both of these interview the user, and a one-shot run has nobody to ask —
3727
- // its prompt says so outright. Their absence there is a decision, not a gap.
3728
- { id: "grill-me", settingsKey: "grillMe", runtimeKey: "grillMeEnabled", scopes: ["chat"] },
3729
- { id: "prd", settingsKey: "prd", runtimeKey: "prdEnabled", scopes: ["chat"] },
3730
- // Structural: append a final review task. Nothing about it needs a dialogue.
3731
- { id: "review", settingsKey: "review", runtimeKey: "reviewEnabled", scopes: ["chat", "one-shot"] },
3732
3737
  { id: "verify", settingsKey: "verification", runtimeKey: "verificationEnabled", scopes: ["chat", "one-shot"] },
3733
- { id: "research-subagents", settingsKey: "researchSubagents", runtimeKey: "researchSubagentsEnabled", scopes: ["chat", "one-shot"] },
3734
3738
  { id: "tdd", settingsKey: "tdd", runtimeKey: "tddEnabled", scopes: ["task"] }
3735
3739
  ];
3736
3740
  function plannerRuntimeToggles(settings) {
@@ -3740,28 +3744,16 @@ function plannerRuntimeToggles(settings) {
3740
3744
  }
3741
3745
  var DEFAULT_PLANNER_MODES = {
3742
3746
  autonomousDefault: true,
3743
- grillMe: false,
3744
- prd: false,
3745
- review: false,
3746
- verification: false,
3747
- researchSubagents: false
3747
+ verification: false
3748
3748
  };
3749
3749
  function plannerModesFrom(settings, autonomousDefault) {
3750
3750
  return {
3751
3751
  autonomousDefault,
3752
- grillMe: settings.grillMeEnabled ?? false,
3753
- prd: settings.prdEnabled ?? false,
3754
- review: settings.reviewEnabled ?? false,
3755
- verification: settings.verificationEnabled ?? false,
3756
- researchSubagents: settings.researchSubagentsEnabled ?? false
3752
+ verification: settings.verificationEnabled ?? false
3757
3753
  };
3758
3754
  }
3759
3755
  var MODE_FIELD = {
3760
- "grill-me": "grillMe",
3761
- prd: "prd",
3762
- review: "review",
3763
3756
  verify: "verification",
3764
- "research-subagents": "researchSubagents",
3765
3757
  tdd: null
3766
3758
  };
3767
3759
  function modesFor(scope, modes) {
@@ -3945,7 +3937,7 @@ function renderTaskQueryAnswer(query, tasks, catalog) {
3945
3937
  }
3946
3938
 
3947
3939
  // src/services/PlanPrompts.ts
3948
- function buildResearchToolsPrompt(subagentsEnabled = false) {
3940
+ function buildResearchToolsPrompt() {
3949
3941
  const lines = [
3950
3942
  "You have access to the following tools to explore the workspace:",
3951
3943
  "",
@@ -3961,15 +3953,11 @@ function buildResearchToolsPrompt(subagentsEnabled = false) {
3961
3953
  "- glob: for finding files by name/path pattern",
3962
3954
  "- grep: for searching inside file contents",
3963
3955
  "",
3964
- "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."
3956
+ "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.",
3957
+ "",
3958
+ '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.',
3959
+ "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."
3965
3960
  ];
3966
- if (subagentsEnabled) {
3967
- lines.push(
3968
- "",
3969
- '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.',
3970
- "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."
3971
- );
3972
- }
3973
3961
  return lines.join("\n");
3974
3962
  }
3975
3963
  function buildSubagentSystemPrompt() {
@@ -4007,11 +3995,11 @@ function buildModeExamplesForRunners(runners) {
4007
3995
  return `${r}: default|plan`;
4008
3996
  }).join(", ");
4009
3997
  }
4010
- function verificationModeBlock(reviewAlsoEnabled = false) {
3998
+ function verificationModeBlock() {
4011
3999
  return [
4012
4000
  "",
4013
4001
  "VERIFICATION MODE:",
4014
- 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).",
4002
+ "Add a FINAL verification task to the end of the plan (highest order number).",
4015
4003
  '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.',
4016
4004
  "This task must:",
4017
4005
  '- Have type "ai" and autonomy "AFK" \u2014 it needs no human input',
@@ -4047,35 +4035,14 @@ function researchPhaseBlock(harnessMode) {
4047
4035
  "- Research and questions can interleave \u2014 you can explore a file, then ask a question grounded in it, in one response"
4048
4036
  ].join("\n");
4049
4037
  }
4050
- function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, grillMeEnabled = false, prdEnabled = false, reviewEnabled = false, verificationEnabled = false, harnessMode = false) {
4038
+ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, verificationEnabled = false, harnessMode = false) {
4051
4039
  const modelsJson = modelsJsonFor(modelsByRunner, runners);
4052
4040
  const modeGuide = runnerModes ? buildModeGuide(runnerModes, autonomousDefault) : buildModeGuideForRunners(runners, autonomousDefault);
4053
4041
  const modeExamples = runnerModes ? Object.entries(runnerModes).map(([r, ms]) => {
4054
4042
  const filtered = filteredBuildModes(ms, autonomousDefault);
4055
4043
  return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
4056
4044
  }).join(", ") : buildModeExamplesForRunners(runners);
4057
- const grillMeBlock = grillMeEnabled ? [
4058
- "",
4059
- "INTERVIEW MODE \u2014 GRILL-ME:",
4060
- "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.",
4061
- "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.",
4062
- "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.",
4063
- "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."
4064
- ].join("\n") : "";
4065
- const prdBlock = prdEnabled ? [
4066
- "",
4067
- "PRD MODE: Before producing the outline, generate a PRD in two steps.",
4068
- "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.",
4069
- "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.",
4070
- '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.',
4071
- "When writing the full PRD, wrap it EXACTLY with these markers so the system can save it automatically:",
4072
- '<!-- ORDEWELL_PRD_START slug="<feature-slug>" -->',
4073
- "... full markdown PRD ...",
4074
- "<!-- ORDEWELL_PRD_END -->",
4075
- "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."
4076
- ].join("\n") : "";
4077
- const reviewBlock = reviewEnabled ? reviewModeBlock() : "";
4078
- const verificationBlock = verificationEnabled ? verificationModeBlock(reviewEnabled) : "";
4045
+ const verificationBlock = verificationEnabled ? verificationModeBlock() : "";
4079
4046
  return buildConversationBody(
4080
4047
  goal,
4081
4048
  context,
@@ -4084,34 +4051,10 @@ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, r
4084
4051
  modeGuide,
4085
4052
  modeExamples,
4086
4053
  harnessMode,
4087
- grillMeBlock,
4088
- prdBlock,
4089
- reviewBlock,
4090
4054
  verificationBlock
4091
4055
  );
4092
4056
  }
4093
- function reviewModeBlock() {
4094
- return [
4095
- "",
4096
- "REVIEW MODE:",
4097
- "Add a FINAL review task to the end of the plan (highest order number).",
4098
- "This task must:",
4099
- '- Have type "ai" and use the STRONGEST available model',
4100
- "- Have dependencies on ALL other AI/user tasks in the plan",
4101
- "- Be assigned appropriately as HITL (human review) since the results need human sign-off",
4102
- "- Its prompt should instruct the agent to:",
4103
- " * Review every completed task's outputs and verify the original goal was met",
4104
- " * Check that each task delivers what its description promised",
4105
- " * Review the work on two axes, reported SEPARATELY so one cannot mask the other:",
4106
- " - 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.",
4107
- " - 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.",
4108
- " * Produce a structured review report listing what was checked and whether each check passed",
4109
- " * Flag any gaps, bugs, or incomplete work",
4110
- " * Output a final PASS/FAIL conclusion with evidence (standards-axis judgement calls alone must not fail the verification)",
4111
- "- Use the same runner as the other tasks"
4112
- ].join("\n");
4113
- }
4114
- function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, grillMeBlock, prdBlock, reviewBlock, verificationBlock) {
4057
+ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, verificationBlock) {
4115
4058
  return [
4116
4059
  "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.",
4117
4060
  "",
@@ -4122,9 +4065,6 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
4122
4065
  '4. After the user confirms the outline, emit the final task plan as a JSON object with a "tasks" array.',
4123
4066
  "",
4124
4067
  researchPhaseBlock(harnessMode),
4125
- grillMeBlock,
4126
- prdBlock,
4127
- reviewBlock,
4128
4068
  verificationBlock,
4129
4069
  "",
4130
4070
  "OUTLINE PHASE:",
@@ -4311,7 +4251,6 @@ function getPlanTemplate() {
4311
4251
  "",
4312
4252
  "TASK MODE:",
4313
4253
  "{{MODE_GUIDE}}",
4314
- "{{REVIEW_BLOCK}}",
4315
4254
  "{{VERIFICATION_BLOCK}}",
4316
4255
  "",
4317
4256
  "CONTEXT:",
@@ -4358,10 +4297,8 @@ function buildPlanPromptBase(userGoal, context, researchResults, modelsByRunner,
4358
4297
  const filtered = filteredBuildModes(ms, autonomousDefault);
4359
4298
  return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
4360
4299
  }).join(", ") : buildModeExamplesForRunners(runners);
4361
- 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 ? `
4362
- ${reviewModeBlock()}
4363
- ` : "").replace("{{VERIFICATION_BLOCK}}", scoped.verification ? `
4364
- ${verificationModeBlock(scoped.review)}
4300
+ 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 ? `
4301
+ ${verificationModeBlock()}
4365
4302
  ` : "").replace("{{MODE_EXAMPLES}}", modeExamples).replace("{{CONTEXT}}", context || "(no additional context)").replace("{{RESEARCH_RESULTS}}", researchResults ? `
4366
4303
 
4367
4304
  RESEARCH FINDINGS:
@@ -4724,8 +4661,8 @@ var SUBAGENT_EXCLUDED = /* @__PURE__ */ new Set(["fetch", "web_search"]);
4724
4661
  function subagentToolSpecs() {
4725
4662
  return RESEARCH_TOOLS.filter((t) => !SUBAGENT_EXCLUDED.has(t.name));
4726
4663
  }
4727
- function withSpawn(includeSubagents) {
4728
- return includeSubagents ? [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC] : RESEARCH_TOOLS;
4664
+ function withSpawn() {
4665
+ return [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC];
4729
4666
  }
4730
4667
  function projectOpenAi(specs) {
4731
4668
  return specs.map((t) => ({
@@ -4746,8 +4683,8 @@ function projectOpenAi(specs) {
4746
4683
  }
4747
4684
  }));
4748
4685
  }
4749
- function toOpenAiTools(includeSubagents = false) {
4750
- return projectOpenAi(withSpawn(includeSubagents));
4686
+ function toOpenAiTools() {
4687
+ return projectOpenAi(withSpawn());
4751
4688
  }
4752
4689
  function toOpenAiSubagentTools() {
4753
4690
  return projectOpenAi(subagentToolSpecs());
@@ -4758,8 +4695,8 @@ var SCHEMA_TYPE = {
4758
4695
  array: SchemaType.ARRAY,
4759
4696
  boolean: SchemaType.BOOLEAN
4760
4697
  };
4761
- function toGeminiToolDeclarations(includeSubagents = false) {
4762
- return withSpawn(includeSubagents).map((t) => {
4698
+ function toGeminiToolDeclarations() {
4699
+ return withSpawn().map((t) => {
4763
4700
  const properties = {};
4764
4701
  for (const [key, param] of Object.entries(t.properties)) {
4765
4702
  properties[key] = {
@@ -4856,33 +4793,10 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
4856
4793
  return (blocks[0].lines.length === 0 ? texts.slice(1) : texts).join("\n");
4857
4794
  }
4858
4795
 
4859
- // src/utils/prdStore.ts
4860
- import * as fs from "fs";
4861
- import * as path4 from "path";
4862
- var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
4863
- function extractPrdBlock(text) {
4864
- const match = PRD_BLOCK_RE.exec(text);
4865
- if (!match) return null;
4866
- const slug2 = sanitizeSlug(match[1]);
4867
- const markdown = match[2].trim();
4868
- if (!slug2 || !markdown) return null;
4869
- return { slug: slug2, markdown };
4870
- }
4871
- function sanitizeSlug(raw) {
4872
- return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
4873
- }
4874
- function savePrdMarkdown(workspace, slug2, markdown) {
4875
- const dir = path4.join(workspace, ".scratch", sanitizeSlug(slug2));
4876
- fs.mkdirSync(dir, { recursive: true });
4877
- const file = path4.join(dir, "PRD.md");
4878
- fs.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
4879
- return file;
4880
- }
4881
-
4882
4796
  // src/services/ContextCollector.ts
4883
4797
  var ORDEWELL_CONTEXT_MAX = 8e3;
4884
- async function collectResearchContext(fs10, runners) {
4885
- const collector = new ContextCollector(fs10);
4798
+ async function collectResearchContext(fs12, runners) {
4799
+ const collector = new ContextCollector(fs12);
4886
4800
  let contextStr = "";
4887
4801
  let firstContext = null;
4888
4802
  for (const r of runners) {
@@ -4909,8 +4823,8 @@ ${firstContext.dirStructure}
4909
4823
  return contextStr;
4910
4824
  }
4911
4825
  var ContextCollector = class {
4912
- constructor(fs10, registry) {
4913
- this.fs = fs10;
4826
+ constructor(fs12, registry) {
4827
+ this.fs = fs12;
4914
4828
  this.registry = registry;
4915
4829
  }
4916
4830
  fs;
@@ -4958,22 +4872,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
4958
4872
  function refused(output) {
4959
4873
  return { success: false, output, truncated: false };
4960
4874
  }
4961
- function nonPromptingFs(fs10) {
4962
- const withinWorkspace = (p) => resolveWithin(fs10.getWorkspaceRoot(), p ?? ".").inside;
4875
+ function nonPromptingFs(fs12) {
4876
+ const withinWorkspace = (p) => resolveWithin(fs12.getWorkspaceRoot(), p ?? ".").inside;
4963
4877
  const outsideRefusal = (p) => refused(
4964
4878
  `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.`
4965
4879
  );
4966
4880
  return {
4967
- getWorkspaceRoot: () => fs10.getWorkspaceRoot(),
4968
- readFile: (p, opts) => withinWorkspace(p) ? fs10.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
4881
+ getWorkspaceRoot: () => fs12.getWorkspaceRoot(),
4882
+ readFile: (p, opts) => withinWorkspace(p) ? fs12.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
4969
4883
  readFiles: (paths) => {
4970
4884
  const escaping = paths.find((p) => !withinWorkspace(p));
4971
- return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs10.readFiles(paths);
4885
+ return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs12.readFiles(paths);
4972
4886
  },
4973
- glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs10.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
4974
- grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs10.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
4975
- findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs10.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
4976
- listDir: (p, depth) => withinWorkspace(p) ? fs10.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
4887
+ glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
4888
+ grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
4889
+ findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs12.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
4890
+ listDir: (p, depth) => withinWorkspace(p) ? fs12.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
4977
4891
  bash: async (command, signal) => {
4978
4892
  const dialect = resolveResearchShell().dialect;
4979
4893
  const { tier, reason } = classifyCommand(command, { dialect });
@@ -4984,13 +4898,13 @@ function nonPromptingFs(fs10) {
4984
4898
  }
4985
4899
  const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
4986
4900
  if (escaping) return outsideRefusal(escaping);
4987
- return fs10.bash(command, signal);
4901
+ return fs12.bash(command, signal);
4988
4902
  }
4989
4903
  };
4990
4904
  }
4991
4905
  async function runLoop(prompt, deps) {
4992
4906
  if (deps.signal?.aborted) return "[research agent aborted before starting]";
4993
- const fs10 = nonPromptingFs(deps.fs);
4907
+ const fs12 = nonPromptingFs(deps.fs);
4994
4908
  const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
4995
4909
  let turn = await chat.sendMessage(prompt, deps.signal);
4996
4910
  for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
@@ -5007,7 +4921,7 @@ ${turn.text}`;
5007
4921
  if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
5008
4922
  const toolArgs = JSON.stringify(tc.args);
5009
4923
  deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
5010
- const res = await executeTool(tc.name, args, fs10, void 0, deps.signal);
4924
+ const res = await executeTool(tc.name, args, fs12, void 0, deps.signal);
5011
4925
  const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
5012
4926
  [... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
5013
4927
  const stepEntry = {
@@ -5065,8 +4979,6 @@ var BaseAiService = class _BaseAiService {
5065
4979
  config;
5066
4980
  conversation = null;
5067
4981
  activeAbort = null;
5068
- /** researchSubagents toggle (issue #34): set per operation from the live settings snapshot; off means bit-for-bit sequential behavior. */
5069
- researchSubagentsEnabled = false;
5070
4982
  hasActiveConversation() {
5071
4983
  return this.conversation !== null;
5072
4984
  }
@@ -5122,10 +5034,7 @@ var BaseAiService = class _BaseAiService {
5122
5034
  * steering/failed result and the loop continues sequentially: a subagent can
5123
5035
  * never fail a plan or a turn.
5124
5036
  */
5125
- async executeSpawnAgent(args, fs10, onProgress, signal, subagentId) {
5126
- if (!this.researchSubagentsEnabled) {
5127
- 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 };
5128
- }
5037
+ async executeSpawnAgent(args, fs12, onProgress, signal, subagentId) {
5129
5038
  const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
5130
5039
  if (!prompt) {
5131
5040
  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 };
@@ -5136,20 +5045,20 @@ var BaseAiService = class _BaseAiService {
5136
5045
  if (!chat) throw new Error("subagent chats are not available for this provider");
5137
5046
  return chat;
5138
5047
  },
5139
- fs: fs10,
5048
+ fs: fs12,
5140
5049
  signal,
5141
5050
  onProgress: (progress) => onProgress({ ...progress, subagentId })
5142
5051
  });
5143
5052
  }
5144
5053
  /** Collect project context for the planning phase. Shared with the harness backend. */
5145
- static async collectResearchContext(fs10, runners) {
5146
- return collectResearchContext(fs10, runners);
5054
+ static async collectResearchContext(fs12, runners) {
5055
+ return collectResearchContext(fs12, runners);
5147
5056
  }
5148
5057
  /**
5149
5058
  * Execute one round of tool calls and report each through onProgress.
5150
5059
  * Returns the results to feed back plus the log entries produced.
5151
5060
  */
5152
- async executeToolCalls(toolCalls, fs10, onProgress, fetcher, stepIndex, thinking, signal) {
5061
+ async executeToolCalls(toolCalls, fs12, onProgress, fetcher, stepIndex, thinking, signal) {
5153
5062
  const toolResults = [];
5154
5063
  const logEntries = [];
5155
5064
  let resultsText = "";
@@ -5168,18 +5077,18 @@ var BaseAiService = class _BaseAiService {
5168
5077
  const subagentId = uuidv4();
5169
5078
  spawnSubagentIds.set(i, subagentId);
5170
5079
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
5171
- precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs10, onProgress, signal, subagentId));
5080
+ precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs12, onProgress, signal, subagentId));
5172
5081
  }) : Promise.resolve(),
5173
5082
  parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
5174
5083
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
5175
- precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs10, fetcher, signal));
5084
+ precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs12, fetcher, signal));
5176
5085
  }) : Promise.resolve()
5177
5086
  ]);
5178
5087
  for (const [index, tc] of toolCalls.entries()) {
5179
5088
  const toolArgs = normalizedArgs[index];
5180
5089
  const preResult = precomputed.get(index);
5181
5090
  if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
5182
- const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs10, fetcher, signal);
5091
+ const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs12, fetcher, signal);
5183
5092
  const MAX_RESPONSE_CHARS = 5e4;
5184
5093
  const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
5185
5094
  const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
@@ -5311,7 +5220,6 @@ ${llmOutput}
5311
5220
  researchLog
5312
5221
  };
5313
5222
  }
5314
- if (extractPrdBlock(turn.text)) ctx.prdCaptured = true;
5315
5223
  const reply = classifyPlannerReply(turn.text, {
5316
5224
  runners: ctx.runners,
5317
5225
  runnerModes: ctx.runnerModes,
@@ -5325,16 +5233,6 @@ ${llmOutput}
5325
5233
  case "task_query":
5326
5234
  return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
5327
5235
  case "plan":
5328
- if (ctx.prdEnabled && !ctx.prdCaptured && !ctx.prdNudgeSent && !signal?.aborted) {
5329
- ctx.prdNudgeSent = true;
5330
- pending = [
5331
- "PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
5332
- "In your next reply: first output the full markdown PRD wrapped EXACTLY in",
5333
- '<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
5334
- "then re-emit the exact same task plan JSON after the PRD block."
5335
- ].join(" ");
5336
- continue;
5337
- }
5338
5236
  return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
5339
5237
  // Botched attempts get a bounded corrective retry — otherwise the
5340
5238
  // broken JSON would surface as a prose bubble and the edit or plan
@@ -5376,7 +5274,7 @@ ${llmOutput}
5376
5274
  * parsed tasks if a plan was emitted mid-loop, or null if the loop
5377
5275
  * exhausted without a valid plan.
5378
5276
  */
5379
- async runResearchLoop(chat, firstMessage, fs10, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
5277
+ async runResearchLoop(chat, firstMessage, fs12, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
5380
5278
  const researchLog = initialLog ? [...initialLog] : [];
5381
5279
  if (!initialLog && userGoal) {
5382
5280
  researchLog.push({
@@ -5403,7 +5301,7 @@ ${llmOutput}
5403
5301
  if (!turn.hasToolCalls) break;
5404
5302
  const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
5405
5303
  turn.toolCalls,
5406
- fs10,
5304
+ fs12,
5407
5305
  onProgress,
5408
5306
  fetcher,
5409
5307
  step,
@@ -5541,9 +5439,6 @@ var GeminiService = class extends BaseAiService {
5541
5439
  req.runners,
5542
5440
  req.runnerModes,
5543
5441
  req.autonomousDefault ?? true,
5544
- req.grillMeEnabled ?? false,
5545
- req.prdEnabled ?? false,
5546
- req.reviewEnabled ?? false,
5547
5442
  req.verificationEnabled ?? false
5548
5443
  );
5549
5444
  const seededHistory = [
@@ -5582,8 +5477,7 @@ ${m.content}` });
5582
5477
  runners: req.runners,
5583
5478
  runnerModes: req.runnerModes,
5584
5479
  autonomousDefault: req.autonomousDefault,
5585
- fetcher: req.fetcher,
5586
- prdEnabled: req.prdEnabled ?? false
5480
+ fetcher: req.fetcher
5587
5481
  };
5588
5482
  this.conversation = { ctx, setProgress: (onProgress) => {
5589
5483
  currentProgress = onProgress;
@@ -5641,11 +5535,11 @@ ${repairHint}` }] : parts;
5641
5535
  return this.consumePlanStream(result, onToken);
5642
5536
  }
5643
5537
  // --- IAiService implementation ---
5644
- async researchAndPlan(userDescription, runners, modelsByRunner, fs10, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5538
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5645
5539
  this.ensureInit();
5646
5540
  const { autonomousDefault } = modes;
5647
5541
  const genModel = this.getPlanningModel();
5648
- const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
5542
+ const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
5649
5543
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
5650
5544
  const firstMessage = `${buildResearchToolsPrompt()}
5651
5545
 
@@ -5656,17 +5550,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
5656
5550
  generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
5657
5551
  });
5658
5552
  const researchChat = new GeminiResearchChat(chat);
5659
- const result = await this.runResearchLoop(researchChat, firstMessage, fs10, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
5553
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
5660
5554
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
5661
5555
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
5662
5556
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
5663
5557
  }
5664
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
5558
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
5665
5559
  this.ensureInit();
5666
5560
  const { autonomousDefault } = modes;
5667
5561
  let ctx = "";
5668
- if (fs10) {
5669
- const collected = await new ContextCollector(fs10).collect(runners[0] ?? "claude-code");
5562
+ if (fs12) {
5563
+ const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
5670
5564
  if (collected.aiflowContext) ctx = `
5671
5565
  <aiflow_context>
5672
5566
  ${collected.aiflowContext}
@@ -5682,12 +5576,12 @@ ${collected.aiflowContext}
5682
5576
  autonomousDefault
5683
5577
  );
5684
5578
  }
5685
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
5579
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
5686
5580
  this.ensureInit();
5687
5581
  const { autonomousDefault } = modes;
5688
5582
  let aiflowContext;
5689
- if (fs10) {
5690
- const collected = await new ContextCollector(fs10).collect(existingPlan.runners[0] ?? "claude-code");
5583
+ if (fs12) {
5584
+ const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
5691
5585
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
5692
5586
  }
5693
5587
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -5870,7 +5764,6 @@ var OpenAiService = class extends BaseAiService {
5870
5764
  async startConversation(req) {
5871
5765
  this.ensureInit();
5872
5766
  const client = this.getClient();
5873
- this.researchSubagentsEnabled = req.researchSubagentsEnabled ?? false;
5874
5767
  const contextStr = await BaseAiService.collectResearchContext(req.fs, req.runners);
5875
5768
  const systemPrompt = buildConversationSystemPrompt(
5876
5769
  req.goal,
@@ -5879,16 +5772,13 @@ var OpenAiService = class extends BaseAiService {
5879
5772
  req.runners,
5880
5773
  req.runnerModes,
5881
5774
  req.autonomousDefault ?? true,
5882
- req.grillMeEnabled ?? false,
5883
- req.prdEnabled ?? false,
5884
- req.reviewEnabled ?? false,
5885
5775
  req.verificationEnabled ?? false
5886
5776
  );
5887
5777
  const userText = req.goal;
5888
5778
  const messages = [
5889
5779
  { role: "system", content: `${systemPrompt}
5890
5780
 
5891
- ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
5781
+ ${buildResearchToolsPrompt()}` }
5892
5782
  ];
5893
5783
  for (const m of req.priorHistory ?? []) {
5894
5784
  messages.push({ role: m.role, content: m.content });
@@ -5898,7 +5788,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
5898
5788
  messages,
5899
5789
  client,
5900
5790
  this.requireModel("orchestratorModel", this.config.orchestratorModel),
5901
- toOpenAiTools(this.researchSubagentsEnabled),
5791
+ toOpenAiTools(),
5902
5792
  (delta) => currentProgress({ type: "thinking", text: delta }),
5903
5793
  (delta) => currentProgress({ type: "plan_token", planToken: delta })
5904
5794
  );
@@ -5908,8 +5798,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
5908
5798
  runners: req.runners,
5909
5799
  runnerModes: req.runnerModes,
5910
5800
  autonomousDefault: req.autonomousDefault,
5911
- fetcher: req.fetcher,
5912
- prdEnabled: req.prdEnabled ?? false
5801
+ fetcher: req.fetcher
5913
5802
  };
5914
5803
  this.conversation = {
5915
5804
  ctx,
@@ -5927,15 +5816,14 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
5927
5816
  }
5928
5817
  }
5929
5818
  // --- IAiService implementation ---
5930
- async researchAndPlan(userDescription, runners, modelsByRunner, fs10, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5819
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5931
5820
  this.ensureInit();
5932
5821
  const client = this.getClient();
5933
- const { autonomousDefault, researchSubagents: researchSubagentsEnabled } = modes;
5934
- this.researchSubagentsEnabled = researchSubagentsEnabled;
5935
- const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
5822
+ const { autonomousDefault } = modes;
5823
+ const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
5936
5824
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
5937
5825
  const userText = `User goal: ${userDescription}`;
5938
- const firstMessage = `${buildResearchToolsPrompt(researchSubagentsEnabled)}
5826
+ const firstMessage = `${buildResearchToolsPrompt()}
5939
5827
 
5940
5828
  Explore the workspace to understand the codebase, then generate the plan.
5941
5829
 
@@ -5947,20 +5835,20 @@ ${userText}`;
5947
5835
  messages,
5948
5836
  client,
5949
5837
  this.requireModel("orchestratorModel", this.config.orchestratorModel),
5950
- toOpenAiTools(researchSubagentsEnabled),
5838
+ toOpenAiTools(),
5951
5839
  (delta) => onProgress({ type: "thinking", text: delta })
5952
5840
  );
5953
- const result = await this.runResearchLoop(researchChat, firstMessage, fs10, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
5841
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
5954
5842
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
5955
5843
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
5956
5844
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
5957
5845
  }
5958
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5846
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5959
5847
  this.ensureInit();
5960
5848
  const { autonomousDefault } = modes;
5961
5849
  let ctx = "";
5962
- if (fs10) {
5963
- const collected = await new ContextCollector(fs10).collect(runners[0] ?? "claude-code");
5850
+ if (fs12) {
5851
+ const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
5964
5852
  if (collected.aiflowContext) ctx = `
5965
5853
  <aiflow_context>
5966
5854
  ${collected.aiflowContext}
@@ -5976,12 +5864,12 @@ ${collected.aiflowContext}
5976
5864
  autonomousDefault
5977
5865
  );
5978
5866
  }
5979
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs10, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5867
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
5980
5868
  this.ensureInit();
5981
5869
  const { autonomousDefault } = modes;
5982
5870
  let aiflowContext;
5983
- if (fs10) {
5984
- const collected = await new ContextCollector(fs10).collect(existingPlan.runners[0] ?? "claude-code");
5871
+ if (fs12) {
5872
+ const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
5985
5873
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
5986
5874
  }
5987
5875
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -6070,21 +5958,28 @@ function pickWindow(sorted, currentIdx, max) {
6070
5958
  const window = sorted.slice(start, start + max);
6071
5959
  return { window, omitted: sorted.length - window.length };
6072
5960
  }
6073
- function renderPlanMap(allTasks, currentTaskId, opts) {
6074
- if (allTasks.length < PLAN_MAP_MIN_TASKS) return "";
6075
- const sorted = [...allTasks].sort((a, b) => a.order - b.order);
5961
+ function renderPlanMap(planTasks, currentTaskId, opts) {
5962
+ const rows = flattenTasksWithParents(planTasks);
5963
+ if (rows.length < PLAN_MAP_MIN_TASKS) return "";
5964
+ const sorted = rows.map((r) => ({
5965
+ ...r,
5966
+ label: taskOrderLabel(r.task, r.parent ?? void 0)
5967
+ })).sort((a, b) => {
5968
+ const parentDiff = (a.parent?.order ?? -1) - (b.parent?.order ?? -1);
5969
+ if (parentDiff !== 0) return parentDiff;
5970
+ return a.task.order - b.task.order;
5971
+ });
6076
5972
  const max = opts?.maxEntries ?? DEFAULT_PLAN_MAP_MAX_ENTRIES;
6077
- const currentIdx = sorted.findIndex((t) => t.id === currentTaskId);
5973
+ const currentIdx = sorted.findIndex((r) => r.task.id === currentTaskId);
6078
5974
  const { window, omitted } = pickWindow(sorted, currentIdx, max);
6079
- const currentTask = sorted.find((t) => t.id === currentTaskId);
6080
- const widthOrder = Math.max(2, String(sorted[sorted.length - 1]?.order ?? sorted.length).length);
6081
- const lines = window.map((t) => {
6082
- const isCurrent = t.id === currentTaskId;
6083
- const tag = planMapStatus(t, isCurrent);
6084
- const order = String(t.order).padStart(widthOrder, " ");
5975
+ const currentTask = sorted.find((r) => r.task.id === currentTaskId)?.task;
5976
+ const lines = window.map((row) => {
5977
+ const isCurrent = row.task.id === currentTaskId;
5978
+ const tag = planMapStatus(row.task, isCurrent);
5979
+ const order = row.parent ? row.label : row.label.padStart(2, " ");
6085
5980
  const arrow = isCurrent ? " \u2190 you are here" : "";
6086
- const runner = t.assignedRunner ? ` (${t.assignedRunner})` : "";
6087
- return `${order}. [${tag.padEnd(7, " ")}] ${t.title}${runner}${arrow}`;
5981
+ const runner = row.task.assignedRunner ? ` (${row.task.assignedRunner})` : "";
5982
+ return `${order}. [${tag.padEnd(7, " ")}] ${row.task.title}${runner}${arrow}`;
6088
5983
  });
6089
5984
  const footer = omitted > 0 ? `
6090
5985
  (${omitted} task${omitted === 1 ? "" : "s"} omitted from this view)` : "";
@@ -6149,7 +6044,8 @@ function composeAugmentedPrompt(task, allTasks, opts) {
6149
6044
  const map = renderPlanMap(allTasks, task.id, { maxEntries: opts?.planMapMaxEntries });
6150
6045
  if (map) blocks.push(map);
6151
6046
  }
6152
- const outputs = collectDirectDependencyOutputs(task, allTasks);
6047
+ const flat = flattenTasks(allTasks);
6048
+ const outputs = collectDirectDependencyOutputs(task, flat);
6153
6049
  if (outputs.length > 0) blocks.push(renderPriorOutputs(outputs));
6154
6050
  if (opts?.tddEnabled) {
6155
6051
  blocks.push(renderTddInstruction());
@@ -6328,6 +6224,7 @@ function renderTerminalOutput(raw) {
6328
6224
  return populated.map((r) => cells(r).join("")).join("\n");
6329
6225
  }
6330
6226
  var MARKER_SCAN_TAIL = 16384;
6227
+ var IDLE_TIMEOUT_MS = 6e4;
6331
6228
  var VerdictEngine = class {
6332
6229
  markerSeen = /* @__PURE__ */ new Set();
6333
6230
  buffers = /* @__PURE__ */ new Map();
@@ -6335,6 +6232,7 @@ var VerdictEngine = class {
6335
6232
  pausedSessions = /* @__PURE__ */ new Map();
6336
6233
  listeners = [];
6337
6234
  checkpointListeners = [];
6235
+ idleListeners = [];
6338
6236
  /**
6339
6237
  * Per-task generation counter. Incremented on every watch() and clear().
6340
6238
  * Stale callbacks (from a prior session whose generation doesn't match
@@ -6342,12 +6240,47 @@ var VerdictEngine = class {
6342
6240
  * wrong session.
6343
6241
  */
6344
6242
  generations = /* @__PURE__ */ new Map();
6243
+ idleTimers = /* @__PURE__ */ new Map();
6244
+ idleSince = /* @__PURE__ */ new Map();
6345
6245
  onVerdict(listener) {
6346
6246
  this.listeners.push(listener);
6347
6247
  }
6348
6248
  onCheckpoint(listener) {
6349
6249
  this.checkpointListeners.push(listener);
6350
6250
  }
6251
+ onIdleChange(listener) {
6252
+ this.idleListeners.push(listener);
6253
+ }
6254
+ /** Advisory silence timestamp for a task, or null if it isn't idle. */
6255
+ getIdleSince(taskId) {
6256
+ return this.idleSince.get(taskId) ?? null;
6257
+ }
6258
+ /** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
6259
+ touchIdle(taskId, gen) {
6260
+ const existing = this.idleTimers.get(taskId);
6261
+ if (existing) clearTimeout(existing);
6262
+ if (this.idleSince.get(taskId)) {
6263
+ this.idleSince.set(taskId, null);
6264
+ for (const l of this.idleListeners) l(taskId, null);
6265
+ }
6266
+ this.idleTimers.set(taskId, setTimeout(() => {
6267
+ if (this.generations.get(taskId) !== gen) return;
6268
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6269
+ this.idleSince.set(taskId, now);
6270
+ for (const l of this.idleListeners) l(taskId, now);
6271
+ }, IDLE_TIMEOUT_MS));
6272
+ }
6273
+ /** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
6274
+ clearIdle(taskId) {
6275
+ const timer = this.idleTimers.get(taskId);
6276
+ if (timer) clearTimeout(timer);
6277
+ this.idleTimers.delete(taskId);
6278
+ const wasIdle = this.idleSince.get(taskId);
6279
+ this.idleSince.delete(taskId);
6280
+ if (wasIdle) {
6281
+ for (const l of this.idleListeners) l(taskId, null);
6282
+ }
6283
+ }
6351
6284
  approveCheckpoint(taskId) {
6352
6285
  const session = this.pausedSessions.get(taskId);
6353
6286
  if (session) {
@@ -6378,6 +6311,7 @@ ORDEWELL_REJECT: ${reason}
6378
6311
  this.checkpointCounts.set(task.id, 0);
6379
6312
  session.onOutput((text) => {
6380
6313
  if (this.generations.get(task.id) !== gen) return;
6314
+ this.touchIdle(task.id, gen);
6381
6315
  if (this.markerSeen.has(task.id)) return;
6382
6316
  const buf = (this.buffers.get(task.id) ?? "") + text;
6383
6317
  this.buffers.set(task.id, buf);
@@ -6389,6 +6323,7 @@ ORDEWELL_REJECT: ${reason}
6389
6323
  this.buffers.delete(task.id);
6390
6324
  this.checkpointCounts.delete(task.id);
6391
6325
  this.pausedSessions.delete(task.id);
6326
+ this.clearIdle(task.id);
6392
6327
  this.generations.set(task.id, gen + 1);
6393
6328
  const verdict = this.decide(task, 0);
6394
6329
  for (const l of this.listeners) l(task.id, verdict, output);
@@ -6413,6 +6348,7 @@ ORDEWELL_REJECT: ${reason}
6413
6348
  this.buffers.delete(task.id);
6414
6349
  this.checkpointCounts.delete(task.id);
6415
6350
  this.pausedSessions.delete(task.id);
6351
+ this.clearIdle(task.id);
6416
6352
  const verdict = this.decide(task, exitCode);
6417
6353
  for (const l of this.listeners) l(task.id, verdict, output);
6418
6354
  });
@@ -6423,6 +6359,7 @@ ORDEWELL_REJECT: ${reason}
6423
6359
  this.buffers.delete(task.id);
6424
6360
  this.checkpointCounts.delete(task.id);
6425
6361
  this.pausedSessions.delete(task.id);
6362
+ this.clearIdle(task.id);
6426
6363
  this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
6427
6364
  return {
6428
6365
  outcome: "pass",
@@ -6444,6 +6381,7 @@ ORDEWELL_REJECT: ${reason}
6444
6381
  this.buffers.delete(task.id);
6445
6382
  this.checkpointCounts.delete(task.id);
6446
6383
  this.pausedSessions.delete(task.id);
6384
+ this.clearIdle(task.id);
6447
6385
  this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
6448
6386
  }
6449
6387
  /** Drop all tracking state (used on stop / loadPlan). */
@@ -6452,6 +6390,9 @@ ORDEWELL_REJECT: ${reason}
6452
6390
  this.buffers.clear();
6453
6391
  this.checkpointCounts.clear();
6454
6392
  this.pausedSessions.clear();
6393
+ for (const timer of this.idleTimers.values()) clearTimeout(timer);
6394
+ this.idleTimers.clear();
6395
+ this.idleSince.clear();
6455
6396
  this.generations.clear();
6456
6397
  }
6457
6398
  decide(task, exitCode) {
@@ -6857,6 +6798,7 @@ var TaskOrchestrator = class {
6857
6798
  this.store.markAwaitingUser(taskId);
6858
6799
  this.emit("onCheckpoint", { taskId, taskTitle: task.title, summary });
6859
6800
  });
6801
+ this.verifier.onIdleChange(() => this.emit("onTaskChanged"));
6860
6802
  }
6861
6803
  config;
6862
6804
  notifications;
@@ -6881,6 +6823,10 @@ var TaskOrchestrator = class {
6881
6823
  workspaceRootFn = () => process.cwd();
6882
6824
  observers = [];
6883
6825
  tddEnabled = () => false;
6826
+ /** Advisory silence timestamp for a task's live runner, or null if not idle. */
6827
+ getIdleSince(taskId) {
6828
+ return this.verifier.getIdleSince(taskId);
6829
+ }
6884
6830
  get storeInstance() {
6885
6831
  return this.store;
6886
6832
  }
@@ -7268,8 +7214,10 @@ ${output.slice(-3e3)}`);
7268
7214
  if (ready.length === 0 && this.activeTaskSessions.size === 0) {
7269
7215
  const remaining = this.store.allTasks.filter((t) => t.status !== "completed");
7270
7216
  console.log(`[TaskOrchestrator] tick(): no work. remaining=${remaining.length}, completed=${this.store.isAllComplete()}`);
7271
- for (const t of remaining) {
7272
- console.log(` - ${t.order}. ${t.title} [${t.type}/${t.status}] prompt=${!!t.prompt}`);
7217
+ const remainingById = new Map(remaining.map((t) => [t.id, t]));
7218
+ for (const { task, parent } of flattenTasksWithParents(this.store.planTasks)) {
7219
+ if (!remainingById.has(task.id)) continue;
7220
+ console.log(` - ${taskOrderLabel(task, parent ?? void 0)}. ${task.title} [${task.type}/${task.status}] prompt=${!!task.prompt}`);
7273
7221
  }
7274
7222
  if (this.store.isAllComplete()) {
7275
7223
  this.running = false;
@@ -7296,7 +7244,7 @@ ${output.slice(-3e3)}`);
7296
7244
  try {
7297
7245
  const cwd = this.workspaceRootFn();
7298
7246
  const runner = this.store.resolveTaskRunner(task);
7299
- const finalPrompt = composeAugmentedPrompt(task, this.store.allTasks, {
7247
+ const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
7300
7248
  planMapEnabled: this.config.planMapEnabled,
7301
7249
  tddEnabled: this.tddEnabled()
7302
7250
  });
@@ -7470,7 +7418,7 @@ import * as osMod from "os";
7470
7418
  import { exec } from "child_process";
7471
7419
  import { promisify } from "util";
7472
7420
  import * as os2 from "os";
7473
- import * as path5 from "path";
7421
+ import * as path4 from "path";
7474
7422
  var execAsync = promisify(exec);
7475
7423
  var LOGIN_SHELL_TIMEOUT_MS = 5e3;
7476
7424
  var PATH_MARKER = "__ORDEWELL_PATH__";
@@ -7481,40 +7429,40 @@ var defaultExec = async (command, options) => {
7481
7429
  function posixBinDirs() {
7482
7430
  const home = os2.homedir();
7483
7431
  return [
7484
- path5.join(home, ".local", "bin"),
7485
- path5.join(home, ".opencode", "bin"),
7486
- path5.join(home, ".claude", "bin"),
7487
- path5.join(home, ".bun", "bin"),
7488
- path5.join(home, ".volta", "bin"),
7489
- path5.join(home, ".npm-global", "bin"),
7490
- path5.join(home, "bin"),
7432
+ path4.join(home, ".local", "bin"),
7433
+ path4.join(home, ".opencode", "bin"),
7434
+ path4.join(home, ".claude", "bin"),
7435
+ path4.join(home, ".bun", "bin"),
7436
+ path4.join(home, ".volta", "bin"),
7437
+ path4.join(home, ".npm-global", "bin"),
7438
+ path4.join(home, "bin"),
7491
7439
  "/usr/local/bin",
7492
7440
  "/opt/homebrew/bin"
7493
7441
  ];
7494
7442
  }
7495
7443
  function windowsBinDirs(env, home) {
7496
- const join16 = (...parts) => path5.win32.join(...parts);
7497
- const under = (base, ...parts) => base ? [join16(base, ...parts)] : [];
7444
+ const join19 = (...parts) => path4.win32.join(...parts);
7445
+ const under = (base, ...parts) => base ? [join19(base, ...parts)] : [];
7498
7446
  const appData = env.APPDATA;
7499
7447
  const localAppData = env.LOCALAPPDATA;
7500
7448
  const programData = env.ProgramData;
7501
7449
  return [
7502
7450
  // Native per-user installers, PowerShell-driven and otherwise.
7503
- join16(home, ".local", "bin"),
7504
- join16(home, ".opencode", "bin"),
7505
- join16(home, ".claude", "bin"),
7506
- join16(home, ".codex", "bin"),
7451
+ join19(home, ".local", "bin"),
7452
+ join19(home, ".opencode", "bin"),
7453
+ join19(home, ".claude", "bin"),
7454
+ join19(home, ".codex", "bin"),
7507
7455
  ...under(localAppData, "Programs", "claude"),
7508
7456
  // Node package managers. npm's global prefix is `%APPDATA%\npm`; the
7509
7457
  // literal spelling is kept as a fallback for a host that does not export
7510
7458
  // APPDATA (a service account, a stripped CI container).
7511
7459
  ...under(appData, "npm"),
7512
- join16(home, "AppData", "Roaming", "npm"),
7460
+ join19(home, "AppData", "Roaming", "npm"),
7513
7461
  ...under(localAppData, "pnpm"),
7514
7462
  ...under(localAppData, "Yarn", "bin"),
7515
- join16(home, ".bun", "bin"),
7463
+ join19(home, ".bun", "bin"),
7516
7464
  // Windows package managers.
7517
- join16(home, "scoop", "shims"),
7465
+ join19(home, "scoop", "shims"),
7518
7466
  ...under(env.SCOOP, "shims"),
7519
7467
  ...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
7520
7468
  ...under(programData, "chocolatey", "bin"),
@@ -7524,7 +7472,7 @@ function windowsBinDirs(env, home) {
7524
7472
  ...under(localAppData, "Microsoft", "WindowsApps"),
7525
7473
  // Version managers. Volta's Windows home is not the POSIX `~/.volta`.
7526
7474
  ...under(localAppData, "Volta", "bin"),
7527
- join16(home, ".volta", "bin")
7475
+ join19(home, ".volta", "bin")
7528
7476
  ];
7529
7477
  }
7530
7478
  function wellKnownBinDirs(deps = {}) {
@@ -7553,14 +7501,14 @@ function augmentedPath(execImpl = defaultExec) {
7553
7501
  const segments = [];
7554
7502
  const push = (p) => {
7555
7503
  if (!p) return;
7556
- for (const seg of p.split(path5.delimiter)) {
7504
+ for (const seg of p.split(path4.delimiter)) {
7557
7505
  if (seg && !segments.includes(seg)) segments.push(seg);
7558
7506
  }
7559
7507
  };
7560
7508
  push(process.env.PATH);
7561
7509
  push(await loginShellPath(execImpl));
7562
7510
  for (const dir of wellKnownBinDirs()) push(dir);
7563
- return segments.join(path5.delimiter);
7511
+ return segments.join(path4.delimiter);
7564
7512
  })();
7565
7513
  }
7566
7514
  return cachedPath;
@@ -7580,6 +7528,50 @@ function withPath(base, resolvedPath, overrides = {}) {
7580
7528
  return env;
7581
7529
  }
7582
7530
 
7531
+ // src/utils/globalDataDir.ts
7532
+ import * as fs from "fs";
7533
+ import * as path5 from "path";
7534
+ import * as os3 from "os";
7535
+ function globalDataDir() {
7536
+ return path5.join(os3.homedir(), ".ordewell");
7537
+ }
7538
+ function copyDirSync(src, dest) {
7539
+ fs.mkdirSync(dest, { recursive: true });
7540
+ const entries = fs.readdirSync(src, { withFileTypes: true });
7541
+ for (const entry of entries) {
7542
+ const srcPath = path5.join(src, entry.name);
7543
+ const destPath = path5.join(dest, entry.name);
7544
+ if (entry.isDirectory()) {
7545
+ copyDirSync(srcPath, destPath);
7546
+ } else {
7547
+ fs.copyFileSync(srcPath, destPath);
7548
+ }
7549
+ }
7550
+ }
7551
+ var migrated = false;
7552
+ function migrateOldConfigDir() {
7553
+ if (migrated) return;
7554
+ migrated = true;
7555
+ const oldDir = path5.join(os3.homedir(), ".config", "ordewell");
7556
+ if (!fs.existsSync(oldDir)) return;
7557
+ const newDir = globalDataDir();
7558
+ const liftFile = (name) => {
7559
+ const oldFile = path5.join(oldDir, name);
7560
+ const newFile = path5.join(newDir, name);
7561
+ if (fs.existsSync(oldFile) && !fs.existsSync(newFile)) {
7562
+ fs.mkdirSync(newDir, { recursive: true });
7563
+ fs.copyFileSync(oldFile, newFile);
7564
+ }
7565
+ };
7566
+ liftFile("settings.json");
7567
+ liftFile(".env");
7568
+ const oldPlugins = path5.join(oldDir, "plugins");
7569
+ const newPlugins = path5.join(newDir, "plugins");
7570
+ if (fs.existsSync(oldPlugins) && !fs.existsSync(newPlugins)) {
7571
+ copyDirSync(oldPlugins, newPlugins);
7572
+ }
7573
+ }
7574
+
7583
7575
  // src/utils/launch.ts
7584
7576
  import * as fsSync2 from "fs";
7585
7577
  import * as path6 from "path";
@@ -7596,9 +7588,11 @@ function buildShellInvocation(command, args) {
7596
7588
  const inner = [command, ...args].map(posixShellQuote).join(" ");
7597
7589
  return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
7598
7590
  }
7599
- function wrapWithPty(command, args) {
7591
+ function wrapWithPty(command, args, opts = {}) {
7600
7592
  const inner = [command, ...args].map(posixShellQuote).join(" ");
7601
- return { command: "script", args: ["-q", "-e", "-f", "-c", inner, "/dev/null"] };
7593
+ const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
7594
+ const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
7595
+ return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
7602
7596
  }
7603
7597
 
7604
7598
  // src/utils/launch.ts
@@ -7905,7 +7899,7 @@ function parseCodexModelsCache(content) {
7905
7899
  }
7906
7900
  var GEMINI_MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7907
7901
  function geminiDiscoveryCacheKey(apiKey) {
7908
- const dir = path7.join(osMod.homedir(), ".config", "ordewell");
7902
+ const dir = globalDataDir();
7909
7903
  const hash = apiKey.slice(-8);
7910
7904
  return {
7911
7905
  key: `gemini-models:${hash}`,
@@ -8435,12 +8429,14 @@ var ModelDiscovery = class {
8435
8429
  // src/services/ModelCatalog.ts
8436
8430
  import * as fs2 from "fs";
8437
8431
  import * as path8 from "path";
8438
- import * as os3 from "os";
8439
8432
  var CACHE_KEY = "model-catalog";
8440
8433
  var CACHE_TTL_MS = 60 * 60 * 1e3;
8441
8434
  function storageKey(baseUrl) {
8442
8435
  return `${CACHE_KEY}:${baseUrl}`;
8443
8436
  }
8437
+ function globalCacheDir() {
8438
+ return globalDataDir();
8439
+ }
8444
8440
  var ModelCatalog = class {
8445
8441
  static async fetchModels(apiKey, baseUrl, fetchImpl) {
8446
8442
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
@@ -8483,7 +8479,7 @@ var ModelCatalog = class {
8483
8479
  try {
8484
8480
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
8485
8481
  const key = storageKey(url);
8486
- const cacheDir = path8.join(os3.homedir(), ".config", "ordewell");
8482
+ const cacheDir = globalCacheDir();
8487
8483
  const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
8488
8484
  if (fs2.existsSync(cacheFile)) fs2.rmSync(cacheFile);
8489
8485
  } catch {
@@ -8491,7 +8487,7 @@ var ModelCatalog = class {
8491
8487
  }
8492
8488
  static readCache(key) {
8493
8489
  try {
8494
- const cacheDir = path8.join(os3.homedir(), ".config", "ordewell");
8490
+ const cacheDir = globalCacheDir();
8495
8491
  const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
8496
8492
  if (!fs2.existsSync(cacheFile)) return null;
8497
8493
  const raw = fs2.readFileSync(cacheFile, "utf8");
@@ -8504,7 +8500,7 @@ var ModelCatalog = class {
8504
8500
  }
8505
8501
  static writeCache(key, models) {
8506
8502
  try {
8507
- const cacheDir = path8.join(os3.homedir(), ".config", "ordewell");
8503
+ const cacheDir = globalCacheDir();
8508
8504
  fs2.mkdirSync(cacheDir, { recursive: true });
8509
8505
  const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
8510
8506
  fs2.writeFileSync(
@@ -9946,14 +9942,14 @@ ${part.text}` : part.text });
9946
9942
  }
9947
9943
  }
9948
9944
  }
9949
- async json(method, path17, body, signal) {
9950
- const response = await this.deps.fetch(`${this.baseUrl}${path17}`, {
9945
+ async json(method, path20, body, signal) {
9946
+ const response = await this.deps.fetch(`${this.baseUrl}${path20}`, {
9951
9947
  method,
9952
9948
  headers: { "content-type": "application/json" },
9953
9949
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
9954
9950
  ...signal ? { signal } : {}
9955
9951
  });
9956
- if (!response.ok) throw new Error(`${method} ${path17} failed: ${response.status} ${response.statusText}`);
9952
+ if (!response.ok) throw new Error(`${method} ${path20} failed: ${response.status} ${response.statusText}`);
9957
9953
  return await response.json().catch(() => null);
9958
9954
  }
9959
9955
  nativeSessionId() {
@@ -10108,9 +10104,6 @@ var CliAgentAiService = class {
10108
10104
  req.runners,
10109
10105
  req.runnerModes,
10110
10106
  req.autonomousDefault ?? true,
10111
- req.grillMeEnabled ?? false,
10112
- req.prdEnabled ?? false,
10113
- req.reviewEnabled ?? false,
10114
10107
  req.verificationEnabled ?? false,
10115
10108
  true
10116
10109
  );
@@ -10125,10 +10118,7 @@ var CliAgentAiService = class {
10125
10118
  startOptions,
10126
10119
  runners: req.runners,
10127
10120
  runnerModes: req.runnerModes,
10128
- autonomousDefault: req.autonomousDefault,
10129
- prdEnabled: req.prdEnabled ?? false,
10130
- prdCaptured: false,
10131
- prdNudgeSent: false
10121
+ autonomousDefault: req.autonomousDefault
10132
10122
  };
10133
10123
  const opening = this.openingMessage(req);
10134
10124
  return this.runConversation(opening, req.onProgress, req.signal);
@@ -10156,8 +10146,8 @@ var CliAgentAiService = class {
10156
10146
  /**
10157
10147
  * Drive one user message to a settled planner turn. Same shape as the API
10158
10148
  * backend's conversation loop minus the tool rounds — those belong to the
10159
- * agent now — and with the same three policies layered on top: the PRD gate,
10160
- * the empty-reply nudge, and a bounded corrective re-emit for botched JSON.
10149
+ * agent now — and with the same two policies layered on top: the
10150
+ * empty-reply nudge, and a bounded corrective re-emit for botched JSON.
10161
10151
  */
10162
10152
  async runConversation(message, onProgress, signal) {
10163
10153
  const conversation = this.conversation;
@@ -10205,7 +10195,6 @@ var CliAgentAiService = class {
10205
10195
  ].join(" ");
10206
10196
  continue;
10207
10197
  }
10208
- if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
10209
10198
  const reply = classifyPlannerReply(turn.text, {
10210
10199
  runners: conversation.runners,
10211
10200
  runnerModes: conversation.runnerModes,
@@ -10219,16 +10208,6 @@ var CliAgentAiService = class {
10219
10208
  case "task_query":
10220
10209
  return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
10221
10210
  case "plan":
10222
- if (conversation.prdEnabled && !conversation.prdCaptured && !conversation.prdNudgeSent && !combined?.aborted) {
10223
- conversation.prdNudgeSent = true;
10224
- pending = [
10225
- "PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
10226
- "In your next reply: first output the full markdown PRD wrapped EXACTLY in",
10227
- '<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
10228
- "then re-emit the exact same task plan JSON after the PRD block."
10229
- ].join(" ");
10230
- continue;
10231
- }
10232
10211
  this.conversation = null;
10233
10212
  return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
10234
10213
  case "broken_task_ops":
@@ -10407,10 +10386,7 @@ var CliAgentAiService = class {
10407
10386
  oneShotAdapter = await this.startAdapter(startOptions);
10408
10387
  this.conversation = {
10409
10388
  startOptions,
10410
- runners: [],
10411
- prdEnabled: false,
10412
- prdCaptured: false,
10413
- prdNudgeSent: false
10389
+ runners: []
10414
10390
  };
10415
10391
  const turn = await this.runTurn(
10416
10392
  "Follow the instructions in your system prompt and produce the plan now.",
@@ -10427,8 +10403,8 @@ var CliAgentAiService = class {
10427
10403
  this.lastNativeSessionId = previousSessionId;
10428
10404
  }
10429
10405
  }
10430
- async researchAndPlan(userDescription, runners, modelsByRunner, fs10, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
10431
- const contextStr = await collectResearchContext(fs10, runners);
10406
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
10407
+ const contextStr = await collectResearchContext(fs12, runners);
10432
10408
  const researchLog = [
10433
10409
  { id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
10434
10410
  ];
@@ -10751,74 +10727,202 @@ function retargetTaskRunner(task, runner, catalog) {
10751
10727
  return { assignedRunner: runner, ...runnerAssignment(catalog, task) };
10752
10728
  }
10753
10729
 
10754
- // src/services/SessionMessage.ts
10755
- function serializeTask(t) {
10756
- return {
10757
- id: t.id,
10758
- order: t.order,
10759
- title: t.title,
10760
- type: t.type,
10761
- description: t.description,
10762
- dependencies: t.dependencies || [],
10763
- assignedRunner: t.assignedRunner,
10764
- assignedModel: t.assignedModel || null,
10765
- taskMode: t.taskMode || "build",
10766
- prompt: t.prompt || null,
10767
- subtasks: (t.subtasks || []).map(serializeTask),
10768
- userSteps: t.userSteps || void 0,
10769
- thinkingEffort: t.thinkingEffort || void 0,
10770
- autonomy: t.autonomy || void 0,
10771
- sliceType: t.sliceType || void 0,
10772
- userStoriesCovered: t.userStoriesCovered || void 0
10730
+ // src/services/SkillsService.ts
10731
+ import * as fs4 from "fs";
10732
+ import * as path11 from "path";
10733
+
10734
+ // src/services/builtinSkills.ts
10735
+ import * as path10 from "path";
10736
+ import { fileURLToPath } from "url";
10737
+ function builtinSkillsDir() {
10738
+ const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path10.dirname(fileURLToPath(
10739
+ // @ts-expect-error - import.meta is only valid under an ESM module setting
10740
+ import.meta.url
10741
+ ));
10742
+ return path10.join(dir, "..", "skills");
10743
+ }
10744
+
10745
+ // src/services/SkillsService.ts
10746
+ var BUILTIN_SKILL_NAMES = ["grilling", "to-spec"];
10747
+ var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
10748
+ function parseSkillFile(filePath, name, source) {
10749
+ const raw = fs4.readFileSync(filePath, "utf8");
10750
+ const frontmatter = {};
10751
+ let content = raw;
10752
+ if (raw.startsWith("---\n")) {
10753
+ const end = raw.indexOf("\n---", 4);
10754
+ if (end !== -1) {
10755
+ const fmText = raw.slice(4, end);
10756
+ for (const line of fmText.split("\n")) {
10757
+ const idx = line.indexOf(":");
10758
+ if (idx === -1) continue;
10759
+ const key = line.slice(0, idx).trim();
10760
+ const value = line.slice(idx + 1).trim();
10761
+ if (key === "disable-model-invocation") {
10762
+ frontmatter[key] = value === "true";
10763
+ } else if (key === "name" || key === "description") {
10764
+ frontmatter[key] = value.replace(/^"|"$/g, "");
10765
+ }
10766
+ }
10767
+ content = raw.slice(end + 4).replace(/^\n+/, "");
10768
+ }
10769
+ }
10770
+ const metadata = {
10771
+ name: frontmatter.name ?? name,
10772
+ description: frontmatter.description ?? "",
10773
+ ...frontmatter["disable-model-invocation"] !== void 0 ? { disableModelInvocation: frontmatter["disable-model-invocation"] } : {}
10773
10774
  };
10775
+ return { name, description: metadata.description, metadata, content, path: filePath, source };
10774
10776
  }
10775
- function serializeTaskStatus(t) {
10776
- return {
10777
- id: t.id,
10778
- status: t.status,
10779
- verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null
10780
- };
10777
+ function copyDirSync2(src, dest) {
10778
+ fs4.mkdirSync(dest, { recursive: true });
10779
+ const entries = fs4.readdirSync(src, { withFileTypes: true });
10780
+ for (const entry of entries) {
10781
+ const srcPath = path11.join(src, entry.name);
10782
+ const destPath = path11.join(dest, entry.name);
10783
+ if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
10784
+ else fs4.copyFileSync(srcPath, destPath);
10785
+ }
10781
10786
  }
10782
- function serializePlan(plan) {
10783
- return {
10784
- tasks: plan.tasks.map(serializeTask),
10785
- runners: plan.runners,
10786
- generatedAt: plan.generatedAt,
10787
- conversationHistory: plan.conversationHistory,
10788
- prdMarkdown: plan.prdMarkdown,
10789
- queuedMessages: plan.queuedMessages
10790
- };
10787
+ function readDir(dir, source) {
10788
+ if (!fs4.existsSync(dir)) return [];
10789
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
10790
+ const skills = [];
10791
+ for (const entry of entries) {
10792
+ if (!entry.isDirectory()) continue;
10793
+ const skillFile = path11.join(dir, entry.name, "SKILL.md");
10794
+ if (!fs4.existsSync(skillFile)) continue;
10795
+ const parsed = parseSkillFile(skillFile, entry.name, source);
10796
+ if (parsed) skills.push(parsed);
10797
+ }
10798
+ return skills;
10791
10799
  }
10792
- function executionSummary(tasks) {
10793
- return {
10794
- total: tasks.length,
10795
- completed: tasks.filter((t) => t.status === "completed").length,
10796
- failed: tasks.filter((t) => t.status === "failed").length
10797
- };
10800
+ var SkillsService = class {
10801
+ constructor(workspaceRoot) {
10802
+ this.workspaceRoot = workspaceRoot;
10803
+ }
10804
+ workspaceRoot;
10805
+ localDir() {
10806
+ return this.workspaceRoot ? path11.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
10807
+ }
10808
+ globalDir() {
10809
+ return path11.join(globalDataDir(), "skills");
10810
+ }
10811
+ seed(name) {
10812
+ const dest = path11.join(this.globalDir(), name);
10813
+ if (fs4.existsSync(dest)) return false;
10814
+ let srcDir;
10815
+ try {
10816
+ srcDir = path11.join(builtinSkillsDir(), name);
10817
+ } catch (err) {
10818
+ console.warn(`Could not locate built-in skills dir: ${String(err)}`);
10819
+ return false;
10820
+ }
10821
+ if (!fs4.existsSync(srcDir)) {
10822
+ console.warn(`Built-in skill not found in package: ${name}`);
10823
+ return false;
10824
+ }
10825
+ copyDirSync2(srcDir, dest);
10826
+ return true;
10827
+ }
10828
+ seedBuiltinSkill(name) {
10829
+ return this.seed(name);
10830
+ }
10831
+ /** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
10832
+ isUnmodifiedRetiredSeed(dir, expectedName) {
10833
+ let entries;
10834
+ try {
10835
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
10836
+ } catch {
10837
+ return false;
10838
+ }
10839
+ if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
10840
+ let raw;
10841
+ try {
10842
+ raw = fs4.readFileSync(path11.join(dir, "SKILL.md"), "utf8");
10843
+ } catch {
10844
+ return false;
10845
+ }
10846
+ if (!raw.startsWith("---\n")) return false;
10847
+ const end = raw.indexOf("\n---", 4);
10848
+ if (end === -1) return false;
10849
+ for (const line of raw.slice(4, end).split("\n")) {
10850
+ const idx = line.indexOf(":");
10851
+ if (idx === -1) continue;
10852
+ const key = line.slice(0, idx).trim();
10853
+ if (key === "name") return line.slice(idx + 1).trim().replace(/^"|"$/g, "") === expectedName;
10854
+ }
10855
+ return false;
10856
+ }
10857
+ pruneRetired() {
10858
+ for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
10859
+ const dest = path11.join(this.globalDir(), name);
10860
+ if (!fs4.existsSync(dest)) continue;
10861
+ if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
10862
+ try {
10863
+ fs4.rmSync(dest, { recursive: true, force: true });
10864
+ } catch (err) {
10865
+ console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
10866
+ }
10867
+ }
10868
+ }
10869
+ findSkill(name) {
10870
+ this.pruneRetired();
10871
+ if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
10872
+ const localDir = this.localDir();
10873
+ if (localDir) {
10874
+ const localFile = path11.join(localDir, name, "SKILL.md");
10875
+ if (fs4.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
10876
+ }
10877
+ const globalFile = path11.join(this.globalDir(), name, "SKILL.md");
10878
+ if (fs4.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
10879
+ return void 0;
10880
+ }
10881
+ listSkills() {
10882
+ this.pruneRetired();
10883
+ for (const name of BUILTIN_SKILL_NAMES) this.seed(name);
10884
+ const byName = /* @__PURE__ */ new Map();
10885
+ for (const skill of readDir(this.globalDir(), "global")) {
10886
+ byName.set(skill.name, skill);
10887
+ }
10888
+ const localDir = this.localDir();
10889
+ if (localDir) {
10890
+ for (const skill of readDir(localDir, "local")) {
10891
+ byName.set(skill.name, skill);
10892
+ }
10893
+ }
10894
+ return [...byName.values()];
10895
+ }
10896
+ getSkillContent(name) {
10897
+ return this.findSkill(name)?.content;
10898
+ }
10899
+ };
10900
+ function createSkillsService(workspaceRoot) {
10901
+ return new SkillsService(workspaceRoot);
10798
10902
  }
10799
10903
 
10800
10904
  // src/utils/sessionStore.ts
10801
- import * as fs5 from "fs";
10802
- import * as path11 from "path";
10905
+ import * as fs6 from "fs";
10906
+ import * as path13 from "path";
10803
10907
 
10804
10908
  // src/utils/fsHelpers.ts
10805
- import * as fs4 from "fs";
10806
- import * as path10 from "path";
10909
+ import * as fs5 from "fs";
10910
+ import * as path12 from "path";
10807
10911
  var STATE_DIR = ".ordewell";
10808
10912
  function ensureDir(dir) {
10809
- if (!fs4.existsSync(dir)) {
10810
- fs4.mkdirSync(dir, { recursive: true });
10913
+ if (!fs5.existsSync(dir)) {
10914
+ fs5.mkdirSync(dir, { recursive: true });
10811
10915
  }
10812
10916
  }
10813
10917
  function getStateDir(baseDir) {
10814
- return path10.join(baseDir ?? process.cwd(), STATE_DIR);
10918
+ return path12.join(baseDir ?? process.cwd(), STATE_DIR);
10815
10919
  }
10816
10920
  var IGNORE_FILE = ".gitignore";
10817
10921
  function ensureStateDirIgnored(baseDir) {
10818
10922
  const dir = getStateDir(baseDir);
10819
10923
  ensureDir(dir);
10820
10924
  try {
10821
- fs4.writeFileSync(path10.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
10925
+ fs5.writeFileSync(path12.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
10822
10926
  } catch (err) {
10823
10927
  if (err.code !== "EEXIST") throw err;
10824
10928
  }
@@ -10833,7 +10937,7 @@ function mintSessionId() {
10833
10937
  // src/utils/sessionStore.ts
10834
10938
  var SESSIONS_SUBDIR = "sessions";
10835
10939
  function getSessionsDir(baseDir) {
10836
- return path11.join(getStateDir(baseDir), SESSIONS_SUBDIR);
10940
+ return path13.join(getStateDir(baseDir), SESSIONS_SUBDIR);
10837
10941
  }
10838
10942
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
10839
10943
  function hasScalarRunner(parsed) {
@@ -10868,17 +10972,17 @@ function saveSession(plan, goal, baseDir, id) {
10868
10972
  plan: JSON.parse(JSON.stringify(plan))
10869
10973
  };
10870
10974
  const filename = sessionFilename(meta);
10871
- const filePath = path11.join(sessionsDir, filename);
10872
- fs5.writeFileSync(filePath, JSON.stringify(session, null, 2));
10975
+ const filePath = path13.join(sessionsDir, filename);
10976
+ fs6.writeFileSync(filePath, JSON.stringify(session, null, 2));
10873
10977
  return meta;
10874
10978
  }
10875
10979
  function listSessions(baseDir, logger = defaultLogger) {
10876
10980
  const sessionsDir = getSessionsDir(baseDir);
10877
- if (!fs5.existsSync(sessionsDir)) return [];
10878
- const files = fs5.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
10981
+ if (!fs6.existsSync(sessionsDir)) return [];
10982
+ const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
10879
10983
  return files.map((f) => {
10880
10984
  try {
10881
- const raw = fs5.readFileSync(path11.join(sessionsDir, f), "utf-8");
10985
+ const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
10882
10986
  const parsed = JSON.parse(raw);
10883
10987
  if (hasScalarRunner(parsed)) {
10884
10988
  logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
@@ -10893,10 +10997,10 @@ function listSessions(baseDir, logger = defaultLogger) {
10893
10997
  }).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
10894
10998
  }
10895
10999
  function findSessionFile(sessionsDir, sessionId, logger) {
10896
- const files = fs5.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
11000
+ const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
10897
11001
  for (const f of files) {
10898
11002
  try {
10899
- const raw = fs5.readFileSync(path11.join(sessionsDir, f), "utf-8");
11003
+ const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
10900
11004
  const session = JSON.parse(raw);
10901
11005
  if (session.meta.id === sessionId) return f;
10902
11006
  } catch (err) {
@@ -10907,11 +11011,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
10907
11011
  }
10908
11012
  function loadSession(sessionId, baseDir, logger = defaultLogger) {
10909
11013
  const sessionsDir = getSessionsDir(baseDir);
10910
- if (!fs5.existsSync(sessionsDir)) return null;
11014
+ if (!fs6.existsSync(sessionsDir)) return null;
10911
11015
  try {
10912
11016
  const file = findSessionFile(sessionsDir, sessionId, logger);
10913
11017
  if (file) {
10914
- const raw = fs5.readFileSync(path11.join(sessionsDir, file), "utf-8");
11018
+ const raw = fs6.readFileSync(path13.join(sessionsDir, file), "utf-8");
10915
11019
  const parsed = JSON.parse(raw);
10916
11020
  if (hasScalarRunner(parsed)) {
10917
11021
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -10941,11 +11045,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
10941
11045
  }
10942
11046
  function deleteSession(sessionId, baseDir, logger = defaultLogger) {
10943
11047
  const sessionsDir = getSessionsDir(baseDir);
10944
- if (!fs5.existsSync(sessionsDir)) return false;
11048
+ if (!fs6.existsSync(sessionsDir)) return false;
10945
11049
  try {
10946
11050
  const file = findSessionFile(sessionsDir, sessionId, logger);
10947
11051
  if (file) {
10948
- fs5.unlinkSync(path11.join(sessionsDir, file));
11052
+ fs6.unlinkSync(path13.join(sessionsDir, file));
10949
11053
  return true;
10950
11054
  }
10951
11055
  } catch (err) {
@@ -10954,6 +11058,29 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
10954
11058
  return false;
10955
11059
  }
10956
11060
 
11061
+ // src/utils/prdStore.ts
11062
+ import * as fs7 from "fs";
11063
+ import * as path14 from "path";
11064
+ var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
11065
+ function extractPrdBlock(text) {
11066
+ const match = PRD_BLOCK_RE.exec(text);
11067
+ if (!match) return null;
11068
+ const slug2 = sanitizeSlug(match[1]);
11069
+ const markdown = match[2].trim();
11070
+ if (!slug2 || !markdown) return null;
11071
+ return { slug: slug2, markdown };
11072
+ }
11073
+ function sanitizeSlug(raw) {
11074
+ return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
11075
+ }
11076
+ function savePrdMarkdown(workspace, slug2, markdown) {
11077
+ const dir = path14.join(workspace, ".scratch", sanitizeSlug(slug2));
11078
+ fs7.mkdirSync(dir, { recursive: true });
11079
+ const file = path14.join(dir, "PRD.md");
11080
+ fs7.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
11081
+ return file;
11082
+ }
11083
+
10957
11084
  // src/services/createSession.ts
10958
11085
  var CATALOG_MODEL_CAP = 100;
10959
11086
  var MAX_TASK_QUERIES = 3;
@@ -10970,6 +11097,17 @@ var PlanEditError = class extends Error {
10970
11097
  function sessionRuntimeSettings(settings) {
10971
11098
  return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
10972
11099
  }
11100
+ function resolveSkillInvocation(text, skillsService) {
11101
+ const match = text.trim().match(/^\/([a-z][a-z0-9_-]*)$/im);
11102
+ if (!match) return text;
11103
+ const skillName = match[1].toLowerCase();
11104
+ const skill = skillsService.findSkill(skillName);
11105
+ if (!skill) {
11106
+ const available = typeof skillsService.listSkills === "function" ? skillsService.listSkills().map((s) => s.name).join(", ") : "";
11107
+ return `Unknown skill: ${skillName}. Available skills: ${available}`;
11108
+ }
11109
+ return skill.content;
11110
+ }
10973
11111
  var Session = class {
10974
11112
  /** Injected by a test; when present it is the service, forever. */
10975
11113
  pinnedAiService;
@@ -10997,6 +11135,7 @@ var Session = class {
10997
11135
  unsubObserver = null;
10998
11136
  hostSessionId;
10999
11137
  currentSessionId;
11138
+ skillsService;
11000
11139
  constructor(deps) {
11001
11140
  this.config = deps.config;
11002
11141
  this.registry = deps.registry;
@@ -11015,6 +11154,7 @@ var Session = class {
11015
11154
  this.settingsFn = deps.settings;
11016
11155
  this.hostSessionId = deps.sessionId;
11017
11156
  this.currentSessionId = deps.sessionId ?? mintSessionId();
11157
+ this.skillsService = deps.skillsService ?? new SkillsService(this.workspaceRootFn());
11018
11158
  this.approvals = new PendingApprovals({
11019
11159
  onRequest: ({ id, request }) => this.broadcast({
11020
11160
  type: "approval_request",
@@ -11109,7 +11249,7 @@ var Session = class {
11109
11249
  onTaskChanged: () => {
11110
11250
  if (!this.plan) return;
11111
11251
  const tasks = this.store.allTasks;
11112
- this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
11252
+ this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
11113
11253
  },
11114
11254
  onQueueReady: () => {
11115
11255
  this.broadcast({ type: "queue_ready" });
@@ -11127,7 +11267,7 @@ var Session = class {
11127
11267
  onTick: () => {
11128
11268
  if (!this.plan) return;
11129
11269
  const tasks = this.store.allTasks;
11130
- this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
11270
+ this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
11131
11271
  },
11132
11272
  onExecutionComplete: () => {
11133
11273
  if (!this.plan) return;
@@ -11339,7 +11479,7 @@ var Session = class {
11339
11479
  */
11340
11480
  async startPlanning(goal, runners, options) {
11341
11481
  this.plan = null;
11342
- this.goal = goal;
11482
+ this.goal = this.resolveSkillInvocation(goal);
11343
11483
  this.remintSessionId();
11344
11484
  this.beginFreshPlan();
11345
11485
  const enabled = this.config.enabledRunners;
@@ -11348,7 +11488,7 @@ var Session = class {
11348
11488
  const modelsByRunner = await this.modelResolver.modelsForRunners(chosenRunners);
11349
11489
  this.modelsCache = modelsByRunner;
11350
11490
  const runnerModes = this.runnerModesFor(chosenRunners);
11351
- const { grillMeEnabled, prdEnabled, reviewEnabled, verificationEnabled, researchSubagentsEnabled, modelAllowlist } = this.settingsFn();
11491
+ const { verificationEnabled, modelAllowlist } = this.settingsFn();
11352
11492
  this.currentAllowlist = modelAllowlist ?? {};
11353
11493
  const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
11354
11494
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -11358,22 +11498,18 @@ var Session = class {
11358
11498
  status: "draft",
11359
11499
  runners: chosenRunners,
11360
11500
  lastUpdated: now,
11361
- researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: goal, timestamp: now }],
11362
- conversationHistory: [{ role: "user", content: goal, timestamp: now }]
11501
+ researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: this.goal, timestamp: now }],
11502
+ conversationHistory: [{ role: "user", content: this.goal, timestamp: now }]
11363
11503
  };
11364
11504
  const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
11365
11505
  try {
11366
11506
  const turn = await this.aiService.startConversation({
11367
- goal,
11507
+ goal: this.goal,
11368
11508
  runners: chosenRunners,
11369
11509
  modelsByRunner: filteredModels,
11370
11510
  runnerModes,
11371
11511
  autonomousDefault: this.config.autonomousMode,
11372
- grillMeEnabled: grillMeEnabled ?? false,
11373
- prdEnabled: prdEnabled ?? false,
11374
- reviewEnabled: reviewEnabled ?? false,
11375
11512
  verificationEnabled: verificationEnabled ?? false,
11376
- researchSubagentsEnabled: researchSubagentsEnabled ?? false,
11377
11513
  fs: this.fsAdapter,
11378
11514
  fetcher: this.fetcher,
11379
11515
  onProgress: (p) => this.translateProgress(p),
@@ -11385,21 +11521,22 @@ var Session = class {
11385
11521
  }
11386
11522
  }
11387
11523
  /**
11388
- * Every subsequent user reply in the planning conversation — grill-me
11389
- * answers, PRD accept/adjust, outline confirm. One branch, no phase ladder.
11524
+ * Every subsequent user reply in the planning conversation — clarifying
11525
+ * answers, outline confirm. One branch, no phase ladder.
11390
11526
  */
11391
11527
  async continueConversation(userMessage, options) {
11392
11528
  if (!this.plan) throw new Error("No planning conversation to continue");
11393
11529
  const priorHistory = this.plan.conversationHistory ?? [];
11394
11530
  const priorResearchLog = this.plan.researchLog ?? [];
11395
11531
  const now = (/* @__PURE__ */ new Date()).toISOString();
11396
- this.appendTranscript("user", userMessage, now);
11532
+ const resolved = this.resolveSkillInvocation(userMessage);
11533
+ this.appendTranscript("user", resolved, now);
11397
11534
  const historyAfterOwnAppend = this.plan.conversationHistory;
11398
- this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: userMessage, timestamp: now }];
11535
+ this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: resolved, timestamp: now }];
11399
11536
  const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
11400
11537
  const outgoing = contextBlock ? `${contextBlock}
11401
11538
 
11402
- ${userMessage}` : userMessage;
11539
+ ${resolved}` : resolved;
11403
11540
  const aiService = this.aiService;
11404
11541
  const canContinueLive = aiService.hasActiveConversation() && (aiService.conversationMatchesConfig?.() ?? true);
11405
11542
  const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
@@ -11539,8 +11676,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
11539
11676
  * The catalog the planner may actually draw from — model ids and task-mode
11540
11677
  * ids, per selected runner — emitted on EVERY turn, before any plan exists
11541
11678
  * or after. The system prompt shows this once at conversation start; a long
11542
- * grill-me interview or PRD negotiation outlives that single showing and the
11543
- * planner starts misquoting it, so this re-states it per turn instead.
11679
+ * clarifying conversation outlives that single showing and the planner
11680
+ * starts misquoting it, so this re-states it per turn instead.
11544
11681
  *
11545
11682
  * Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
11546
11683
  * one the system prompt used — a restricted allowlist stays a hard bound on
@@ -11671,7 +11808,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
11671
11808
  const modelsByRunner = await this.modelResolver.modelsForRunners(runners);
11672
11809
  this.modelsCache = modelsByRunner;
11673
11810
  const runnerModes = this.runnerModesFor(runners);
11674
- const { grillMeEnabled, prdEnabled, reviewEnabled, verificationEnabled, researchSubagentsEnabled, modelAllowlist } = this.settingsFn();
11811
+ const { verificationEnabled, modelAllowlist } = this.settingsFn();
11675
11812
  this.currentAllowlist = modelAllowlist ?? {};
11676
11813
  const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
11677
11814
  const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
@@ -11681,11 +11818,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
11681
11818
  modelsByRunner: filteredModels,
11682
11819
  runnerModes,
11683
11820
  autonomousDefault: this.config.autonomousMode,
11684
- grillMeEnabled: grillMeEnabled ?? false,
11685
- prdEnabled: prdEnabled ?? false,
11686
- reviewEnabled: reviewEnabled ?? false,
11687
11821
  verificationEnabled: verificationEnabled ?? false,
11688
- researchSubagentsEnabled: researchSubagentsEnabled ?? false,
11689
11822
  fs: this.fsAdapter,
11690
11823
  fetcher: this.fetcher,
11691
11824
  onProgress: (p) => this.translateProgress(p),
@@ -11698,6 +11831,17 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
11698
11831
  get isConversationActive() {
11699
11832
  return this.aiService.hasActiveConversation();
11700
11833
  }
11834
+ /**
11835
+ * Intercept a skill invocation (/skill-name) and substitute the skill's
11836
+ * markdown content BEFORE the message reaches the planner. Prevents runners
11837
+ * (Claude Code, OpenCode) from trying to resolve the skill in their own
11838
+ * directory instead of .ordewell/skills/. An unknown skill is surfaced to
11839
+ * the planner as a notice naming what IS available, rather than passing a
11840
+ * bare /unknown through to be mis-resolved.
11841
+ */
11842
+ resolveSkillInvocation(text) {
11843
+ return resolveSkillInvocation(text, this.skillsService);
11844
+ }
11701
11845
  /** Append one dialogue entry to the persisted transcript. Callers persist via the mutatePlan ritual. */
11702
11846
  appendTranscript(role, content, timestamp, kind) {
11703
11847
  if (!this.plan) return;
@@ -12444,17 +12588,25 @@ var HeadlessSession = class extends AbstractTerminalSession {
12444
12588
  spawnImpl;
12445
12589
  process = null;
12446
12590
  outputBuffer = "";
12591
+ controlStream = null;
12447
12592
  get isStarted() {
12448
12593
  return this.process !== null;
12449
12594
  }
12450
- start(launch, cwd, resolvedPath, env) {
12595
+ start(launch, cwd, resolvedPath, env, options) {
12451
12596
  if (this.exited || this.process) return;
12597
+ const stdio = ["pipe", "pipe", "pipe"];
12598
+ if (options?.controlChannel) stdio.push("pipe");
12452
12599
  this.process = this.spawnImpl(launch.file, launch.args, {
12453
12600
  env: withPath(process.env, resolvedPath, env),
12454
- stdio: ["pipe", "pipe", "pipe"],
12601
+ stdio,
12455
12602
  cwd,
12456
12603
  windowsVerbatimArguments: launch.verbatim
12457
12604
  });
12605
+ if (options?.controlChannel) {
12606
+ this.controlStream = this.process.stdio?.[3] ?? null;
12607
+ this.controlStream?.on("error", () => {
12608
+ });
12609
+ }
12458
12610
  const onData = (data) => {
12459
12611
  const raw = data.toString();
12460
12612
  this.outputBuffer += stripAnsi(raw);
@@ -12487,6 +12639,12 @@ Process error: ${err.message}
12487
12639
  this.process.stdin.write(text);
12488
12640
  }
12489
12641
  }
12642
+ /** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
12643
+ writeControl(text) {
12644
+ if (this.controlStream && !this.controlStream.destroyed) {
12645
+ this.controlStream.write(text);
12646
+ }
12647
+ }
12490
12648
  };
12491
12649
  var HeadlessRunner = class extends AbstractRunner {
12492
12650
  spawnImpl;
@@ -12512,7 +12670,7 @@ var HeadlessRunner = class extends AbstractRunner {
12512
12670
  return new HeadlessSession(id, taskId, this.spawnImpl);
12513
12671
  }
12514
12672
  /** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
12515
- async prepareLaunch(opts) {
12673
+ async prepareLaunch(opts, ptyOptions) {
12516
12674
  const interactive = this.defaultInteractive;
12517
12675
  const invocation = buildRunnerInvocation({
12518
12676
  runner: opts.runner,
@@ -12529,7 +12687,7 @@ var HeadlessRunner = class extends AbstractRunner {
12529
12687
  const manifest = opts.registry?.get(opts.runner)?.manifest;
12530
12688
  const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
12531
12689
  const resolvedPath = await this.resolvePath();
12532
- const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args) : { command: invocation.command, args: invocation.args };
12690
+ const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
12533
12691
  return {
12534
12692
  launch: await planDirectLaunch(command, args, this.launchDeps),
12535
12693
  resolvedPath,
@@ -12553,8 +12711,8 @@ var HeadlessRunner = class extends AbstractRunner {
12553
12711
  import { execFile as execFile2 } from "child_process";
12554
12712
  import { promisify as promisify4 } from "util";
12555
12713
  import { tmpdir } from "os";
12556
- import { join as join10 } from "path";
12557
- import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
12714
+ import { join as join13 } from "path";
12715
+ import { existsSync as existsSync8, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
12558
12716
 
12559
12717
  // src/utils/tmux.ts
12560
12718
  import { execSync as execSync2 } from "child_process";
@@ -12659,7 +12817,7 @@ var TmuxSession = class extends AbstractTerminalSession {
12659
12817
  if (this.exited) return;
12660
12818
  let content;
12661
12819
  try {
12662
- content = existsSync6(this.logPath) ? readFileSync4(this.logPath, "utf8") : "";
12820
+ content = existsSync8(this.logPath) ? readFileSync5(this.logPath, "utf8") : "";
12663
12821
  } catch {
12664
12822
  return;
12665
12823
  }
@@ -12838,7 +12996,7 @@ var TmuxRunner = class extends AbstractRunner {
12838
12996
  windowName,
12839
12997
  this.execFileImpl,
12840
12998
  this.pollIntervalMs,
12841
- join10(this.logDir, `${id}.log`),
12999
+ join13(this.logDir, `${id}.log`),
12842
13000
  this.socket
12843
13001
  );
12844
13002
  invocation.env = { ...invocation.env, PATH: await this.resolvePath() };
@@ -12850,22 +13008,21 @@ var TmuxRunner = class extends AbstractRunner {
12850
13008
 
12851
13009
  // src/utils/daemonToken.ts
12852
13010
  import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
12853
- import { closeSync, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeSync } from "fs";
12854
- import { homedir as homedir5 } from "os";
12855
- import { join as join11 } from "path";
13011
+ import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeSync } from "fs";
13012
+ import { join as join14 } from "path";
12856
13013
  var BEARER_PREFIX = "bearer ";
12857
13014
  var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
12858
13015
  var DAEMON_SUBPROTOCOL = "ordewell.v1";
12859
13016
  function configDir() {
12860
- return join11(homedir5(), ".config", "ordewell");
13017
+ return globalDataDir();
12861
13018
  }
12862
13019
  function daemonTokenPath(port) {
12863
- return join11(configDir(), `server-${port}.token`);
13020
+ return join14(configDir(), `server-${port}.token`);
12864
13021
  }
12865
13022
  function mintDaemonToken(port) {
12866
13023
  const token = randomBytes2(32).toString("base64url");
12867
13024
  const file = daemonTokenPath(port);
12868
- mkdirSync5(configDir(), { recursive: true });
13025
+ mkdirSync7(configDir(), { recursive: true });
12869
13026
  try {
12870
13027
  unlinkSync3(file);
12871
13028
  } catch {
@@ -12880,7 +13037,7 @@ function mintDaemonToken(port) {
12880
13037
  }
12881
13038
  function readDaemonToken(port) {
12882
13039
  try {
12883
- const token = readFileSync5(daemonTokenPath(port), "utf8").trim();
13040
+ const token = readFileSync6(daemonTokenPath(port), "utf8").trim();
12884
13041
  return token === "" ? void 0 : token;
12885
13042
  } catch {
12886
13043
  return void 0;
@@ -12921,19 +13078,18 @@ function tokensMatch(presented, expected) {
12921
13078
  }
12922
13079
 
12923
13080
  // src/plugins/RunnerRegistry.ts
12924
- import * as fs7 from "fs";
12925
- import * as path14 from "path";
12926
- import * as os5 from "os";
13081
+ import * as fs9 from "fs";
13082
+ import * as path17 from "path";
13083
+ import * as os4 from "os";
12927
13084
  import { execFileSync } from "child_process";
12928
13085
  import { randomBytes as randomBytes3 } from "crypto";
12929
13086
 
12930
13087
  // src/plugins/FsPluginStore.ts
12931
- import * as fs6 from "fs";
12932
- import * as path13 from "path";
12933
- import * as os4 from "os";
13088
+ import * as fs8 from "fs";
13089
+ import * as path16 from "path";
12934
13090
 
12935
13091
  // src/plugins/pluginNames.ts
12936
- import * as path12 from "path";
13092
+ import * as path15 from "path";
12937
13093
  var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
12938
13094
  var MAX_PLUGIN_NAME_LENGTH = 64;
12939
13095
  function isPlainPluginName(name) {
@@ -12948,10 +13104,10 @@ function assertPlainPluginName(name) {
12948
13104
  }
12949
13105
  function resolvePluginInstallDir(pluginsDir, name) {
12950
13106
  assertPlainPluginName(name);
12951
- const root = path12.resolve(pluginsDir);
12952
- const dest = path12.resolve(root, name);
12953
- const relative3 = path12.relative(root, dest);
12954
- if (relative3 !== name || relative3 === "" || path12.isAbsolute(relative3)) {
13107
+ const root = path15.resolve(pluginsDir);
13108
+ const dest = path15.resolve(root, name);
13109
+ const relative3 = path15.relative(root, dest);
13110
+ if (relative3 !== name || relative3 === "" || path15.isAbsolute(relative3)) {
12955
13111
  throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
12956
13112
  }
12957
13113
  return dest;
@@ -12966,13 +13122,13 @@ function isValidManifest(obj) {
12966
13122
 
12967
13123
  // src/plugins/FsPluginStore.ts
12968
13124
  function userPluginsDir() {
12969
- return path13.join(os4.homedir(), ".config", "ordewell", "plugins");
13125
+ return path16.join(globalDataDir(), "plugins");
12970
13126
  }
12971
13127
  function loadManifestFromDir(dir) {
12972
- const manifestPath = path13.join(dir, "manifest.json");
12973
- if (!fs6.existsSync(manifestPath)) return null;
13128
+ const manifestPath = path16.join(dir, "manifest.json");
13129
+ if (!fs8.existsSync(manifestPath)) return null;
12974
13130
  try {
12975
- const raw = fs6.readFileSync(manifestPath, "utf8");
13131
+ const raw = fs8.readFileSync(manifestPath, "utf8");
12976
13132
  const parsed = JSON.parse(raw);
12977
13133
  if (!isValidManifest(parsed)) return null;
12978
13134
  return parsed;
@@ -12980,31 +13136,31 @@ function loadManifestFromDir(dir) {
12980
13136
  return null;
12981
13137
  }
12982
13138
  }
12983
- function copyDirSync(src, dest) {
13139
+ function copyDirSync3(src, dest) {
12984
13140
  ensureDir(dest);
12985
- const entries = fs6.readdirSync(src, { withFileTypes: true });
13141
+ const entries = fs8.readdirSync(src, { withFileTypes: true });
12986
13142
  for (const entry of entries) {
12987
- const srcPath = path13.join(src, entry.name);
12988
- const destPath = path13.join(dest, entry.name);
13143
+ const srcPath = path16.join(src, entry.name);
13144
+ const destPath = path16.join(dest, entry.name);
12989
13145
  if (entry.isDirectory()) {
12990
- copyDirSync(srcPath, destPath);
13146
+ copyDirSync3(srcPath, destPath);
12991
13147
  } else {
12992
- fs6.copyFileSync(srcPath, destPath);
13148
+ fs8.copyFileSync(srcPath, destPath);
12993
13149
  }
12994
13150
  }
12995
13151
  }
12996
13152
  function rmDirSync(dir) {
12997
- if (!fs6.existsSync(dir)) return;
12998
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
13153
+ if (!fs8.existsSync(dir)) return;
13154
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
12999
13155
  for (const entry of entries) {
13000
- const p = path13.join(dir, entry.name);
13156
+ const p = path16.join(dir, entry.name);
13001
13157
  if (entry.isDirectory()) {
13002
13158
  rmDirSync(p);
13003
13159
  } else {
13004
- fs6.unlinkSync(p);
13160
+ fs8.unlinkSync(p);
13005
13161
  }
13006
13162
  }
13007
- fs6.rmdirSync(dir);
13163
+ fs8.rmdirSync(dir);
13008
13164
  }
13009
13165
  var FsPluginStore = class {
13010
13166
  getUserPluginsDir() {
@@ -13012,10 +13168,10 @@ var FsPluginStore = class {
13012
13168
  }
13013
13169
  listUserPluginDirs() {
13014
13170
  const dir = userPluginsDir();
13015
- if (!fs6.existsSync(dir)) return [];
13171
+ if (!fs8.existsSync(dir)) return [];
13016
13172
  let entries;
13017
13173
  try {
13018
- entries = fs6.readdirSync(dir, { withFileTypes: true });
13174
+ entries = fs8.readdirSync(dir, { withFileTypes: true });
13019
13175
  } catch {
13020
13176
  return [];
13021
13177
  }
@@ -13023,7 +13179,7 @@ var FsPluginStore = class {
13023
13179
  }
13024
13180
  listDir(dir) {
13025
13181
  try {
13026
- return fs6.readdirSync(dir);
13182
+ return fs8.readdirSync(dir);
13027
13183
  } catch {
13028
13184
  return [];
13029
13185
  }
@@ -13032,7 +13188,7 @@ var FsPluginStore = class {
13032
13188
  return loadManifestFromDir(pluginDir);
13033
13189
  }
13034
13190
  copyDir(sourceDir, destDir) {
13035
- copyDirSync(sourceDir, destDir);
13191
+ copyDirSync3(sourceDir, destDir);
13036
13192
  }
13037
13193
  removeDir(dir) {
13038
13194
  rmDirSync(dir);
@@ -13041,20 +13197,20 @@ var FsPluginStore = class {
13041
13197
  ensureDir(dir);
13042
13198
  }
13043
13199
  writeFile(filePath, content) {
13044
- fs6.writeFileSync(filePath, content);
13200
+ fs8.writeFileSync(filePath, content);
13045
13201
  }
13046
13202
  readFile(filePath) {
13047
13203
  try {
13048
- return fs6.readFileSync(filePath, "utf8");
13204
+ return fs8.readFileSync(filePath, "utf8");
13049
13205
  } catch {
13050
13206
  return null;
13051
13207
  }
13052
13208
  }
13053
- dirExists(path17) {
13054
- return fs6.existsSync(path17) && fs6.statSync(path17).isDirectory();
13209
+ dirExists(path20) {
13210
+ return fs8.existsSync(path20) && fs8.statSync(path20).isDirectory();
13055
13211
  }
13056
- exists(path17) {
13057
- return fs6.existsSync(path17);
13212
+ exists(path20) {
13213
+ return fs8.existsSync(path20);
13058
13214
  }
13059
13215
  };
13060
13216
 
@@ -13481,7 +13637,7 @@ var RunnerRegistry = class {
13481
13637
  return entry?.source === "builtin";
13482
13638
  }
13483
13639
  installFromPath(sourcePath) {
13484
- const absPath = path14.resolve(sourcePath);
13640
+ const absPath = path17.resolve(sourcePath);
13485
13641
  if (!this.store.dirExists(absPath)) {
13486
13642
  throw new Error(`Plugin source must be a directory: ${absPath}`);
13487
13643
  }
@@ -13493,7 +13649,7 @@ var RunnerRegistry = class {
13493
13649
  }
13494
13650
  installFromGit(url) {
13495
13651
  const validated = assertInstallablePluginUrl(url);
13496
- const tmpDir = path14.join(os5.tmpdir(), `ordewell-plugin-${randomBytes3(12).toString("hex")}`);
13652
+ const tmpDir = path17.join(os4.tmpdir(), `ordewell-plugin-${randomBytes3(12).toString("hex")}`);
13497
13653
  this.store.ensureDir(tmpDir);
13498
13654
  try {
13499
13655
  try {
@@ -13506,7 +13662,7 @@ var RunnerRegistry = class {
13506
13662
  const hasManifest = contents.some((f) => f === "manifest.json");
13507
13663
  let sourceDir = tmpDir;
13508
13664
  if (!hasManifest) {
13509
- const onlyEntry = contents.length === 1 ? path14.join(tmpDir, contents[0]) : void 0;
13665
+ const onlyEntry = contents.length === 1 ? path17.join(tmpDir, contents[0]) : void 0;
13510
13666
  if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
13511
13667
  throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
13512
13668
  }
@@ -13554,7 +13710,7 @@ var RunnerRegistry = class {
13554
13710
  }
13555
13711
  createSkeleton(name, outputDir) {
13556
13712
  assertPlainPluginName(name);
13557
- const dir = path14.resolve(outputDir, name);
13713
+ const dir = path17.resolve(outputDir, name);
13558
13714
  this.store.ensureDir(dir);
13559
13715
  const manifest = {
13560
13716
  name,
@@ -13586,8 +13742,8 @@ var RunnerRegistry = class {
13586
13742
  ]
13587
13743
  }
13588
13744
  };
13589
- this.store.writeFile(path14.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
13590
- const runSh = path14.join(dir, "run.sh");
13745
+ this.store.writeFile(path17.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
13746
+ const runSh = path17.join(dir, "run.sh");
13591
13747
  const scriptContent = `#!/usr/bin/env bash
13592
13748
  # Custom runner script for ${name}
13593
13749
  # Receives:
@@ -13613,30 +13769,30 @@ echo "Running ${name} with prompt: $prompt"
13613
13769
  };
13614
13770
  function fsChmodSync(fpath, mode) {
13615
13771
  try {
13616
- fs7.chmodSync(fpath, mode);
13772
+ fs9.chmodSync(fpath, mode);
13617
13773
  } catch {
13618
13774
  }
13619
13775
  }
13620
13776
 
13621
13777
  // src/utils/stateStore.ts
13622
- import * as fs8 from "fs";
13623
- import * as path15 from "path";
13778
+ import * as fs10 from "fs";
13779
+ import * as path18 from "path";
13624
13780
  var STATE_FILE = "state.json";
13625
13781
  function getStatePath(baseDir) {
13626
- return path15.join(getStateDir(baseDir), STATE_FILE);
13782
+ return path18.join(getStateDir(baseDir), STATE_FILE);
13627
13783
  }
13628
13784
  function saveState(plan, baseDir) {
13629
13785
  const statePath = getStatePath(baseDir ?? process.cwd());
13630
- ensureDir(path15.dirname(statePath));
13786
+ ensureDir(path18.dirname(statePath));
13631
13787
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
13632
- fs8.writeFileSync(statePath, JSON.stringify(plan, null, 2));
13788
+ fs10.writeFileSync(statePath, JSON.stringify(plan, null, 2));
13633
13789
  }
13634
13790
  function loadState(baseDir, logger = defaultLogger) {
13635
13791
  const dir = baseDir ?? process.cwd();
13636
13792
  const statePath = getStatePath(dir);
13637
- if (!fs8.existsSync(statePath)) return null;
13793
+ if (!fs10.existsSync(statePath)) return null;
13638
13794
  try {
13639
- const raw = fs8.readFileSync(statePath, "utf-8");
13795
+ const raw = fs10.readFileSync(statePath, "utf-8");
13640
13796
  const parsed = JSON.parse(raw);
13641
13797
  if (!Array.isArray(parsed.runners)) {
13642
13798
  logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
@@ -13651,29 +13807,24 @@ function loadState(baseDir, logger = defaultLogger) {
13651
13807
  function clearState(baseDir) {
13652
13808
  const dir = baseDir ?? process.cwd();
13653
13809
  const statePath = getStatePath(dir);
13654
- if (fs8.existsSync(statePath)) fs8.unlinkSync(statePath);
13810
+ if (fs10.existsSync(statePath)) fs10.unlinkSync(statePath);
13655
13811
  }
13656
13812
  function stateExists(baseDir) {
13657
13813
  const dir = baseDir ?? process.cwd();
13658
- return fs8.existsSync(getStatePath(dir));
13814
+ return fs10.existsSync(getStatePath(dir));
13659
13815
  }
13660
13816
 
13661
13817
  // src/services/SettingsService.ts
13662
- import * as fs9 from "fs";
13663
- import * as path16 from "path";
13664
- import * as os6 from "os";
13818
+ import * as fs11 from "fs";
13819
+ import * as path19 from "path";
13665
13820
  var DEFAULTS = {
13666
- grillMe: { enabled: false },
13667
13821
  tdd: { enabled: true },
13668
- prd: { enabled: false },
13669
- review: { enabled: false },
13670
- verification: { enabled: false },
13671
- researchSubagents: { enabled: false }
13822
+ verification: { enabled: false }
13672
13823
  };
13673
13824
  function getSettingsPath() {
13674
13825
  const override = process.env.ORDEWELL_SETTINGS_PATH;
13675
13826
  if (override && override.trim()) return override.trim();
13676
- return path16.join(os6.homedir(), ".config", "ordewell", "settings.json");
13827
+ return path19.join(globalDataDir(), "settings.json");
13677
13828
  }
13678
13829
  function readPlannerModels(raw) {
13679
13830
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -13706,43 +13857,19 @@ var SettingsService = class {
13706
13857
  }
13707
13858
  fileMtimeMs() {
13708
13859
  try {
13709
- return fs9.statSync(this.filePath).mtimeMs;
13860
+ return fs11.statSync(this.filePath).mtimeMs;
13710
13861
  } catch {
13711
13862
  return null;
13712
13863
  }
13713
13864
  }
13714
- getGrillMe() {
13715
- return this.getAll().grillMe.enabled;
13716
- }
13717
13865
  getTdd() {
13718
13866
  return this.getAll().tdd.enabled;
13719
13867
  }
13720
- getPrd() {
13721
- return this.getAll().prd.enabled;
13722
- }
13723
- setGrillMe(enabled) {
13724
- this.getAll();
13725
- this.cache.grillMe.enabled = enabled;
13726
- this.persist();
13727
- }
13728
13868
  setTdd(enabled) {
13729
13869
  this.getAll();
13730
13870
  this.cache.tdd.enabled = enabled;
13731
13871
  this.persist();
13732
13872
  }
13733
- setPrd(enabled) {
13734
- this.getAll();
13735
- this.cache.prd.enabled = enabled;
13736
- this.persist();
13737
- }
13738
- getReview() {
13739
- return this.getAll().review.enabled;
13740
- }
13741
- setReview(enabled) {
13742
- this.getAll();
13743
- this.cache.review.enabled = enabled;
13744
- this.persist();
13745
- }
13746
13873
  getVerification() {
13747
13874
  return this.getAll().verification.enabled;
13748
13875
  }
@@ -13751,14 +13878,6 @@ var SettingsService = class {
13751
13878
  this.cache.verification.enabled = enabled;
13752
13879
  this.persist();
13753
13880
  }
13754
- getResearchSubagents() {
13755
- return this.getAll().researchSubagents.enabled;
13756
- }
13757
- setResearchSubagents(enabled) {
13758
- this.getAll();
13759
- this.cache.researchSubagents.enabled = enabled;
13760
- this.persist();
13761
- }
13762
13881
  getModelAllowlist(runner) {
13763
13882
  return this.getAll().modelAllowlist?.[runner];
13764
13883
  }
@@ -13808,18 +13927,13 @@ var SettingsService = class {
13808
13927
  this.persist();
13809
13928
  }
13810
13929
  load() {
13930
+ migrateOldConfigDir();
13811
13931
  try {
13812
- if (fs9.existsSync(this.filePath)) {
13813
- const raw = JSON.parse(fs9.readFileSync(this.filePath, "utf-8"));
13932
+ if (fs11.existsSync(this.filePath)) {
13933
+ const raw = JSON.parse(fs11.readFileSync(this.filePath, "utf-8"));
13814
13934
  const settings = {
13815
- grillMe: { enabled: raw.grillMe?.enabled ?? DEFAULTS.grillMe.enabled },
13816
13935
  tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
13817
- prd: { enabled: raw.prd?.enabled ?? DEFAULTS.prd.enabled },
13818
- // `verify` is the legacy key for review mode; keep reading it so old settings files survive the rename.
13819
- // The evidence-based verification mode uses the distinct `verification` key to avoid colliding with it.
13820
- review: { enabled: raw.review?.enabled ?? raw.verify?.enabled ?? DEFAULTS.review.enabled },
13821
- verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled },
13822
- researchSubagents: { enabled: raw.researchSubagents?.enabled ?? DEFAULTS.researchSubagents.enabled }
13936
+ verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
13823
13937
  };
13824
13938
  if (raw.modelAllowlist !== void 0) {
13825
13939
  settings.modelAllowlist = raw.modelAllowlist;
@@ -13835,14 +13949,14 @@ var SettingsService = class {
13835
13949
  }
13836
13950
  } catch {
13837
13951
  }
13838
- return { ...DEFAULTS, grillMe: { ...DEFAULTS.grillMe }, tdd: { ...DEFAULTS.tdd }, prd: { ...DEFAULTS.prd }, review: { ...DEFAULTS.review }, verification: { ...DEFAULTS.verification }, researchSubagents: { ...DEFAULTS.researchSubagents } };
13952
+ return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
13839
13953
  }
13840
13954
  persist() {
13841
- const dir = path16.dirname(this.filePath);
13842
- if (!fs9.existsSync(dir)) {
13843
- fs9.mkdirSync(dir, { recursive: true });
13955
+ const dir = path19.dirname(this.filePath);
13956
+ if (!fs11.existsSync(dir)) {
13957
+ fs11.mkdirSync(dir, { recursive: true });
13844
13958
  }
13845
- fs9.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
13959
+ fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
13846
13960
  this.cachedMtimeMs = this.fileMtimeMs();
13847
13961
  }
13848
13962
  };
@@ -13888,9 +14002,11 @@ export {
13888
14002
  AbstractRunner,
13889
14003
  AbstractTerminalSession,
13890
14004
  ApprovalPolicy,
14005
+ BUILTIN_SKILL_NAMES,
13891
14006
  BaseAiService,
13892
14007
  BaseConfig,
13893
14008
  BaseFileSystem,
14009
+ CHECKPOINT_TRUNCATE_LENGTH,
13894
14010
  CLAUDE_CODE_MANIFEST,
13895
14011
  CLI_PROVIDERS,
13896
14012
  CMD_EXE_MAX_COMMAND_LINE,
@@ -13946,6 +14062,7 @@ export {
13946
14062
  SYMBOL_LANGUAGES,
13947
14063
  Session,
13948
14064
  SettingsService,
14065
+ SkillsService,
13949
14066
  StdioAgentAdapter,
13950
14067
  TASK_OPS_ENVELOPE_KEY,
13951
14068
  TASK_QUERY_ANSWER_OR_OPS,
@@ -14011,6 +14128,7 @@ export {
14011
14128
  configuredProviders,
14012
14129
  createAiService,
14013
14130
  createEmptyPlan,
14131
+ createSkillsService,
14014
14132
  createTask,
14015
14133
  daemonTokenPath,
14016
14134
  defaultLogger,
@@ -14037,12 +14155,14 @@ export {
14037
14155
  filterModelsForPrompt,
14038
14156
  filteredBuildModes,
14039
14157
  flattenTasks,
14158
+ flattenTasksWithParents,
14040
14159
  formatSearchOutput,
14041
14160
  generatePlanWithRepair,
14042
14161
  getLatestSession,
14043
14162
  getProviderMeta,
14044
14163
  getSettingsPath,
14045
14164
  getStateDir,
14165
+ globalDataDir,
14046
14166
  grantScopeFor,
14047
14167
  hasTmux,
14048
14168
  includeGlobFor,
@@ -14062,6 +14182,7 @@ export {
14062
14182
  looksLikePlanAttempt,
14063
14183
  mapAgentTool,
14064
14184
  migrateLegacyPlan,
14185
+ migrateOldConfigDir,
14065
14186
  migratePlanState,
14066
14187
  migrateTask,
14067
14188
  mintDaemonToken,
@@ -14096,10 +14217,12 @@ export {
14096
14217
  resolveArgs,
14097
14218
  resolveDefaultMode,
14098
14219
  resolveModelShortcut,
14220
+ resolveOrderLabel,
14099
14221
  resolvePluginInstallDir,
14100
14222
  resolveProvider,
14101
14223
  resolveProviderFromPrefix,
14102
14224
  resolveResearchShell,
14225
+ resolveSkillInvocation,
14103
14226
  resolveTaskMode,
14104
14227
  resolveWithin,
14105
14228
  retargetTaskRunner,
@@ -14122,6 +14245,7 @@ export {
14122
14245
  summarizeOutput,
14123
14246
  summarizeToolCall,
14124
14247
  taskOpsRejectedPrompt,
14248
+ taskOrderLabel,
14125
14249
  taskQuerySignature,
14126
14250
  textHasTaskOps,
14127
14251
  textHasTaskQuery,
@@ -14131,6 +14255,7 @@ export {
14131
14255
  toOrchestratorOptions,
14132
14256
  tokenSubprotocols,
14133
14257
  tokensMatch,
14258
+ truncateCheckpointSummary,
14134
14259
  truncatedPlanReEmitPrompt,
14135
14260
  updateTaskInPlan,
14136
14261
  validateModifiedPlan,