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.
- package/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1125 -2
- package/NOTICE +11 -6
- package/README.md +127 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +65 -21
- package/docs/components.md +36 -15
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +62 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +341 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +36 -11
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/plugin.json +27 -0
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +249 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +26 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +43 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +207 -14
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +406 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +13 -4
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +36 -2
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +18 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +181 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
- package/scripts/lib/validate/check-skill-links.mjs +163 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-unwired-features.mjs +0 -2
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +161 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /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
|
-
*
|
|
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
|
|
195
|
-
*
|
|
196
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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: ${
|
|
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
|
|
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
|
|
60
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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 =
|
|
261
|
+
path: filePath = resolveOwnerYamlPath(),
|
|
262
262
|
} = opts;
|
|
263
263
|
|
|
264
264
|
const fileExists = existsSync(filePath);
|