session-orchestrator 3.24.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * learnings/evolve-telemetry.mjs — mechanical `orchestrator.evolve.completed` /
3
+ * `orchestrator.dialectic.completed` emitters for the two `/evolve` run
4
+ * classes (issue #1206).
5
+ *
6
+ * Both events were prose-only until this module: five emit sites lived in
7
+ * `skills/evolve/SKILL.md` as `node scripts/emit-event.mjs ...` bash blocks the
8
+ * coordinator had to remember to run — a mechanism hiding inside prose, the
9
+ * same shape #1017 already fixed once for the learnings-store write path
10
+ * (`pruneLearnings()` in `./expiry-sweep.mjs`). This module gives both events
11
+ * a CODE call site instead: `emitEvolveCompleted()` is invoked from
12
+ * `scripts/sweep-expired-learnings.mjs`'s `--prune` exit path (the ONE store
13
+ * write `/evolve analyze` already performs), and `recordDialecticRun()` is
14
+ * invoked from `scripts/dialectic-deriver.mjs::runDialecticDeriver()` for the
15
+ * three abort statuses it can determine on its own, plus dry-run success —
16
+ * see that module's header for why APPLY-mode success and the two
17
+ * throw-based abort classes (`unknown-model`, `subagent-crash`) still call
18
+ * `recordDialecticRun()` from `skills/evolve/SKILL.md` prose (the merge and
19
+ * the dispatch-agent try/catch both happen one layer up, outside this pure
20
+ * pipeline).
21
+ *
22
+ * Both emitters are best-effort and try/catch-wrapped — same posture as
23
+ * `emitReconcileCompleted` in `scripts/lib/reconcile/engine.mjs` and
24
+ * `decideAndRecordAutoDialectic` in `scripts/lib/auto-dialectic.mjs` — because
25
+ * `emitEvent()` THROWS `EventValidationError` on a malformed record, and a
26
+ * telemetry failure must never break the `/evolve` pipeline itself.
27
+ *
28
+ * Both REFUSE to emit without an explicit `repoRoot` (#1119): most unit tests
29
+ * call the underlying pipelines (`pruneLearnings`, `runDialecticDeriver`)
30
+ * without one, and a `SO_PROJECT_DIR` fallback would silently append
31
+ * synthetic records to the operator's REAL fleet ledger on every `npm test`.
32
+ */
33
+
34
+ const REASON_MAX_CHARS = 300;
35
+
36
+ /**
37
+ * Record one `/evolve analyze` run — success or abort.
38
+ *
39
+ * @param {object} [opts]
40
+ * @param {string} opts.repoRoot - REQUIRED. No SO_PROJECT_DIR fallback (#1119).
41
+ * @param {number} [opts.appended=0] - new learnings written (Step 3.5(4)).
42
+ * @param {number} [opts.boosted=0] - existing learnings reinforced (Step 3.5(2)).
43
+ * @param {number} [opts.pruned=0] - `$PRUNE.archived` total from the
44
+ * `--prune` call this function is invoked alongside.
45
+ * @param {number} [opts.promoted=0] - always 0 from this call site; promotion
46
+ * to `public` scope is a separate CLI (`npm run share:hw-learnings -- --promote`).
47
+ * @param {number} [opts.durationMs] - elapsed ms since the run's telemetry
48
+ * start marker; omitted (never fabricated) when not measured.
49
+ * @param {string[]} [opts.skipped=[]] - HR-105: optional steps that RAN but
50
+ * were themselves skipped this run (e.g. `skill-evolution-off`,
51
+ * `vault-mirror-off`) — distinguishes "ran, a step inside it skipped" from
52
+ * "did not run at all" (the `aborted` form below). Included only when
53
+ * non-empty; success-form counters are ALWAYS present regardless.
54
+ * @param {string} [opts.aborted] - when given (non-empty string), emit the
55
+ * ABORT form instead of the success form: `{aborted, reason, duration_ms}`.
56
+ * @param {string} [opts.reason] - abort message shown to the user, clamped to
57
+ * {@link REASON_MAX_CHARS}.
58
+ * @param {Function} [opts.emitFn] - DI hook for testing; defaults to the real
59
+ * `emitEvent` from `../events.mjs` (dynamically imported, so a caller that
60
+ * supplies `emitFn` never touches the real module at all).
61
+ * @returns {Promise<void>}
62
+ */
63
+ export async function emitEvolveCompleted({
64
+ repoRoot,
65
+ appended = 0,
66
+ boosted = 0,
67
+ pruned = 0,
68
+ promoted = 0,
69
+ durationMs,
70
+ skipped = [],
71
+ aborted,
72
+ reason,
73
+ emitFn,
74
+ } = {}) {
75
+ if (typeof repoRoot !== 'string' || repoRoot.trim() === '') {
76
+ process.stderr.write(
77
+ 'evolve: skipped orchestrator.evolve.completed — no repoRoot given; ' +
78
+ 'refusing the ambient SO_PROJECT_DIR destination (#1119).\n',
79
+ );
80
+ return;
81
+ }
82
+
83
+ const payload =
84
+ typeof aborted === 'string' && aborted.length > 0
85
+ ? {
86
+ aborted,
87
+ reason: typeof reason === 'string' ? reason.slice(0, REASON_MAX_CHARS) : reason,
88
+ duration_ms: durationMs,
89
+ }
90
+ : {
91
+ appended,
92
+ boosted,
93
+ pruned,
94
+ promoted,
95
+ duration_ms: durationMs,
96
+ ...(Array.isArray(skipped) && skipped.length > 0 ? { skipped } : {}),
97
+ };
98
+
99
+ try {
100
+ const emit = emitFn ?? (await import('../events.mjs')).emitEvent;
101
+ await emit('orchestrator.evolve.completed', payload, { repoRoot });
102
+ } catch (err) {
103
+ // best-effort — a telemetry failure must never break the caller's pipeline,
104
+ // but a silently swallowed failure is exactly the 0-records unfalsifiability
105
+ // #1206 was filed against — so it is surfaced on stderr instead.
106
+ process.stderr.write(
107
+ '⚠ evolve-telemetry: emit failed (orchestrator.evolve.completed): ' +
108
+ (err?.message ?? String(err)) +
109
+ '\n',
110
+ );
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Record one `/evolve --dialectic` run — success or abort.
116
+ *
117
+ * @param {object} [opts]
118
+ * @param {string} opts.repoRoot - REQUIRED, same #1119 refusal as {@link emitEvolveCompleted}.
119
+ * @param {string} opts.status - `ok` for the success form, or one of the abort
120
+ * slugs `unknown-model` | `budget-exceeded` | `would-empty-card` |
121
+ * `empty-input` | `subagent-crash` (mirrors `skills/evolve/SKILL.md` Step 6.5
122
+ * verbatim) — any other non-`ok` string is emitted as the abort form as-is.
123
+ * @param {'dry-run'|'apply'} [opts.mode] - success form only.
124
+ * @param {number} [opts.userDeltas=0] - success form only.
125
+ * @param {number} [opts.agentDeltas=0] - success form only.
126
+ * @param {number} [opts.tokensIn] - success form only; omitted when not measured.
127
+ * @param {number} [opts.tokensOut] - success form only; omitted when not measured.
128
+ * @param {number} [opts.durationMs] - omitted (never fabricated) when not measured.
129
+ * @param {Function} [opts.emitFn] - DI hook for testing; defaults to the real
130
+ * `emitEvent` from `../events.mjs` (dynamically imported, so a caller that
131
+ * supplies `emitFn` never touches the real module at all).
132
+ * @returns {Promise<void>}
133
+ */
134
+ export async function recordDialecticRun({
135
+ repoRoot,
136
+ status,
137
+ mode,
138
+ userDeltas = 0,
139
+ agentDeltas = 0,
140
+ tokensIn,
141
+ tokensOut,
142
+ durationMs,
143
+ emitFn,
144
+ } = {}) {
145
+ if (typeof repoRoot !== 'string' || repoRoot.trim() === '') {
146
+ process.stderr.write(
147
+ 'dialectic: skipped orchestrator.dialectic.completed — no repoRoot given; ' +
148
+ 'refusing the ambient SO_PROJECT_DIR destination (#1119).\n',
149
+ );
150
+ return;
151
+ }
152
+
153
+ const payload =
154
+ status === 'ok'
155
+ ? {
156
+ mode,
157
+ user_deltas: userDeltas,
158
+ agent_deltas: agentDeltas,
159
+ tokens_in: tokensIn,
160
+ tokens_out: tokensOut,
161
+ duration_ms: durationMs,
162
+ }
163
+ : { aborted: status, duration_ms: durationMs };
164
+
165
+ try {
166
+ const emit = emitFn ?? (await import('../events.mjs')).emitEvent;
167
+ await emit('orchestrator.dialectic.completed', payload, { repoRoot });
168
+ } catch (err) {
169
+ // best-effort — a telemetry failure must never break the caller's pipeline,
170
+ // but a silently swallowed failure is exactly the 0-records unfalsifiability
171
+ // #1206 was filed against — so it is surfaced on stderr instead.
172
+ process.stderr.write(
173
+ '⚠ evolve-telemetry: emit failed (orchestrator.dialectic.completed): ' +
174
+ (err?.message ?? String(err)) +
175
+ '\n',
176
+ );
177
+ }
178
+ }
@@ -189,22 +189,76 @@ export async function appendLearning(filePath, entry) {
189
189
  const BACKUP_KEEP = 3;
190
190
 
191
191
  /**
192
- * Best-effort keep-N rotation of `${baseName}.bak-*` siblings in `dir`.
192
+ * Timestamp suffix of a backup sibling of `baseName`, or `null` if `fileName`
193
+ * is not one. Accepts BOTH historical delimiters after `.bak` (#1173): the
194
+ * canonical `-` this module writes, and the legacy `.` that pre-#721 writers
195
+ * (e.g. `learnings.jsonl.bak.evolve-<ts>`) still left on disk in consumer
196
+ * repos — those files were invisible to rotation AND to restore.
197
+ * The delimiter check is what keeps a `${baseName}.backfill-tmp-*` scratch
198
+ * file out: `.bak` followed by `f` is not a backup.
199
+ *
200
+ * REVISIT-TRIGGER for the legacy `.` branch (BV-004): `-` has been the only
201
+ * delimiter any writer emits since #721, so the `.` arm exists purely to keep
202
+ * pre-#721 files on disk visible to rotation and restore. Drop it once no fleet
203
+ * repo reports a `.bak.` sibling — re-measure via the fleet sweep, do not infer
204
+ * it from this repo alone.
205
+ *
206
+ * @param {string} baseName — basename of the store file (e.g. `learnings.jsonl`)
207
+ * @param {string} fileName — sibling filename to classify
208
+ * @returns {string|null} the suffix after the delimiter, or `null`
209
+ */
210
+ export function backupSuffixOf(baseName, fileName) {
211
+ const stem = `${baseName}.bak`;
212
+ if (!fileName.startsWith(stem)) return null;
213
+ const delim = fileName[stem.length];
214
+ return delim === '-' || delim === '.' ? fileName.slice(stem.length + 1) : null;
215
+ }
216
+
217
+ /**
218
+ * True when `fileName` is a backup sibling of `baseName` in either delimiter
219
+ * form. Shared by this module's rotation and by `backfill-learnings-from-vault`'s
220
+ * restore sweep so the two can never drift apart again (#1173).
221
+ *
222
+ * @param {string} baseName
223
+ * @param {string} fileName
224
+ * @returns {boolean}
225
+ */
226
+ export function isBackupOf(baseName, fileName) {
227
+ return backupSuffixOf(baseName, fileName) !== null;
228
+ }
229
+
230
+ /**
231
+ * Best-effort keep-N rotation of `${baseName}.bak[-.]*` siblings in `dir`.
193
232
  * The `.bak-<ISO>` naming uses ISO 8601 with `:`/`.` swapped for `-`, so a
194
- * plain lexical sort of the filenames is chronological. Oldest beyond `keep`
195
- * are unlinked. Errors PROPAGATE the single caller wraps this in try/catch so
196
- * a rotation failure can never abort the rewrite it protects.
233
+ * plain lexical sort of the SUFFIX is chronological. Sorting on the suffix
234
+ * rather than the whole filename is load-bearing across the two delimiter
235
+ * forms (#1173): `-` (0x2D) sorts before `.` (0x2E), so a whole-name sort
236
+ * would group every legacy dot-form file after every hyphen-form one
237
+ * regardless of age, and rotation would prune only hyphen-form backups.
238
+ * Oldest beyond `keep` are unlinked. Errors PROPAGATE — the single caller
239
+ * wraps this in try/catch so a rotation failure can never abort the rewrite it
240
+ * protects.
197
241
  *
198
242
  * @param {string} dir — directory holding the store + its backups
199
243
  * @param {string} baseName — basename of the store file (e.g. `learnings.jsonl`)
200
244
  * @param {number} keep — number of newest backups to retain
201
245
  */
202
246
  async function rotateBackups(dir, baseName, keep = BACKUP_KEEP) {
203
- const prefix = `${baseName}.bak-`;
204
247
  const names = await readdir(dir);
205
248
  // Lexical sort == chronological for the dash-normalized ISO suffix. Ascending
206
249
  // → oldest first, so the head of the list is what we prune.
207
- const backups = names.filter((n) => n.startsWith(prefix)).sort();
250
+ const backups = names
251
+ .map((n) => ({ name: n, suffix: backupSuffixOf(baseName, n) }))
252
+ .filter((e) => e.suffix !== null)
253
+ // Sort key strips a leading non-digit label so a labelled legacy suffix
254
+ // (`.bak.evolve-<ts>`) compares against a bare one (`.bak-<ts>`) on the
255
+ // timestamp, not on the label. Ceiling (#1173): this assumes the label
256
+ // PRECEDES the timestamp, which holds for every form observed on disk; a
257
+ // suffix carrying no digits at all sorts oldest and is pruned first.
258
+ // Revisit if a writer ever appends its label after the timestamp.
259
+ .map((e) => ({ ...e, key: e.suffix.replace(/^\D*/, '') }))
260
+ .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
261
+ .map((e) => e.name);
208
262
  const stale = backups.slice(0, Math.max(0, backups.length - keep));
209
263
  for (const name of stale) {
210
264
  await unlink(path.join(dir, name));
@@ -227,11 +227,18 @@ function countWaveLines(jsonlPath, waveId) {
227
227
  }
228
228
 
229
229
  /**
230
- * Read the per-wave summary JSON, or return a zeroed summary if absent.
230
+ * Read + parse a per-wave summary JSON file. Returns the parsed summary on
231
+ * success, or `null` on any failure — missing file (ENOENT) silently, any
232
+ * other read/parse failure with a stderr WARN (#1210 — ENOENT and other
233
+ * failures are different facts, same split as `sessions-canonical.mjs`
234
+ * `readCanonicalSessions`). Shared by `readSummary` (defaults to zeros) and
235
+ * `readWaveSummary` (surfaces `null` to its own caller) — one read path
236
+ * instead of two near-identical copies.
237
+ *
231
238
  * @param {string} summaryPath
232
- * @returns {{ queued: number, dropped: number, below_floor: number, fs_error: number }}
239
+ * @returns {{ queued: number, dropped: number, below_floor: number, fs_error: number } | null}
233
240
  */
234
- function readSummary(summaryPath) {
241
+ function readSummaryFileSafe(summaryPath) {
235
242
  try {
236
243
  const raw = fs.readFileSync(summaryPath, 'utf8');
237
244
  const parsed = JSON.parse(raw);
@@ -243,10 +250,26 @@ function readSummary(summaryPath) {
243
250
  fs_error: parsed.fs_error ?? 0,
244
251
  };
245
252
  }
246
- } catch {
247
- /* ENOENT or malformed — return zeros */
253
+ return null;
254
+ } catch (err) {
255
+ if (!err || err.code !== 'ENOENT') {
256
+ process.stderr.write(
257
+ `⚠ readSummaryFileSafe: cannot read ${summaryPath} ` +
258
+ `(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
259
+ 'treating as EMPTY, counts below are floors\n',
260
+ );
261
+ }
262
+ return null;
248
263
  }
249
- return { queued: 0, dropped: 0, below_floor: 0, fs_error: 0 };
264
+ }
265
+
266
+ /**
267
+ * Read the per-wave summary JSON, or return a zeroed summary if absent.
268
+ * @param {string} summaryPath
269
+ * @returns {{ queued: number, dropped: number, below_floor: number, fs_error: number }}
270
+ */
271
+ function readSummary(summaryPath) {
272
+ return readSummaryFileSafe(summaryPath) ?? { queued: 0, dropped: 0, below_floor: 0, fs_error: 0 };
250
273
  }
251
274
 
252
275
  /**
@@ -422,20 +445,5 @@ export async function countProposalsForWave({ repoRoot, waveId }) {
422
445
  */
423
446
  export async function readWaveSummary({ repoRoot, waveId }) {
424
447
  const summaryPath = summaryPathFor(repoRoot, waveId);
425
- try {
426
- const raw = fs.readFileSync(summaryPath, 'utf8');
427
- const parsed = JSON.parse(raw);
428
- if (parsed && typeof parsed === 'object') {
429
- return {
430
- queued: parsed.queued ?? 0,
431
- dropped: parsed.dropped ?? 0,
432
- below_floor: parsed.below_floor ?? 0,
433
- fs_error: parsed.fs_error ?? 0,
434
- };
435
- }
436
- return null;
437
- } catch (err) {
438
- if (err.code === 'ENOENT') return null;
439
- return null;
440
- }
448
+ return readSummaryFileSafe(summaryPath);
441
449
  }
@@ -36,7 +36,14 @@
36
36
  * - Issue #820.
37
37
  */
38
38
 
39
- import { loadOwnerConfig, OWNER_YAML_PATH } from './owner-yaml.mjs';
39
+ import { loadOwnerConfig, resolveOwnerYamlPath, getDefaults } from './owner-yaml.mjs';
40
+
41
+ /**
42
+ * OPTIONAL object sections (mirrors `owner-yaml.mjs`'s internal, unexported
43
+ * `OPTIONAL_OBJECT_SECTIONS`) — the only sections the #1244 discard-branch
44
+ * merge rule can carry through a whole-file discard.
45
+ */
46
+ const OPTIONAL_OBJECT_SECTION_NAMES = /** @type {const} */ (['paths', 'dispatcher']);
40
47
 
41
48
  /**
42
49
  * Format a `{ section, errors }` entry into a short `"name" (first error)` tag.
@@ -52,6 +59,7 @@ function formatSectionTag(entry) {
52
59
  * Check owner.yaml load health and produce a session-start banner.
53
60
  *
54
61
  * @param {{ loader?: (opts?: object) => {
62
+ * config?: object,
55
63
  * source: 'file'|'defaults'|'partial',
56
64
  * errors: string[],
57
65
  * droppedSections?: Array<{ section: string, errors: string[] }>,
@@ -86,15 +94,44 @@ export function checkOwnerConfig({ loader = loadOwnerConfig } = {}) {
86
94
  }
87
95
 
88
96
  // Whole-file discard: a REQUIRED section was invalid (legacy behaviour).
97
+ //
98
+ // #1244 MERGE RULE (owner-yaml.mjs loadOwnerConfig): a VALID optional
99
+ // object section (`paths`, `dispatcher`) survives the discard — merged
100
+ // onto its default in `result.config`. loadOwnerConfig's return carries no
101
+ // dedicated "survived" field, so it is derived here: a section differs
102
+ // from `getDefaults()` only when real, validly-parsed values were merged
103
+ // in. Without this, the banner claimed "the entire file was discarded"
104
+ // while a host-local `paths.confidential-names-file` the operator DID
105
+ // configure correctly was still live — the exact silent-fix-that-lies gap
106
+ // Q3 measured with a tmp owner.yaml (invalid `owner.name`, valid
107
+ // `paths.confidential-names-file`).
89
108
  if (result.source === 'defaults' && errors.length > 0) {
90
109
  const firstError = errors[0];
110
+ const path = resolveOwnerYamlPath();
111
+ const survivedSections = [];
112
+ if (result.config && typeof result.config === 'object') {
113
+ const defaults = getDefaults();
114
+ for (const name of OPTIONAL_OBJECT_SECTION_NAMES) {
115
+ if (JSON.stringify(result.config[name]) !== JSON.stringify(defaults[name])) {
116
+ survivedSections.push(name);
117
+ }
118
+ }
119
+ }
120
+
121
+ const message =
122
+ survivedSections.length > 0
123
+ ? `⚠ owner-config: ${path} has required section(s) invalid (${firstError}) — ` +
124
+ `${survivedSections.map((s) => `"${s}"`).join('/')} kept, everything else defaulted. ` +
125
+ 'Fix the file (see .claude/rules/owner-persona.md) to restore your settings.'
126
+ : `⚠ owner-config: ${path} is invalid (${firstError}) — the entire file was ` +
127
+ 'discarded and defaults are in effect. Fix the file (see .claude/rules/owner-persona.md) ' +
128
+ 'to restore your settings.';
129
+
91
130
  return {
92
131
  severity: 'warn',
93
- message:
94
- `⚠ owner-config: ${OWNER_YAML_PATH} is invalid (${firstError}) — the entire file was ` +
95
- 'discarded and defaults are in effect. Fix the file (see .claude/rules/owner-persona.md) ' +
96
- 'to restore your settings.',
132
+ message,
97
133
  discarded: true,
134
+ ...(droppedSections.length > 0 ? { droppedSections } : {}),
98
135
  };
99
136
  }
100
137
 
@@ -115,7 +152,7 @@ export function checkOwnerConfig({ loader = loadOwnerConfig } = {}) {
115
152
 
116
153
  return {
117
154
  severity: 'warn',
118
- message: `⚠ owner-config: ${OWNER_YAML_PATH} — ${parts.join('; ')} — fix the file to restore full config.`,
155
+ message: `⚠ owner-config: ${resolveOwnerYamlPath()} — ${parts.join('; ')} — fix the file to restore full config.`,
119
156
  ...(droppedSections.length > 0 ? { droppedSections } : {}),
120
157
  ...(sectionWarnings.length > 0 ? { sectionWarnings } : {}),
121
158
  };
@@ -8,7 +8,10 @@
8
8
  *
9
9
  * ── PATH RESOLUTION ──────────────────────────────────────────────────────
10
10
  *
11
- * Default path: `${XDG_CONFIG_HOME ?? ${HOME}/.config}/session-orchestrator/owner.yaml`
11
+ * Default path (#1223 delegated to the single resolver
12
+ * `config/private-config-dir.mjs`; `SO_CONFIG_HOME` names the private dir
13
+ * ITSELF and wins):
14
+ * `${SO_CONFIG_HOME ?? ${XDG_CONFIG_HOME ?? ${HOME}/.config}/session-orchestrator}/owner.yaml`
12
15
  * - macOS / Linux: `~/.config/session-orchestrator/owner.yaml`
13
16
  * - Windows: `%APPDATA%\session-orchestrator\owner.yaml` when
14
17
  * APPDATA is set and XDG_CONFIG_HOME is not (Node uses
@@ -43,29 +46,37 @@
43
46
  import { readFile } from 'node:fs/promises';
44
47
  import { existsSync } from 'node:fs';
45
48
  import { join } from 'node:path';
46
- import { homedir } from 'node:os';
47
49
  import yaml from 'js-yaml';
48
50
 
49
51
  import { validate } from './owner-config.mjs';
52
+ import { resolvePrivateConfigDir } from './config/private-config-dir.mjs';
50
53
 
51
54
  // ---------------------------------------------------------------------------
52
55
  // Path resolution
53
56
  // ---------------------------------------------------------------------------
54
57
 
55
- const APP_DIR = 'session-orchestrator';
56
- const CONFIG_FILE = 'owner.yaml';
57
-
58
58
  /**
59
- * Resolve the canonical owner.yaml path on disk. Honours XDG_CONFIG_HOME
60
- * when set (Linux convention), otherwise falls back to `${HOME}/.config`.
59
+ * Resolve the canonical owner.yaml path on disk: the ONE private-config-dir
60
+ * resolver (#1223) plus the file's basename — precedence `SO_CONFIG_HOME` (the
61
+ * private dir itself) > `XDG_CONFIG_HOME` (its parent) > `${HOME}/.config`, each
62
+ * `.trim()`ed so a whitespace-only value falls through instead of
63
+ * short-circuiting (`.claude/rules/development.md` § Error Handling).
64
+ *
65
+ * Reaches the leaf DIRECTLY rather than through `owner-yaml.mjs`'s
66
+ * `resolveOwnerYamlPath()`: that hop exists only to append the same basename and
67
+ * drags a `js-yaml`-importing module in with it. The precedence — the part #1223
68
+ * consolidated — still lives in exactly one place; only the literal `'owner.yaml'`
69
+ * is stated twice.
70
+ *
71
+ * Before #1223 this function knew only `XDG_CONFIG_HOME` and did not trim, so
72
+ * `SO_CONFIG_HOME=<sandbox>` still read the operator's real home.
73
+ *
61
74
  * Returns an absolute path string. Does NOT touch the filesystem.
62
75
  *
63
76
  * @returns {string} absolute path to owner.yaml
64
77
  */
65
78
  export function resolveOwnerConfigPath() {
66
- const xdg = process.env.XDG_CONFIG_HOME;
67
- const base = xdg && xdg.length > 0 ? xdg : join(homedir(), '.config');
68
- return join(base, APP_DIR, CONFIG_FILE);
79
+ return join(resolvePrivateConfigDir(), 'owner.yaml');
69
80
  }
70
81
 
71
82
  // ---------------------------------------------------------------------------
@@ -37,7 +37,7 @@
37
37
  import { randomBytes } from 'node:crypto';
38
38
  import { existsSync, copyFileSync } from 'node:fs';
39
39
  import {
40
- OWNER_YAML_PATH,
40
+ resolveOwnerYamlPath,
41
41
  validateOwnerConfig,
42
42
  writeOwnerConfig,
43
43
  getDefaults,
@@ -161,7 +161,7 @@ export function getInterviewQuestions() {
161
161
  * @returns {{ ok: boolean, path: string, errors: string[] }}
162
162
  */
163
163
  export function applyInterviewAnswers(answers, opts = {}) {
164
- const filePath = opts.path ?? OWNER_YAML_PATH;
164
+ const filePath = opts.path ?? resolveOwnerYamlPath();
165
165
 
166
166
  if (!Array.isArray(answers) || answers.length !== 5) {
167
167
  return { ok: false, path: filePath, errors: ['applyInterviewAnswers requires exactly 5 answers'] };
@@ -258,7 +258,7 @@ export function runOwnerInterview(opts = {}) {
258
258
  const {
259
259
  skipIfExists = true,
260
260
  force = false,
261
- path: filePath = OWNER_YAML_PATH,
261
+ path: filePath = resolveOwnerYamlPath(),
262
262
  } = opts;
263
263
 
264
264
  const fileExists = existsSync(filePath);