session-orchestrator 3.24.0 → 4.0.1
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 +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- 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/.cursor-plugin/plugin.json +30 -0
- 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 +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -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 +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -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 +1488 -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 +60 -14
- 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/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-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -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 +88 -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/host-paths.mjs +20 -4
- 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 +30 -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 +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -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 +51 -11
- 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 +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -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 +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- 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 +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- 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 +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- 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 +164 -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 +160 -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/SKILL.md +10 -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
|
@@ -213,6 +213,37 @@ function learningContentMatches(existingContent, renderedContent) {
|
|
|
213
213
|
);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Would the process-wide masker (see `ensureMasker` below) still change `text`
|
|
218
|
+
* if it ran again right now? (#1028 residue 1)
|
|
219
|
+
*
|
|
220
|
+
* Used as a LAST guard before returning `skipped-noop` in BOTH `processLearning`
|
|
221
|
+
* and `processSession` — the session channel's two skip sites were date-only
|
|
222
|
+
* until the W4 review of #1028 (see the module note above).
|
|
223
|
+
* `learningContentMatches` above compares only five canonical fields, so it
|
|
224
|
+
* cannot see a raw secret sitting in a NON-canonical field (`evidence` is the
|
|
225
|
+
* realistic one — see the KNOWN-RESIDUAL note on `maskEntrySecrets` below): a
|
|
226
|
+
* candidate that matches on those five fields can still be an on-disk leak.
|
|
227
|
+
* When this returns `true`, the caller must NOT skip — it falls through to the
|
|
228
|
+
* write path so the note is re-rendered (and re-masked) from the current entry.
|
|
229
|
+
*
|
|
230
|
+
* NAMED CEILING: this only heals a needle that is still present in the CURRENT
|
|
231
|
+
* process env. A needle that has since been rotated out of the env no longer
|
|
232
|
+
* masks to anything different, so `mask(text) === text` and this returns
|
|
233
|
+
* `false` — that needle's leak is healed only by an explicit `force: true`
|
|
234
|
+
* re-mirror (which bypasses the comparison entirely), never by a plain re-run.
|
|
235
|
+
*
|
|
236
|
+
* COST: one extra `mask()` call per candidate no-op — cheap relative to the
|
|
237
|
+
* read/parse/render already done to reach this check, and only paid on the
|
|
238
|
+
* no-op path (a real content change already writes without reaching here).
|
|
239
|
+
*
|
|
240
|
+
* @param {string} text — on-disk note content to probe
|
|
241
|
+
* @returns {boolean} true when masking `text` again would produce different output
|
|
242
|
+
*/
|
|
243
|
+
function maskerWouldChange(text) {
|
|
244
|
+
return ensureMasker().mask(text) !== text;
|
|
245
|
+
}
|
|
246
|
+
|
|
216
247
|
// ── repo derivation ───────────────────────────────────────────────────────────
|
|
217
248
|
|
|
218
249
|
/**
|
|
@@ -451,36 +482,54 @@ export function getMaskerStats() {
|
|
|
451
482
|
* as a wildcard (see that function) so this direction resolves to
|
|
452
483
|
* `skipped-noop` again.
|
|
453
484
|
*
|
|
454
|
-
*
|
|
455
|
-
* That revision claimed a COLD-START
|
|
456
|
-
* run without the env writes the raw
|
|
457
|
-
* and the note freezes. Measured, that
|
|
458
|
-
* on-disk side `matchesModuloRedaction`
|
|
459
|
-
* canonical fields differ, and the run
|
|
460
|
-
* asymmetry is still deliberate (an on-disk
|
|
461
|
-
* an on-disk raw value is evidence of
|
|
462
|
-
* anything the field comparison can see.
|
|
463
|
-
*
|
|
464
|
-
* What
|
|
465
|
-
*
|
|
466
|
-
* `
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
485
|
+
* RESIDUAL (#1028), NOW HEALED BY A MASK RE-PROBE — and NOT the residual an
|
|
486
|
+
* earlier revision of this note named. That revision claimed a COLD-START
|
|
487
|
+
* FREEZE over the CANONICAL fields: first run without the env writes the raw
|
|
488
|
+
* value, later runs render `[REDACTED]`, and the note freezes. Measured, that
|
|
489
|
+
* direction HEALS: with no marker on the on-disk side `matchesModuloRedaction`
|
|
490
|
+
* returns false at its first line, the canonical fields differ, and the run
|
|
491
|
+
* writes the masked content. The asymmetry is still deliberate (an on-disk
|
|
492
|
+
* redaction is evidence a mask ran; an on-disk raw value is evidence of
|
|
493
|
+
* nothing) — it simply does not freeze anything the field comparison can see.
|
|
494
|
+
*
|
|
495
|
+
* What the field comparison ALONE cannot see: `learningContentMatches`
|
|
496
|
+
* compares exactly five canonical fields — `status`, `expires`, `confidence`,
|
|
497
|
+
* `insight`, `source_session`. A raw secret sitting in any OTHER field
|
|
498
|
+
* (`evidence` is the realistic one; it is agent-authored free text and it is
|
|
499
|
+
* rendered into the note) leaves all five identical between the raw on-disk
|
|
500
|
+
* note and the masked candidate — the comparison alone reports a match.
|
|
501
|
+
*
|
|
502
|
+
* THE FIX — IN BOTH GENERATORS, which is the half an earlier revision of
|
|
503
|
+
* this note got wrong: it described the guard as a `processLearning` matter,
|
|
504
|
+
* and `processSession` shipped with two purely DATE-based `skipped-noop`
|
|
505
|
+
* returns beside it. The session note's narrative body is the LARGER
|
|
506
|
+
* free-text surface of the two, so that omission was the bigger half of the
|
|
507
|
+
* leak. Every `skipped-noop` return in BOTH `processLearning` (legacy-flat,
|
|
508
|
+
* disambig-collision, same-id) and `processSession` (legacy-flat, same-id)
|
|
509
|
+
* is now gated by `maskerWouldChange` (see that function above). When the
|
|
510
|
+
* on-disk content would still be changed by the CURRENT masker, the run
|
|
511
|
+
* falls through to the write path and re-renders (masked) instead of
|
|
512
|
+
* skipping — so a plain re-mirror heals the leak on its own; `force: true`
|
|
513
|
+
* is no longer the only escape hatch. The disambig-collision branch's
|
|
514
|
+
* `!force` guard now also matches the same-id and legacy-flat branches (it
|
|
515
|
+
* previously lacked one, so `force: true` was silently ignored there).
|
|
516
|
+
*
|
|
517
|
+
* THE LEGACY-FLAT BRANCH NEEDS ONE MORE STEP (#1028 residue 2). Falling
|
|
518
|
+
* through there writes the NAMESPACED path, and from the next run on
|
|
519
|
+
* `existsSync(targetPath)` is true — the flat file is never read again. A
|
|
520
|
+
* guard alone would therefore leave the plaintext on disk and merely make it
|
|
521
|
+
* unreachable to the heal, with the ledger reporting a clean write. Both
|
|
522
|
+
* generators now re-render the legacy flat note IN PLACE (masked) before
|
|
523
|
+
* falling through, and mark the resulting action `healed_legacy_flat: true`.
|
|
524
|
+
* Rewriting rather than deleting is what the deferred-migration decision
|
|
525
|
+
* above asks for: that decision is about not MOVING the note, which a masked
|
|
526
|
+
* re-render preserves and a deletion would silently undo.
|
|
527
|
+
* Revisit-Trigger: `maskerWouldChange` only detects needles present in the
|
|
528
|
+
* CURRENT process env — a needle since rotated out of the env is not seen as
|
|
529
|
+
* still-leaking and is healed only by an explicit `force: true` re-mirror.
|
|
530
|
+
* Widen this (e.g. persist a needle-shape fingerprint, or diff the whole
|
|
531
|
+
* rendered body) the first time a rotated-out secret is observed staying on
|
|
532
|
+
* disk after a plain re-run.
|
|
484
533
|
*
|
|
485
534
|
* FRONTMATTER AND BODY ARE TREATED IDENTICALLY. A credential is exactly as
|
|
486
535
|
* published in `title:` as it is under `## Insight` — both live in the same
|
|
@@ -636,22 +685,48 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
|
636
685
|
// duplicating the note. The deferred-migration decision means we only skip;
|
|
637
686
|
// we do NOT move the flat note into the namespaced dir here.
|
|
638
687
|
const legacyFlatPath = join(resolve(vaultDir), '40-learnings', `${slug}.md`);
|
|
688
|
+
// Set when a still-leaking legacy flat note was healed in place below, so the
|
|
689
|
+
// action emitted for THIS entry can say so (see the write sites at the end).
|
|
690
|
+
let healedLegacyFlat = false;
|
|
639
691
|
if (!existsSync(targetPath) && existsSync(legacyFlatPath)) {
|
|
640
692
|
const legacyContent = readFileSync(legacyFlatPath, 'utf8');
|
|
641
693
|
const legacyFm = parseFrontmatter(legacyContent);
|
|
642
694
|
// Only skip if the flat note is ours (has our generator marker and matching id).
|
|
643
695
|
if (legacyFm && legacyFm['_generator'] === GENERATOR_MARKER && legacyFm['id'] === slug) {
|
|
644
696
|
const entryUpdated = toDate(dateSource);
|
|
697
|
+
// #1028 residue 2 (W4 review HIGH-2): the leak probe must run on EVERY
|
|
698
|
+
// path that leaves this branch, not only the date-not-advanced one — every
|
|
699
|
+
// one of them falls through to the NAMESPACED path, after which
|
|
700
|
+
// `existsSync(targetPath)` is true forever and this legacy file is never
|
|
701
|
+
// read again. A raw secret left in it would become unreachable to the
|
|
702
|
+
// self-heal while staying fully published in the tracked vault.
|
|
703
|
+
const legacyStillLeaks = maskerWouldChange(legacyContent);
|
|
645
704
|
if (!force && legacyFm['updated'] && legacyFm['updated'] >= entryUpdated) {
|
|
646
705
|
// Date has not advanced — but content may have changed (confidence, insight, etc.).
|
|
647
706
|
// Render the candidate and compare canonical fields before deciding to skip.
|
|
648
707
|
const candidateContent = generator(entry, slug, generatorOpts);
|
|
649
|
-
if (learningContentMatches(legacyContent, candidateContent)) {
|
|
708
|
+
if (learningContentMatches(legacyContent, candidateContent) && !legacyStillLeaks) {
|
|
709
|
+
// #1028 residue 1: the five-field compare cannot see a raw secret
|
|
710
|
+
// sitting in a non-canonical field (e.g. `evidence`) — the
|
|
711
|
+
// `legacyStillLeaks` probe above is what makes this match trustworthy.
|
|
650
712
|
return emitEntryAction(_lineNum, ctx, { action: 'skipped-noop', path: legacyFlatPath, id: slug });
|
|
651
713
|
}
|
|
652
|
-
// Content differs
|
|
714
|
+
// Content differs, or the on-disk note still leaks under the current
|
|
715
|
+
// env — fall through to write into the namespaced path.
|
|
653
716
|
}
|
|
654
717
|
// Updated date would advance or content changed — fall through to write into the namespaced path.
|
|
718
|
+
//
|
|
719
|
+
// Heal the leaking legacy note IN PLACE first. Rewriting is the option
|
|
720
|
+
// consistent with the deferred-migration rationale above: that decision is
|
|
721
|
+
// about not MOVING the note (the flat path stays authoritative for older
|
|
722
|
+
// vaults and for hand-made links into it), which a masked re-render
|
|
723
|
+
// preserves and a deletion would silently undo. Deleting an operator-visible
|
|
724
|
+
// vault file to fix a leak also trades one irreversible loss for another —
|
|
725
|
+
// the content here is already ours (generator marker + id checked above).
|
|
726
|
+
if (legacyStillLeaks) {
|
|
727
|
+
if (!dryRun) writeFileSync(legacyFlatPath, generator(entry, slug, generatorOpts), 'utf8');
|
|
728
|
+
healedLegacyFlat = true;
|
|
729
|
+
}
|
|
655
730
|
}
|
|
656
731
|
}
|
|
657
732
|
|
|
@@ -686,13 +761,22 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
|
686
761
|
return emitEntryAction(_lineNum, ctx, { action: 'skipped-handwritten', path: targetPath, id: entryId });
|
|
687
762
|
}
|
|
688
763
|
// Check updated advancement; if date has not advanced, also diff content.
|
|
764
|
+
// #1028 residue 1: this guard previously lacked the `!force &&` that the
|
|
765
|
+
// legacy-flat and same-id branches carry, so `force: true` was silently
|
|
766
|
+
// ignored on this one path — byte-identical guard shape to those two now.
|
|
689
767
|
const entryUpdated = toDate(dateSource);
|
|
690
|
-
if (disambigFm['updated'] && disambigFm['updated'] >= entryUpdated) {
|
|
768
|
+
if (!force && disambigFm['updated'] && disambigFm['updated'] >= entryUpdated) {
|
|
691
769
|
const candidateContent = generator(entry, slug, generatorOpts);
|
|
692
770
|
if (learningContentMatches(disambigContent, candidateContent)) {
|
|
693
|
-
|
|
771
|
+
// #1028 residue 1: probe the on-disk content against the CURRENT
|
|
772
|
+
// masker before trusting the five-field match — see maskerWouldChange.
|
|
773
|
+
const stillLeaks = maskerWouldChange(disambigContent);
|
|
774
|
+
if (!stillLeaks) {
|
|
775
|
+
return emitEntryAction(_lineNum, ctx, { action: 'skipped-noop', path: targetPath, id: disambigSlug });
|
|
776
|
+
}
|
|
694
777
|
}
|
|
695
|
-
// Content differs
|
|
778
|
+
// Content differs, or the on-disk note still leaks under the current
|
|
779
|
+
// env — fall through to write.
|
|
696
780
|
}
|
|
697
781
|
}
|
|
698
782
|
|
|
@@ -708,9 +792,15 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
|
708
792
|
if (!force && fm['updated'] && fm['updated'] >= entryUpdated) {
|
|
709
793
|
const candidateContent = generator(entry, slug, generatorOpts);
|
|
710
794
|
if (learningContentMatches(existingContent, candidateContent)) {
|
|
711
|
-
|
|
795
|
+
// #1028 residue 1: probe the on-disk content against the CURRENT masker
|
|
796
|
+
// before trusting the five-field match — see maskerWouldChange above.
|
|
797
|
+
const stillLeaks = maskerWouldChange(existingContent);
|
|
798
|
+
if (!stillLeaks) {
|
|
799
|
+
return emitEntryAction(_lineNum, ctx, { action: 'skipped-noop', path: targetPath, id: slug });
|
|
800
|
+
}
|
|
712
801
|
}
|
|
713
|
-
// Content differs
|
|
802
|
+
// Content differs, or the on-disk note still leaks under the current env —
|
|
803
|
+
// fall through to overwrite (same path as date-advance branch).
|
|
714
804
|
}
|
|
715
805
|
|
|
716
806
|
// Overwrite with advanced updated date (or forced re-render)
|
|
@@ -722,7 +812,15 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
|
722
812
|
// File does not exist — create
|
|
723
813
|
const content = generator(entry, slug, generatorOpts);
|
|
724
814
|
if (!dryRun) writeFileSync(targetPath, content, 'utf8');
|
|
725
|
-
return emitEntryAction(_lineNum, ctx, {
|
|
815
|
+
return emitEntryAction(_lineNum, ctx, {
|
|
816
|
+
action: 'created',
|
|
817
|
+
path: targetPath,
|
|
818
|
+
id: slug,
|
|
819
|
+
// No PATH in the meta: the stdout payload's `path` is vault-relative on
|
|
820
|
+
// purpose (emitAction relativises it), a raw absolute path here would put
|
|
821
|
+
// the operator's home dir on stdout and into the ledger record.
|
|
822
|
+
...(healedLegacyFlat ? { meta: { healed_legacy_flat: true } } : {}),
|
|
823
|
+
});
|
|
726
824
|
}
|
|
727
825
|
|
|
728
826
|
export async function processSession(rawEntry, _lineNum, ctx) {
|
|
@@ -841,15 +939,30 @@ export async function processSession(rawEntry, _lineNum, ctx) {
|
|
|
841
939
|
// the namespaced path as absent. If a session note already exists flat
|
|
842
940
|
// (pre-namespace migration), skip creating a duplicate.
|
|
843
941
|
const legacyFlatPath = join(resolve(vaultDir), '50-sessions', `${session_id}.md`);
|
|
942
|
+
let healedLegacyFlat = false;
|
|
844
943
|
if (!existsSync(targetPath) && existsSync(legacyFlatPath)) {
|
|
845
944
|
const legacyContent = readFileSync(legacyFlatPath, 'utf8');
|
|
846
945
|
const legacyFm = parseFrontmatter(legacyContent);
|
|
847
946
|
if (legacyFm && legacyFm['_generator'] === GENERATOR_MARKER && legacyFm['id'] === session_id) {
|
|
848
947
|
const entryUpdated = toDate(entry.completed_at);
|
|
849
|
-
|
|
948
|
+
// #1028 residue 1, session channel (W4 review HIGH-1): the skip decision
|
|
949
|
+
// here is DATE-ONLY — it never looks at the note's content, so a raw
|
|
950
|
+
// secret written into the narrative before the env carried the needle
|
|
951
|
+
// would stay published forever. Same `maskerWouldChange` probe the
|
|
952
|
+
// learning channel carries; see that function above.
|
|
953
|
+
const legacyStillLeaks = maskerWouldChange(legacyContent);
|
|
954
|
+
if (!force && !legacyStillLeaks && legacyFm['updated'] && legacyFm['updated'] >= entryUpdated) {
|
|
850
955
|
return emitEntryAction(_lineNum, ctx, { action: 'skipped-noop', path: legacyFlatPath, id: session_id });
|
|
851
956
|
}
|
|
852
|
-
// Updated date would advance
|
|
957
|
+
// Updated date would advance, or the on-disk note still leaks — fall
|
|
958
|
+
// through to write into the namespaced path. Heal the legacy note in place
|
|
959
|
+
// first (HIGH-2, same rationale as processLearning's legacy-flat branch):
|
|
960
|
+
// after this run `existsSync(targetPath)` is true, so the flat file is
|
|
961
|
+
// never read again and its plaintext would be unreachable to the heal.
|
|
962
|
+
if (legacyStillLeaks) {
|
|
963
|
+
if (!dryRun) writeFileSync(legacyFlatPath, renderedBody, 'utf8');
|
|
964
|
+
healedLegacyFlat = true;
|
|
965
|
+
}
|
|
853
966
|
}
|
|
854
967
|
}
|
|
855
968
|
|
|
@@ -871,7 +984,11 @@ export async function processSession(rawEntry, _lineNum, ctx) {
|
|
|
871
984
|
// Same generator: check id and updated
|
|
872
985
|
if (fm['id'] === session_id) {
|
|
873
986
|
const entryUpdated = toDate(entry.completed_at);
|
|
874
|
-
|
|
987
|
+
// #1028 residue 1, session channel (W4 review HIGH-1): probe the on-disk
|
|
988
|
+
// content against the CURRENT masker before trusting the date-only skip —
|
|
989
|
+
// the session note's narrative body is the larger free-text leak surface
|
|
990
|
+
// of the two channels. See maskerWouldChange above.
|
|
991
|
+
if (!force && !maskerWouldChange(existingContent) && fm['updated'] && fm['updated'] >= entryUpdated) {
|
|
875
992
|
return emitEntryAction(_lineNum, ctx, { action: 'skipped-noop', path: targetPath, id: session_id });
|
|
876
993
|
}
|
|
877
994
|
if (!dryRun) writeFileSync(targetPath, renderedBody, 'utf8');
|
|
@@ -882,5 +999,11 @@ export async function processSession(rawEntry, _lineNum, ctx) {
|
|
|
882
999
|
// File does not exist — create. Reuse the rendered body computed during the
|
|
883
1000
|
// quality-gate check (avoids a second generator invocation).
|
|
884
1001
|
if (!dryRun) writeFileSync(targetPath, renderedBody, 'utf8');
|
|
885
|
-
return emitEntryAction(_lineNum, ctx, {
|
|
1002
|
+
return emitEntryAction(_lineNum, ctx, {
|
|
1003
|
+
action: 'created',
|
|
1004
|
+
path: targetPath,
|
|
1005
|
+
id: session_id,
|
|
1006
|
+
// Boolean only — never a path; see the same note in processLearning.
|
|
1007
|
+
...(healedLegacyFlat ? { meta: { healed_legacy_flat: true } } : {}),
|
|
1008
|
+
});
|
|
886
1009
|
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
import { emitEvent, sessionAttribution } from '../events.mjs';
|
|
41
|
-
import {
|
|
41
|
+
import { getProjectDir } from '../platform.mjs';
|
|
42
42
|
|
|
43
43
|
/** Canonical event name for a single vault-mirror JSONL entry. */
|
|
44
44
|
export const MIRROR_EVENT = 'orchestrator.vault.mirror_completed';
|
|
@@ -81,7 +81,7 @@ const present = (v) => v !== undefined && v !== null;
|
|
|
81
81
|
* @returns {{session_id?: string, semantic_session_id?: string}}
|
|
82
82
|
*/
|
|
83
83
|
function mirrorSessionAttribution() {
|
|
84
|
-
return sessionAttribution(
|
|
84
|
+
return sessionAttribution(getProjectDir());
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -218,15 +218,27 @@ export function extractNarrative(stateMdContents) {
|
|
|
218
218
|
* REFERENCE (byte-identical downstream), and `mask` passes non-strings through —
|
|
219
219
|
* masking must never be the reason a mirror run dies.
|
|
220
220
|
*
|
|
221
|
+
* Takes the ALREADY-BUILT masker (rather than building its own) so
|
|
222
|
+
* `runNarrativeMirror` can build it exactly ONCE per run and reuse it for both
|
|
223
|
+
* the actual masking below AND the `orchestrator.secret_masker.applied`
|
|
224
|
+
* telemetry (#1028) — the masker's `needleCount` is measured once, not
|
|
225
|
+
* potentially twice with process.env read between the two reads.
|
|
226
|
+
*
|
|
221
227
|
* @param {{ waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null }} narrative
|
|
222
|
-
* @
|
|
228
|
+
* @param {{ mask: (text: string) => string, needleCount: number }} masker
|
|
229
|
+
* @returns {{ narrative: { waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null }, hits: number }}
|
|
223
230
|
*/
|
|
224
|
-
function maskNarrative(narrative) {
|
|
225
|
-
const { mask, needleCount } =
|
|
226
|
-
if (needleCount === 0) return narrative;
|
|
231
|
+
function maskNarrative(narrative, masker) {
|
|
232
|
+
const { mask, needleCount } = masker;
|
|
233
|
+
if (needleCount === 0) return { narrative, hits: 0 };
|
|
227
234
|
|
|
235
|
+
let hits = 0;
|
|
228
236
|
const walk = (value) => {
|
|
229
|
-
if (typeof value === 'string')
|
|
237
|
+
if (typeof value === 'string') {
|
|
238
|
+
const masked = mask(value);
|
|
239
|
+
if (masked !== value) hits++;
|
|
240
|
+
return masked;
|
|
241
|
+
}
|
|
230
242
|
if (Array.isArray(value)) return value.map(walk);
|
|
231
243
|
if (value && typeof value === 'object') {
|
|
232
244
|
const out = {};
|
|
@@ -236,7 +248,7 @@ function maskNarrative(narrative) {
|
|
|
236
248
|
return value;
|
|
237
249
|
};
|
|
238
250
|
|
|
239
|
-
return walk(narrative);
|
|
251
|
+
return { narrative: walk(narrative), hits };
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
// ── Pure render ────────────────────────────────────────────────────────────────
|
|
@@ -404,6 +416,30 @@ function normalizeUpdated(content) {
|
|
|
404
416
|
* between runs: run 1 (env set) → `skipped-noop`, run 2 (env absent) → `written`
|
|
405
417
|
* with the raw value on disk.
|
|
406
418
|
*
|
|
419
|
+
* WHY THERE IS NO `maskerWouldChange` RE-PROBE HERE (#1214). The two sibling
|
|
420
|
+
* sinks in `vault-mirror/process.mjs` (processLearning, processSession) guard
|
|
421
|
+
* all five of their `skipped-noop` returns with `maskerWouldChange(existing)`,
|
|
422
|
+
* because their five-field / date comparisons can report a match while the
|
|
423
|
+
* on-disk note still carries a raw needle. This sink compares the WHOLE
|
|
424
|
+
* document, which makes that residue unreachable: `matchesModuloRedaction`
|
|
425
|
+
* only fires when the EXISTING side carries a `[REDACTED]` marker, and every
|
|
426
|
+
* literal segment around such a marker must appear verbatim in the candidate —
|
|
427
|
+
* but the candidate comes through `maskNarrative` with the CURRENT masker, so
|
|
428
|
+
* it can never contain a value that masker would redact. A raw needle on disk
|
|
429
|
+
* therefore always lands in a literal segment that fails to match, and the run
|
|
430
|
+
* writes. Measured 2026-09-04 at HEAD cd785003 in both directions (no marker on
|
|
431
|
+
* disk; marker already on disk plus a second needle entering the env): both
|
|
432
|
+
* returned `written` with the raw value gone. The two tests named `(#1214)` in
|
|
433
|
+
* tests/lib/vault-status/narrative-mirror.test.mjs pin that invariant — an
|
|
434
|
+
* argument-order slip on the call below makes both return `skipped-noop` and
|
|
435
|
+
* republish the raw value, which is how they were verified to bite.
|
|
436
|
+
*
|
|
437
|
+
* NAMED CEILING: the invariant rests on `maskNarrative` walking EVERY string
|
|
438
|
+
* that reaches the render. `repo` is fed to `renderNarrative` outside that walk
|
|
439
|
+
* (it is a directory basename, not STATE.md content), so a future rendered
|
|
440
|
+
* field added outside the walk would reopen this. Revisit trigger: any new
|
|
441
|
+
* `renderNarrative` input that does not pass through `maskNarrative`.
|
|
442
|
+
*
|
|
407
443
|
* `matchesModuloRedaction` is IMPORTED from `../vault-mirror/process.mjs`, not
|
|
408
444
|
* re-derived here: `secret-masker.mjs`'s header states this compensation as a
|
|
409
445
|
* contract binding on every consumer that diffs a written artifact against a fresh
|
|
@@ -664,6 +700,56 @@ async function emitNarrativeEvent({ repoRoot, action, path: outputPath, chars, e
|
|
|
664
700
|
}
|
|
665
701
|
}
|
|
666
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Emit this channel's `orchestrator.secret_masker.applied` record (#1028
|
|
705
|
+
* residue 2). Until now the narrative mirror was the ONLY masker channel of
|
|
706
|
+
* the three (`vault-mirror`, `narrative-mirror`, `export-hw-learnings`) that
|
|
707
|
+
* never emitted this event — `maskNarrative` has masked unconditionally since
|
|
708
|
+
* #1025, but nothing told the ledger. A census grepping event NAMES for
|
|
709
|
+
* `narrative` therefore read as "this channel masks nothing", which was never
|
|
710
|
+
* true; it just never SAID so.
|
|
711
|
+
*
|
|
712
|
+
* Emitted UNCONDITIONALLY once per `mirrorNarrative` run whenever a `repoRoot`
|
|
713
|
+
* was resolvable — including every skip outcome, not only `written` — so
|
|
714
|
+
* `needle_count: 0` is a REAL measured "the env carries no secret-shaped
|
|
715
|
+
* value" rather than a stand-in for "the masker never ran" (same
|
|
716
|
+
* force-build-don't-default posture as `getMaskerStats()` in
|
|
717
|
+
* `scripts/lib/vault-mirror/process.mjs`). `records` is the literal `1`: one
|
|
718
|
+
* `mirrorNarrative` run always masks (or attempts to mask) exactly one
|
|
719
|
+
* STATE.md, unlike the CLI channels that fan out over many JSONL lines.
|
|
720
|
+
*
|
|
721
|
+
* Same field set as the `vault-mirror` channel's own masker emit
|
|
722
|
+
* (`channel`, `needle_count`, `records`, `hits`, `dry_run`), PLUS
|
|
723
|
+
* `session_id`/`semantic_session_id` via `sessionAttribution` — matching this
|
|
724
|
+
* module's own `emitNarrativeEvent` above rather than the CLI's 2-arg
|
|
725
|
+
* `emitEvent` call, because `mirrorNarrative` (unlike the CLI) already always
|
|
726
|
+
* carries a `repoRoot` to attribute against.
|
|
727
|
+
*
|
|
728
|
+
* Best-effort: never throws, never alters the mirror result.
|
|
729
|
+
*
|
|
730
|
+
* @param {{ repoRoot?: string, needleCount: number, hits: number, dryRun: boolean }} opts
|
|
731
|
+
* @returns {Promise<void>}
|
|
732
|
+
*/
|
|
733
|
+
async function emitMaskerEvent({ repoRoot, needleCount, hits, dryRun }) {
|
|
734
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) return;
|
|
735
|
+
try {
|
|
736
|
+
await emitEvent(
|
|
737
|
+
'orchestrator.secret_masker.applied',
|
|
738
|
+
{
|
|
739
|
+
channel: 'narrative-mirror',
|
|
740
|
+
needle_count: needleCount,
|
|
741
|
+
records: 1,
|
|
742
|
+
hits,
|
|
743
|
+
dry_run: dryRun,
|
|
744
|
+
...sessionAttribution(repoRoot),
|
|
745
|
+
},
|
|
746
|
+
{ repoRoot },
|
|
747
|
+
);
|
|
748
|
+
} catch {
|
|
749
|
+
/* Best-effort telemetry — see emitNarrativeEvent above for why. */
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
667
753
|
// ── Convenience orchestration ────────────────────────────────────────────────────
|
|
668
754
|
|
|
669
755
|
/**
|
|
@@ -723,42 +809,62 @@ export async function mirrorNarrative(opts) {
|
|
|
723
809
|
chars: outcome.chars,
|
|
724
810
|
});
|
|
725
811
|
|
|
812
|
+
// #1028 residue 2: the masker-telemetry sibling of the narrative event
|
|
813
|
+
// above, on every non-throwing outcome (including skips) — see
|
|
814
|
+
// emitMaskerEvent for why this fires unconditionally.
|
|
815
|
+
await emitMaskerEvent({
|
|
816
|
+
repoRoot: opts?.repoRoot,
|
|
817
|
+
needleCount: outcome.needleCount,
|
|
818
|
+
hits: outcome.hits,
|
|
819
|
+
dryRun: outcome.dryRun,
|
|
820
|
+
});
|
|
821
|
+
|
|
726
822
|
return outcome.result;
|
|
727
823
|
}
|
|
728
824
|
|
|
729
825
|
/**
|
|
730
826
|
* The mirror itself — every early return of {@link mirrorNarrative} lives here.
|
|
731
827
|
*
|
|
732
|
-
* Split out so that
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
828
|
+
* Split out so that the two emit sites in `mirrorNarrative` (the narrative
|
|
829
|
+
* event AND, since #1028, the masker event) each cover EVERY outcome from
|
|
830
|
+
* ONE call: a future early return added inside this function is telemetered
|
|
831
|
+
* by construction, whereas hand-placing an emit beside each of the seven
|
|
832
|
+
* `return`s makes "forgot the new one" the default failure. The `chars` companion travels beside the result
|
|
736
833
|
* rather than inside it because the returned object is a PUBLIC shape that
|
|
737
834
|
* callers (and tests) compare with `toEqual` — adding a key there would be an
|
|
738
835
|
* observable contract change for a purely internal measurement.
|
|
739
836
|
*
|
|
740
837
|
* @param {Parameters<typeof mirrorNarrative>[0]} opts
|
|
741
|
-
* @returns {Promise<{ result: { action: string, path?: string }, chars?: number }>}
|
|
838
|
+
* @returns {Promise<{ result: { action: string, path?: string }, chars?: number, needleCount: number, hits: number, dryRun: boolean }>}
|
|
742
839
|
*/
|
|
743
840
|
async function runNarrativeMirror(opts) {
|
|
744
841
|
const { repoRoot, repo, now = new Date(), dryRun = false, fs: injectedFs, hostPaths } = opts;
|
|
745
842
|
|
|
746
843
|
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
747
|
-
return { result: { action: 'skipped-vault-disabled' } };
|
|
844
|
+
return { result: { action: 'skipped-vault-disabled' }, needleCount: 0, hits: 0, dryRun };
|
|
748
845
|
}
|
|
749
846
|
|
|
847
|
+
// #1028: the masker is built ONCE per run, right here — before every
|
|
848
|
+
// remaining early-return below — so `mirrorNarrative`'s
|
|
849
|
+
// `orchestrator.secret_masker.applied` emit always carries a REAL measured
|
|
850
|
+
// `needle_count` on every outcome (including a skip), rather than a
|
|
851
|
+
// defaulted 0 that would be indistinguishable from "the masker never ran".
|
|
852
|
+
// Same "force-build, never default" posture as `getMaskerStats()` in
|
|
853
|
+
// `scripts/lib/vault-mirror/process.mjs`.
|
|
854
|
+
const masker = createSecretValueMasker(process.env);
|
|
855
|
+
|
|
750
856
|
// Read Session Config (CLAUDE.md / AGENTS.md) and resolve vault settings.
|
|
751
857
|
let config;
|
|
752
858
|
try {
|
|
753
859
|
const configText = await readConfigFile(repoRoot);
|
|
754
860
|
config = parseSessionConfig(configText, { hostPaths });
|
|
755
861
|
} catch {
|
|
756
|
-
return { result: { action: 'skipped-vault-disabled' } };
|
|
862
|
+
return { result: { action: 'skipped-vault-disabled' }, needleCount: masker.needleCount, hits: 0, dryRun };
|
|
757
863
|
}
|
|
758
864
|
|
|
759
865
|
const vaultIntegration = config?.['vault-integration'];
|
|
760
866
|
if (!vaultIntegration || vaultIntegration.enabled !== true) {
|
|
761
|
-
return { result: { action: 'skipped-vault-disabled' } };
|
|
867
|
+
return { result: { action: 'skipped-vault-disabled' }, needleCount: masker.needleCount, hits: 0, dryRun };
|
|
762
868
|
}
|
|
763
869
|
|
|
764
870
|
// Defense-in-depth: when the caller omits (or passes an empty) `repo`, derive
|
|
@@ -775,7 +881,7 @@ async function runNarrativeMirror(opts) {
|
|
|
775
881
|
|
|
776
882
|
const rawVaultDir = vaultIntegration['vault-dir'];
|
|
777
883
|
if (!rawVaultDir || typeof rawVaultDir !== 'string') {
|
|
778
|
-
return { result: { action: 'skipped-vault-disabled' } };
|
|
884
|
+
return { result: { action: 'skipped-vault-disabled' }, needleCount: masker.needleCount, hits: 0, dryRun };
|
|
779
885
|
}
|
|
780
886
|
|
|
781
887
|
const vaultDir = path.resolve(expandTilde(rawVaultDir));
|
|
@@ -799,7 +905,7 @@ async function runNarrativeMirror(opts) {
|
|
|
799
905
|
canonicalizeRoot: true,
|
|
800
906
|
});
|
|
801
907
|
if (!inside.ok) {
|
|
802
|
-
return { result: { action: 'skipped-invalid-path', path: outputPath } };
|
|
908
|
+
return { result: { action: 'skipped-invalid-path', path: outputPath }, needleCount: masker.needleCount, hits: 0, dryRun };
|
|
803
909
|
}
|
|
804
910
|
|
|
805
911
|
// Read STATE.md (best-effort; absent STATE.md → nothing to mirror).
|
|
@@ -808,13 +914,13 @@ async function runNarrativeMirror(opts) {
|
|
|
808
914
|
try {
|
|
809
915
|
stateContents = await readFile(stateMdPath, 'utf8');
|
|
810
916
|
} catch {
|
|
811
|
-
return { result: { action: 'skipped-no-statemd', path: outputPath } };
|
|
917
|
+
return { result: { action: 'skipped-no-statemd', path: outputPath }, needleCount: masker.needleCount, hits: 0, dryRun };
|
|
812
918
|
}
|
|
813
919
|
|
|
814
920
|
// #1025: the ONE masking site for the narrative mirror — after the frontmatter
|
|
815
921
|
// parse (so `[REDACTED]` can never break it) and before the render, the
|
|
816
922
|
// idempotency comparison and the write. See maskNarrative above.
|
|
817
|
-
const narrative = maskNarrative(extractNarrative(stateContents));
|
|
923
|
+
const { narrative, hits } = maskNarrative(extractNarrative(stateContents), masker);
|
|
818
924
|
const content = renderNarrative({ repo: repoName, narrative, now });
|
|
819
925
|
|
|
820
926
|
// `chars` measures the document THIS call rendered — so it is present on
|
|
@@ -823,5 +929,8 @@ async function runNarrativeMirror(opts) {
|
|
|
823
929
|
return {
|
|
824
930
|
result: writeNarrative({ outputPath, content, dryRun, fs: injectedFs }),
|
|
825
931
|
chars: content.length,
|
|
932
|
+
needleCount: masker.needleCount,
|
|
933
|
+
hits,
|
|
934
|
+
dryRun,
|
|
826
935
|
};
|
|
827
936
|
}
|