session-orchestrator 3.23.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 +13 -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 +1401 -0
- package/NOTICE +11 -6
- package/README.md +127 -92
- package/agents/db-specialist.md +0 -1
- 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 +249 -48
- package/docs/codex-setup.md +66 -22
- package/docs/components.md +37 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +51 -10
- 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 +8 -8
- package/docs/session-config-reference.md +120 -61
- package/docs/session-config-template.md +40 -33
- package/docs/telemetry/telemetry-claims.md +11 -10
- package/docs/telemetry.md +187 -4
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/atomic-json.mjs +111 -0
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-paths.mjs +143 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +11 -31
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/enforce-scope.mjs +35 -6
- 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 +280 -14
- package/hooks/on-session-start.mjs +153 -4
- package/hooks/on-stop.mjs +371 -17
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +189 -4
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +278 -392
- package/hooks/post-tool-batch-wave-signal.mjs +272 -44
- package/hooks/post-tool-failure-corrective-context.mjs +11 -34
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +24 -30
- 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/autopilot.mjs +26 -12
- package/scripts/backfill-abandoned-sessions.mjs +130 -15
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/emit-event.mjs +10 -2
- 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/auq/parse.mjs +5 -29
- package/scripts/lib/auto-dialectic.mjs +68 -0
- 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/autopilot/worktree-pipeline.mjs +82 -6
- package/scripts/lib/build-live-signals.mjs +25 -22
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/cold-start-detector.mjs +23 -14
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +63 -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 +9 -3
- 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 +388 -0
- 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 +234 -0
- 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/config.mjs +31 -3
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/enumerate.mjs +2 -17
- 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-schema.mjs +48 -0
- package/scripts/lib/events.mjs +256 -7
- package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
- 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/gitlab-portfolio/cli.mjs +3 -15
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
- 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-banner.mjs +20 -8
- 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/peer-discovery.mjs +20 -2
- 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 +249 -9
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/scope-gate.mjs +36 -0
- package/scripts/lib/session-close-backfill.mjs +125 -18
- package/scripts/lib/session-discovery.mjs +57 -3
- package/scripts/lib/session-end/phase-skip.mjs +2 -2
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +187 -11
- 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/session-transition.mjs +1 -1
- package/scripts/lib/sessions-canonical.mjs +446 -0
- 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 +255 -17
- package/scripts/lib/telemetry/sync.mjs +417 -24
- package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
- 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-doc-cli-commands.mjs +9 -33
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
- 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 +455 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
- package/scripts/lib/validate/check-unwired-features.mjs +0 -9
- package/scripts/lib/validate/check-validator-registration.mjs +254 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/markdown-fences.mjs +196 -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/board-lock.mjs +185 -0
- package/scripts/lib/vault-status/board-writer.mjs +174 -135
- package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
- 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 +502 -0
- package/scripts/lib/wave-resource-gate.mjs +133 -7
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +142 -8
- package/scripts/materialize-wave-scope.mjs +32 -9
- package/scripts/memory-propose.mjs +146 -8
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/promote-vault-strict.mjs +4 -15
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +36 -4
- 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 +187 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/vault-consolidate.mjs +3 -11
- package/scripts/vault-integration-watcher.mjs +2 -4
- package/scripts/vault-mirror.mjs +111 -26
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-auq.md +31 -2
- package/skills/_shared/parallel-aware-preamble.md +18 -4
- 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/ecosystem-health/SKILL.md +4 -1
- package/skills/ecosystem-health/wizard.md +5 -0
- package/skills/evolve/SKILL.md +87 -11
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +38 -2
- package/skills/remote-offload/SKILL.md +89 -0
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +19 -9
- 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 +41 -900
- 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 +16 -2
- 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 -1271
- 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 -269
- 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
|
@@ -43,18 +43,94 @@ const REQUIRED_CODEX_EVENTS = [
|
|
|
43
43
|
'Stop',
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Every hook event the Codex runtime knows, MEASURED — not quoted from release
|
|
48
|
+
* notes. Read out of the `codex` binary shipped as codex-cli **0.144.4**
|
|
49
|
+
* (`@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex`,
|
|
50
|
+
* 2026-09-06), which embeds one JSON Schema pair per event:
|
|
51
|
+
*
|
|
52
|
+
* strings -a <codex> | grep '"title": "'
|
|
53
|
+
* -> pre-tool-use / post-tool-use / permission-request / pre-compact
|
|
54
|
+
* / post-compact / session-start / subagent-start / subagent-stop
|
|
55
|
+
* / user-prompt-submit / stop .command.{input,output} (10 pairs)
|
|
56
|
+
*
|
|
57
|
+
* The same binary carries the manifest deserializer's key list
|
|
58
|
+
* (`PreToolUse PermissionRequest PostToolUse PreCompact PostCompact
|
|
59
|
+
* SessionStart SubagentStart SubagentStop` + the substring-deduped `Stop` /
|
|
60
|
+
* `UserPromptSubmit`) directly beside its `unexpected map key` error string —
|
|
61
|
+
* so an event key outside this set is rejected, and rejecting one key rejects
|
|
62
|
+
* the manifest that carries it.
|
|
63
|
+
*
|
|
64
|
+
* **`SessionEnd` and `Interrupt` are NOT in it.** They are not merely unwired
|
|
65
|
+
* here: at 0.144.4 they do not exist, and adding either to hooks-codex.json
|
|
66
|
+
* risks taking every already-working hook down with it. Newer Codex releases
|
|
67
|
+
* (0.148+ async hooks, 0.150+ `Interrupt`) are documented-but-unverified on
|
|
68
|
+
* this host — re-measure against the shipped binary before widening this set,
|
|
69
|
+
* never against a changelog.
|
|
70
|
+
*/
|
|
71
|
+
export const CODEX_NATIVE_EVENTS = new Set([
|
|
72
|
+
'SessionStart',
|
|
73
|
+
'UserPromptSubmit',
|
|
74
|
+
'PreToolUse',
|
|
75
|
+
'PermissionRequest',
|
|
76
|
+
'PostToolUse',
|
|
77
|
+
'PreCompact',
|
|
78
|
+
'PostCompact',
|
|
79
|
+
'SubagentStart',
|
|
80
|
+
'SubagentStop',
|
|
81
|
+
'Stop',
|
|
51
82
|
]);
|
|
52
83
|
|
|
53
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Claude hook events this repo wires that Codex 0.144.4 has no counterpart for.
|
|
86
|
+
* DERIVED from {@link CODEX_NATIVE_EVENTS} rather than hand-listed, so a new
|
|
87
|
+
* Claude event is forbidden on Codex by construction until someone measures it
|
|
88
|
+
* into the native set above.
|
|
89
|
+
*/
|
|
90
|
+
const FORBIDDEN_CODEX_EVENTS = new Set(
|
|
91
|
+
['SessionEnd', 'PostToolUseFailure', 'PostToolBatch', 'CwdChanged', 'Interrupt']
|
|
92
|
+
.filter((event) => !CODEX_NATIVE_EVENTS.has(event)),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The tool-name vocabulary Codex puts in `tool_name`, measured at 0.144.4 from
|
|
97
|
+
* the same binary. This is the load-bearing incompatibility, and it is NOT the
|
|
98
|
+
* one the older comments in `check-hooks-symmetry.mjs` claimed.
|
|
99
|
+
*
|
|
100
|
+
* `pre-tool-use.command.input` REQUIRES `tool_name` and `tool_input` (alongside
|
|
101
|
+
* `cwd`, `hook_event_name`, `model`, `permission_mode`, `session_id`,
|
|
102
|
+
* `tool_use_id`, `transcript_path`, `turn_id`) — so the payload fields ARE
|
|
103
|
+
* delivered. What differs is their VALUES: Codex has no `Bash`, `Edit`, `Write`
|
|
104
|
+
* or `MultiEdit` tool (`strings -a <codex> | grep -c '"Bash"'` -> 0). Every
|
|
105
|
+
* PreToolUse guard in `hooks/` opens with an equality gate on one of those four
|
|
106
|
+
* names and returns `emitAllow()` otherwise, so wiring one here yields a
|
|
107
|
+
* handler that runs, matches nothing, and allows everything — false
|
|
108
|
+
* enforcement, which is worse than a registered gap.
|
|
109
|
+
*
|
|
110
|
+
* The adapter this actually needs is a tool-name MAP (shell/exec_command/
|
|
111
|
+
* unified_exec -> Bash, apply_patch -> Edit/Write), not a payload bridge.
|
|
112
|
+
*/
|
|
113
|
+
export const CODEX_TOOL_NAMES = Object.freeze([
|
|
114
|
+
'shell',
|
|
115
|
+
'exec_command',
|
|
116
|
+
'unified_exec',
|
|
117
|
+
'apply_patch',
|
|
118
|
+
'update_plan',
|
|
119
|
+
'view_image',
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Hook handlers that read an Edit/Write `tool_input.file_path` payload. Codex
|
|
124
|
+
* emits `apply_patch` payloads of a different shape, so wiring one of these into
|
|
125
|
+
* hooks-codex.json produces a handler that silently never fires. Exported so the
|
|
126
|
+
* contract test can assert the census rather than re-list it.
|
|
127
|
+
*/
|
|
128
|
+
export const DISALLOWED_EDIT_HANDLERS = [
|
|
54
129
|
'enforce-scope.mjs',
|
|
55
130
|
'config-protection.mjs',
|
|
56
131
|
'post-edit-validate.mjs',
|
|
57
132
|
'post-tooluse-frontend-slop.mjs',
|
|
133
|
+
'post-edit-import-probe.mjs',
|
|
58
134
|
];
|
|
59
135
|
|
|
60
136
|
const CODEX_HOOK_WRAPPER_COMMAND =
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* Behaviour (PRD §F1.3):
|
|
11
11
|
* 1. Skip if `bootstrap.lock` is missing (repo not yet bootstrapped —
|
|
12
12
|
* the bootstrap-gate handles that path).
|
|
13
|
-
* 2. Skip if `sessions.jsonl`
|
|
14
|
-
*
|
|
13
|
+
* 2. Skip if the `sessions.jsonl` DISTINCT-session count ≥
|
|
14
|
+
* `silence-after-sessions` — identities, not lines (see `countSessions`);
|
|
15
|
+
* the operator has already engaged at least once.
|
|
15
16
|
* 3. Skip if bootstrap age < `nudge-after-hours` (give the operator a
|
|
16
17
|
* reasonable window after bootstrap before nudging).
|
|
17
18
|
* 4. Emit otherwise, with a `markerPath` when the migration marker
|
|
@@ -34,6 +35,7 @@ import { stat, readFile, unlink } from 'node:fs/promises';
|
|
|
34
35
|
import path from 'node:path';
|
|
35
36
|
|
|
36
37
|
import { parseBootstrapLock } from './bootstrap-lock-freshness.mjs';
|
|
38
|
+
import { countSessionsInJsonl } from './sessions-canonical.mjs';
|
|
37
39
|
|
|
38
40
|
/** Milliseconds in one hour — exported for testability. */
|
|
39
41
|
export const MS_PER_HOUR = 60 * 60 * 1000;
|
|
@@ -77,24 +79,31 @@ function buildBannerLines({ bootstrappedAt = null } = {}) {
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
|
-
* Best-effort
|
|
81
|
-
* missing OR empty OR unreadable — all three are
|
|
82
|
-
* cold-start decision ("no sessions yet").
|
|
82
|
+
* Best-effort counter of DISTINCT physical sessions in an NDJSON ledger.
|
|
83
|
+
* Returns 0 when the file is missing OR empty OR unreadable — all three are
|
|
84
|
+
* equivalent for the cold-start decision ("no sessions yet").
|
|
85
|
+
*
|
|
86
|
+
* Counts IDENTITIES, not lines (#1167): `sessions.jsonl` is append-only, so one
|
|
87
|
+
* physical session can occupy two lines (a backfilled abandoned stub plus the
|
|
88
|
+
* authoritative record that supersedes it, or the systemic double-stub pair
|
|
89
|
+
* written by the two backfill writers). A raw line count therefore reports
|
|
90
|
+
* "the operator has already engaged" one session earlier than is true, and the
|
|
91
|
+
* cold-start nudge — one-shot per repo at the default threshold of 1 — is
|
|
92
|
+
* silently never emitted.
|
|
83
93
|
*
|
|
84
94
|
* Reads the full file because sessions.jsonl is small (1 line/session, ~2 KB
|
|
85
|
-
* each — even 100 sessions = ~200 KB).
|
|
95
|
+
* each — even 100 sessions = ~200 KB). The async `readFile` is kept (the
|
|
96
|
+
* SessionStart hook's 5s budget must not block); only the collapse rules come
|
|
97
|
+
* from the shared canonical module.
|
|
86
98
|
*
|
|
87
99
|
* @param {string} filePath
|
|
88
100
|
* @returns {Promise<number>}
|
|
89
101
|
*/
|
|
90
|
-
async function
|
|
102
|
+
async function countSessions(filePath) {
|
|
91
103
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// both Unix (\n) and Windows (\r\n) line endings.
|
|
96
|
-
const lines = raw.split('\n').filter((l) => l.length > 0);
|
|
97
|
-
return lines.length;
|
|
104
|
+
// Counting rule (blank/malformed lines, id-less records) lives in
|
|
105
|
+
// `countSessionsInJsonl`; only the async read is local.
|
|
106
|
+
return countSessionsInJsonl(await readFile(filePath, 'utf8'));
|
|
98
107
|
} catch {
|
|
99
108
|
return 0;
|
|
100
109
|
}
|
|
@@ -166,7 +175,7 @@ export async function detectColdStart(opts = {}) {
|
|
|
166
175
|
|
|
167
176
|
// PRD §F1.3 Ubiquitous: silence once the repo has ≥ N sessions. This is
|
|
168
177
|
// the auto-silence path — banner is one-shot per repo (default N=1).
|
|
169
|
-
const sessionCount = await
|
|
178
|
+
const sessionCount = await countSessions(sessionsPath);
|
|
170
179
|
if (sessionCount >= silenceAfterSessions) {
|
|
171
180
|
return {
|
|
172
181
|
shouldEmit: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* auto-dream.mjs — Parser for the top-level `auto-dream:` YAML block (issue #566).
|
|
@@ -32,7 +33,7 @@ export function _parseAutoDream(content) {
|
|
|
32
33
|
'min-confidence': 0.5,
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
const lines = content
|
|
36
|
+
const lines = preprocessBlockLines(content);
|
|
36
37
|
let inBlock = false;
|
|
37
38
|
const blockLines = [];
|
|
38
39
|
|
|
@@ -31,6 +31,19 @@
|
|
|
31
31
|
*
|
|
32
32
|
* The two `**` markers are independent (`**key:` and `key:**` both match),
|
|
33
33
|
* mirroring the tolerant #823 vault-integration regex.
|
|
34
|
+
*
|
|
35
|
+
* COMMENT-STRIPPING IS THE CALLER'S JOB (#1222). Every matcher here sees RAW
|
|
36
|
+
* lines: a header inside a `<!-- … -->` HTML comment matches exactly like a live
|
|
37
|
+
* one. That is deliberate — stripping comments needs the `block-preprocess.mjs`
|
|
38
|
+
* state machine, and importing it would end this module's zero-import leaf
|
|
39
|
+
* status. A caller that must agree with a comment-STRIPPING parser preprocesses
|
|
40
|
+
* its content first (`preprocessBlockLinesNoDash(content).join('\n')`) — see
|
|
41
|
+
* `dispatcher-autonomy-capture.mjs` `isDispatcherAutonomyBlockPresent()`.
|
|
42
|
+
*
|
|
43
|
+
* `matchBlockHeaderDetailed` (#1185) is a SEPARATE, additive matcher below for
|
|
44
|
+
* parsers that need what this contract deliberately rejects — an indented
|
|
45
|
+
* (nested) header and/or an inline value. It does not change the contract
|
|
46
|
+
* above; `matchBlockHeader`/`hasBlockHeader` keep exactly this behaviour.
|
|
34
47
|
*/
|
|
35
48
|
|
|
36
49
|
/**
|
|
@@ -73,6 +86,56 @@ export function matchBlockHeader(line, key) {
|
|
|
73
86
|
return blockHeaderRe(key).test(line);
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Build the indent-aware, inline-value-capturing block-header regex for a key.
|
|
91
|
+
* A strict SUPERSET of `blockHeaderRe`: it additionally matches an ARBITRARY
|
|
92
|
+
* leading indent (group 1) and an optional inline value trailing the colon
|
|
93
|
+
* (group 2) — the two things `matchBlockHeader` deliberately rejects (a
|
|
94
|
+
* nested sub-key, and a header carrying a value, per the module docblock).
|
|
95
|
+
*
|
|
96
|
+
* @param {string} key
|
|
97
|
+
* @returns {RegExp}
|
|
98
|
+
*/
|
|
99
|
+
function detailedBlockHeaderRe(key) {
|
|
100
|
+
return new RegExp(
|
|
101
|
+
'^(\\s*)(?:-\\s+)?(?:\\*\\*)?' + escapeRegExp(key) + ':(?:\\*\\*)?(?:[ \\t]+(.*))?$'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Indent + inline-value variant of `matchBlockHeader` (#1185). Additive —
|
|
107
|
+
* `matchBlockHeader`/`hasBlockHeader` are unchanged and every existing caller
|
|
108
|
+
* keeps its current behaviour untouched.
|
|
109
|
+
*
|
|
110
|
+
* Built for parsers whose header can be NESTED under a parent block (e.g.
|
|
111
|
+
* `health-endpoints:` one level under `ecosystem-health:`) and/or carry an
|
|
112
|
+
* INLINE value (`health-endpoints: [{name: …}]`) on the same line — exactly
|
|
113
|
+
* the two forms `matchBlockHeader` treats as "not a top-level block-opener".
|
|
114
|
+
* This is a separate, purpose-built matcher, not a relaxed replacement: a
|
|
115
|
+
* caller that only needs the boolean top-level check keeps using
|
|
116
|
+
* `matchBlockHeader`.
|
|
117
|
+
*
|
|
118
|
+
* The captured value is RAW text, never comment-stripped — `key: # note`
|
|
119
|
+
* reports `value: '# note'`, exactly as the pre-#1185 `health-endpoints.mjs`
|
|
120
|
+
* `HEADER_RE` did. A caller that needs comment semantics strips them itself
|
|
121
|
+
* (as `health-endpoints.mjs` already does for its block BODY via its own
|
|
122
|
+
* `stripComment()` — only the header line's raw capture moved here).
|
|
123
|
+
*
|
|
124
|
+
* @param {string} line — a single line (callers strip the trailing `\n`)
|
|
125
|
+
* @param {string} key — the literal block key
|
|
126
|
+
* @returns {{indent: number, value: string|null}|null} `null` on no match;
|
|
127
|
+
* `value` is `null` for a bare header (`key:`, optionally trailing
|
|
128
|
+
* whitespace only), else the trimmed text following the colon.
|
|
129
|
+
*/
|
|
130
|
+
export function matchBlockHeaderDetailed(line, key) {
|
|
131
|
+
if (typeof line !== 'string' || typeof key !== 'string' || key === '') return null;
|
|
132
|
+
const m = line.match(detailedBlockHeaderRe(key));
|
|
133
|
+
if (!m) return null;
|
|
134
|
+
const indent = m[1].length;
|
|
135
|
+
const trimmed = (m[2] ?? '').trim();
|
|
136
|
+
return { indent, value: trimmed === '' ? null : trimmed };
|
|
137
|
+
}
|
|
138
|
+
|
|
76
139
|
/**
|
|
77
140
|
* Multiline presence variant: true when ANY line of `content` opens the
|
|
78
141
|
* top-level block for `key`. For whole-file PRESENCE guards (e.g.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* block-preprocess.mjs — THE line-preprocessing contract for the block parsers
|
|
3
|
+
* under `scripts/lib/config/` (#1162).
|
|
4
|
+
*
|
|
5
|
+
* Every block parser here begins with `content.split(/\r?\n/)`, then walks lines
|
|
6
|
+
* looking for its own header and the indented sub-keys under it. Two markdown
|
|
7
|
+
* shapes that are ordinary in a CLAUDE.md / AGENTS.md were invisible to that
|
|
8
|
+
* walk, and both failed in the dangerous direction:
|
|
9
|
+
*
|
|
10
|
+
* (a) A block commented OUT with a multi-line `<!-- … -->` was read as LIVE
|
|
11
|
+
* config. Commenting a key out is the most ordinary way to disable it; it
|
|
12
|
+
* must not arm it. (`_extractConfigSection` learned this in #1097 — the
|
|
13
|
+
* block parsers never did.)
|
|
14
|
+
* (b) The bold-bullet rendering of a SUB-key (`- **enabled:** true`) matched
|
|
15
|
+
* no sub-key regex, so the key was absent and its DEFAULT applied. For a
|
|
16
|
+
* boolean that default is `false`, which reads exactly like a deliberately
|
|
17
|
+
* disabled block — no error, in any log. #823 fixed this for the block
|
|
18
|
+
* HEADER only (see `block-header.mjs`); this fixes it for the body.
|
|
19
|
+
*
|
|
20
|
+
* ZERO IMPORTS beyond the sibling `section-extractor.mjs` skipper, stdlib only —
|
|
21
|
+
* `tests/lib/config/cycle-guard.test.mjs` forbids any `scripts/lib/config/*.mjs`
|
|
22
|
+
* from importing `../config.mjs`, and the RAW-PARITY constraint (block-header.mjs
|
|
23
|
+
* docblock) requires the normalisation to live in the helper each parser calls,
|
|
24
|
+
* never in a preprocessing step in `config.mjs`.
|
|
25
|
+
*
|
|
26
|
+
* DELIBERATE NON-GOAL — fenced code blocks. These parsers are fence-UNAWARE, and
|
|
27
|
+
* that is a pinned contract, not an oversight. Two tests, cited by NAME because
|
|
28
|
+
* line numbers rot: `tests/lib/config/frontend-slop-hook.test.mjs` — "parses a
|
|
29
|
+
* block wrapped in a fenced code block — the parser is fence-unaware", and
|
|
30
|
+
* `tests/lib/config/dispatcher-autonomy-capture.test.mjs` — "treats a header
|
|
31
|
+
* inside a fenced code block as PRESENT". Both assert that a ```-fenced block IS
|
|
32
|
+
* parsed. Teaching this module about
|
|
33
|
+
* fences would flip ~36 parsers at once and break those pins, so it does not.
|
|
34
|
+
* Revisit only as its own change, with those two tests as the blast radius.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { htmlCommentSkipper } from './section-extractor.mjs';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Drop every line that lies inside — or opens — a multi-line `<!-- … -->` HTML
|
|
41
|
+
* comment (#1162a).
|
|
42
|
+
*
|
|
43
|
+
* Wraps `htmlCommentSkipper()` from `section-extractor.mjs` rather than
|
|
44
|
+
* re-deriving the state machine, so the block parsers and the Session Config
|
|
45
|
+
* KV extractor share ONE definition of "this line is commented-out
|
|
46
|
+
* documentation" by construction. A fresh closure is built per call: the skipper
|
|
47
|
+
* carries per-document state, and a shared instance would leak an unterminated
|
|
48
|
+
* comment from one document into the next.
|
|
49
|
+
*
|
|
50
|
+
* Inherits the skipper's deliberate ceiling: the opener must be at the START of
|
|
51
|
+
* a line (`/^\s*<!--/`), so `waves: 5 <!-- note` does NOT swallow the following
|
|
52
|
+
* lines — the trailing single-line comment form this repo's own convention
|
|
53
|
+
* encourages keeps working.
|
|
54
|
+
*
|
|
55
|
+
* Never touches `- ` list markers, indentation, or line content.
|
|
56
|
+
*
|
|
57
|
+
* FAIL-CLOSED on an UNTERMINATED comment. A stray `<!--` with no `-->` after it
|
|
58
|
+
* puts the skipper in the swallowing state for the REST of the document, so
|
|
59
|
+
* every later block silently disappears and each parser falls back to its
|
|
60
|
+
* defaults — for `config-protection` that is `strict` → `warn`, i.e. a guard
|
|
61
|
+
* disarmed by a typo, with no error anywhere. When the skipper would still be
|
|
62
|
+
* inside a comment at EOF, this returns the INPUT lines unfiltered (the
|
|
63
|
+
* pre-#1162 behaviour: the opener is treated as literal text), so nothing can
|
|
64
|
+
* vanish. The condition is reported via `onUnterminated` and is separately
|
|
65
|
+
* observable through {@link findUnterminatedComment}.
|
|
66
|
+
*
|
|
67
|
+
* @param {string[]} lines
|
|
68
|
+
* @param {(lineNo: number) => void} [onUnterminated] — called with the 1-based
|
|
69
|
+
* line number of the unterminated opener when stripping is disabled.
|
|
70
|
+
* @returns {string[]} a new array; the input is not mutated.
|
|
71
|
+
*/
|
|
72
|
+
export function stripHtmlCommentBlocks(lines, onUnterminated) {
|
|
73
|
+
if (!Array.isArray(lines)) return [];
|
|
74
|
+
const unterminatedAt = findUnterminatedComment(lines);
|
|
75
|
+
if (unterminatedAt !== null) {
|
|
76
|
+
if (typeof onUnterminated === 'function') onUnterminated(unterminatedAt);
|
|
77
|
+
return lines.slice();
|
|
78
|
+
}
|
|
79
|
+
const skip = htmlCommentSkipper();
|
|
80
|
+
return lines.filter((line) => !skip(typeof line === 'string' ? line : ''));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The 1-based line number of an `<!--` that is never closed, or `null` when
|
|
85
|
+
* every comment in `lines` is terminated.
|
|
86
|
+
*
|
|
87
|
+
* Derived from the SAME `htmlCommentSkipper()` state machine rather than a
|
|
88
|
+
* second copy of it (the RAW-PARITY constraint): the skipper swallows the
|
|
89
|
+
* closing `-->` line too, so the document ends inside a comment exactly when
|
|
90
|
+
* the LAST swallowed line carries no `-->`. A swallowed line that follows a
|
|
91
|
+
* closing line (or the first one of all) is an opener, which is how the
|
|
92
|
+
* reported line number is obtained.
|
|
93
|
+
*
|
|
94
|
+
* @param {string[]} lines
|
|
95
|
+
* @returns {number|null}
|
|
96
|
+
*/
|
|
97
|
+
export function findUnterminatedComment(lines) {
|
|
98
|
+
if (!Array.isArray(lines)) return null;
|
|
99
|
+
const skip = htmlCommentSkipper();
|
|
100
|
+
let openerLine = null;
|
|
101
|
+
/** @type {string|null} */
|
|
102
|
+
let lastSwallowed = null;
|
|
103
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
104
|
+
const line = typeof lines[i] === 'string' ? lines[i] : '';
|
|
105
|
+
if (!skip(line)) continue;
|
|
106
|
+
if (lastSwallowed === null || lastSwallowed.includes('-->')) openerLine = i + 1;
|
|
107
|
+
lastSwallowed = line;
|
|
108
|
+
}
|
|
109
|
+
if (lastSwallowed === null || lastSwallowed.includes('-->')) return null;
|
|
110
|
+
return openerLine;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* `- **key:** value` / `- **key**: value` / `**key:** value` → `key: value`,
|
|
115
|
+
* preserving the leading indentation EXACTLY (#1162b).
|
|
116
|
+
*
|
|
117
|
+
* The indent is load-bearing: every block parser ends its block at the first
|
|
118
|
+
* non-empty non-indented line, so an indent-eating rewrite would truncate the
|
|
119
|
+
* block one line early. Only the `**` markers and the optional `- ` bullet
|
|
120
|
+
* immediately around the `key:` token at line start are removed; the value
|
|
121
|
+
* portion (including any literal `**` inside it) is untouched — same rule as the
|
|
122
|
+
* `_matchKVLine` Format-1 branch and the #823 header tolerance.
|
|
123
|
+
*
|
|
124
|
+
* A line that is not a bold key/value pair is returned byte-identical.
|
|
125
|
+
*
|
|
126
|
+
* @internal — exported for this module's own tests. Production callers use
|
|
127
|
+
* {@link preprocessBlockLines} / {@link preprocessBlockLinesNoDash}.
|
|
128
|
+
*
|
|
129
|
+
* NAMED RISK — dash-record blocks. In any block whose body uses `- ` as a RECORD
|
|
130
|
+
* boundary, de-dashing the first key of a record merges it into the PREVIOUS
|
|
131
|
+
* record, silently producing one record where two were written. Those parsers
|
|
132
|
+
* must use
|
|
133
|
+
* `preprocessBlockLinesNoDash` instead. `tests/lib/config/block-preprocess.test.mjs`
|
|
134
|
+
* demonstrates the merge so the next reader sees why the NoDash variant exists.
|
|
135
|
+
*
|
|
136
|
+
* @param {string[]} lines
|
|
137
|
+
* @returns {string[]} a new array; the input is not mutated.
|
|
138
|
+
*/
|
|
139
|
+
export function normalizeBoldSubkeys(lines) {
|
|
140
|
+
if (!Array.isArray(lines)) return [];
|
|
141
|
+
return lines.map((line) => {
|
|
142
|
+
if (typeof line !== 'string') return line;
|
|
143
|
+
// (1) indent (2) optional bullet (3) **key:** or **key**: (4) rest
|
|
144
|
+
const m = line.match(/^(\s*)(?:-\s+)?\*\*([^*:]+?)(?::\*\*|\*\*:)\s*(.*)$/);
|
|
145
|
+
if (!m) return line;
|
|
146
|
+
const value = m[3];
|
|
147
|
+
return value === '' ? `${m[1]}${m[2]}:` : `${m[1]}${m[2]}: ${value}`;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* THE drop-in replacement for `content.split(/\r?\n/)` in a flat or
|
|
153
|
+
* nested-OBJECT block parser: strip commented-out lines, then normalise bold
|
|
154
|
+
* sub-keys.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} content — full file contents
|
|
157
|
+
* @returns {string[]} preprocessed lines (empty array for non-string input)
|
|
158
|
+
*/
|
|
159
|
+
export function preprocessBlockLines(content) {
|
|
160
|
+
if (typeof content !== 'string') return [];
|
|
161
|
+
return normalizeBoldSubkeys(stripHtmlCommentBlocks(content.split(/\r?\n/)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* THE drop-in for every dash-RECORD parser — any parser whose block body uses
|
|
166
|
+
* `- ` as a record boundary, so the dash must survive: HTML-comment stripping
|
|
167
|
+
* only, no bold-subkey normalisation. (Enumerating them here goes stale; the
|
|
168
|
+
* rule is the criterion. `grep -l preprocessBlockLinesNoDash scripts/lib/config/`
|
|
169
|
+
* lists the current set.)
|
|
170
|
+
*
|
|
171
|
+
* @param {string} content — full file contents
|
|
172
|
+
* @returns {string[]} preprocessed lines (empty array for non-string input)
|
|
173
|
+
*/
|
|
174
|
+
export function preprocessBlockLinesNoDash(content) {
|
|
175
|
+
if (typeof content !== 'string') return [];
|
|
176
|
+
return stripHtmlCommentBlocks(content.split(/\r?\n/));
|
|
177
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* broken-window.mjs — Parser for the top-level `broken-window-budget:` YAML block
|
|
@@ -48,7 +49,7 @@ export function _parseBrokenWindow(content) {
|
|
|
48
49
|
'due-days': 7,
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
const lines = String(content ?? '')
|
|
52
|
+
const lines = preprocessBlockLines(String(content ?? ''));
|
|
52
53
|
let inBlock = false;
|
|
53
54
|
const blockLines = [];
|
|
54
55
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* cold-start.mjs — Parser for the top-level `cold-start:` YAML block (PRD F1.3 /
|
|
@@ -30,7 +31,7 @@ export function _parseColdStart(content) {
|
|
|
30
31
|
'silence-after-sessions': 1,
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
const lines = content
|
|
34
|
+
const lines = preprocessBlockLines(content);
|
|
34
35
|
let inBlock = false;
|
|
35
36
|
const blockLines = [];
|
|
36
37
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { findUnterminatedComment, preprocessBlockLines, preprocessBlockLinesNoDash } from './block-preprocess.mjs';
|
|
2
3
|
import { isSessionConfigHeading } from './section-extractor.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -45,7 +46,7 @@ export function _parseConfigProtection(content) {
|
|
|
45
46
|
|
|
46
47
|
if (typeof content !== 'string' || content.length === 0) return defaults;
|
|
47
48
|
|
|
48
|
-
const lines = content
|
|
49
|
+
const lines = preprocessBlockLines(content);
|
|
49
50
|
let inBlock = false;
|
|
50
51
|
const blockLines = [];
|
|
51
52
|
|
|
@@ -107,7 +108,26 @@ export function _parseConfigProtection(content) {
|
|
|
107
108
|
export function _isConfigWeakeningAllowed(content) {
|
|
108
109
|
if (typeof content !== 'string' || content.length === 0) return false;
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
// HTML-comment stripping ONLY (`preprocessBlockLinesNoDash`), never the full
|
|
112
|
+
// `preprocessBlockLines`: bold-subkey normalisation would make
|
|
113
|
+
// `- **allow-config-weakening:** true` arm the bypass, and
|
|
114
|
+
// `tests/lib/config/config-protection.test.mjs` pins the opposite — only the
|
|
115
|
+
// plain form is the supported bypass. Fail-closed beats markdown tolerance there.
|
|
116
|
+
// The comment strip is the OTHER direction and is mandatory (W4/F1): with a raw
|
|
117
|
+
// `content.split(/\r?\n/)` a COMMENTED-OUT `<!--\nallow-config-weakening: true\n-->`
|
|
118
|
+
// inside `## Session Config` ARMED the bypass — commenting a key out is the most
|
|
119
|
+
// ordinary way to disable it and must never enable it (same class as #1162a).
|
|
120
|
+
//
|
|
121
|
+
// UNTERMINATED `<!--` — the fail-closed direction INVERTS for a bypass scan.
|
|
122
|
+
// `stripHtmlCommentBlocks` returns the lines UNFILTERED when a comment is never
|
|
123
|
+
// closed, which is right for a block PARSER (nothing may silently vanish) and
|
|
124
|
+
// wrong here: it hands the commented-out bypass line straight back and re-arms
|
|
125
|
+
// the guard's own off switch. For this scan "I cannot tell where the comments
|
|
126
|
+
// end" must mean NOT ARMED — the bypass is opt-in, so refusing to grant it is
|
|
127
|
+
// the safe reading in every ambiguous document.
|
|
128
|
+
if (findUnterminatedComment(content.split(/\r?\n/)) !== null) return false;
|
|
129
|
+
|
|
130
|
+
const lines = preprocessBlockLinesNoDash(content);
|
|
111
131
|
let inConfig = false;
|
|
112
132
|
for (const line of lines) {
|
|
113
133
|
if (isSessionConfigHeading(line)) { inConfig = true; continue; }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* context-coverage.mjs — Parser for the top-level `context-coverage:` YAML block.
|
|
@@ -37,7 +38,7 @@ import { matchBlockHeader } from './block-header.mjs';
|
|
|
37
38
|
export function _parseContextCoverage(content) {
|
|
38
39
|
const defaults = { enabled: false, mode: 'warn' };
|
|
39
40
|
|
|
40
|
-
const lines = content
|
|
41
|
+
const lines = preprocessBlockLines(content);
|
|
41
42
|
let inBlock = false;
|
|
42
43
|
const blockLines = [];
|
|
43
44
|
|
|
@@ -20,6 +20,7 @@ import { homedir } from 'node:os';
|
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { readConfigFile } from './io.mjs';
|
|
22
22
|
import { matchBlockHeader } from './block-header.mjs';
|
|
23
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Resolve the cross-repo confinement root. Defaults to ~/Projects.
|
|
@@ -47,7 +48,7 @@ const SAFE_PATH_RE = /^[A-Za-z0-9._~/-]+$/;
|
|
|
47
48
|
* @returns {string[]} list of project paths, or [] when absent/empty/none/null
|
|
48
49
|
*/
|
|
49
50
|
export function _parseCrossRepo(content) {
|
|
50
|
-
const lines = content
|
|
51
|
+
const lines = preprocessBlockLines(content);
|
|
51
52
|
let inBlock = false;
|
|
52
53
|
|
|
53
54
|
for (const rawLine of lines) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLinesNoDash } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* custom-phases.mjs — Parser for the `custom-phases:` Session Config block (#637).
|
|
@@ -65,7 +66,7 @@ const SAFE_PATH_RE = /^[A-Za-z0-9._~/-]+$/;
|
|
|
65
66
|
* @returns {Array<{name: string, when: string, command: string, mode: string, review: string|null}>}
|
|
66
67
|
*/
|
|
67
68
|
export function _parseCustomPhases(content) {
|
|
68
|
-
const lines = content
|
|
69
|
+
const lines = preprocessBlockLinesNoDash(content);
|
|
69
70
|
let inBlock = false;
|
|
70
71
|
const blockLines = [];
|
|
71
72
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* dialectic.mjs — Parser for top-level `dialectic:` YAML block (#506).
|
|
@@ -30,7 +31,7 @@ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
|
30
31
|
export function _parseDialectic(content) {
|
|
31
32
|
const defaults = { cadence: 5, model: 'haiku', 'budget-tokens': 8000 };
|
|
32
33
|
|
|
33
|
-
const lines = content
|
|
34
|
+
const lines = preprocessBlockLines(content);
|
|
34
35
|
let inBlock = false;
|
|
35
36
|
const blockLines = [];
|
|
36
37
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* discovery-validator.mjs — Parser for the top-level `discovery-validator:`
|
|
@@ -10,6 +11,11 @@ import { matchBlockHeader } from './block-header.mjs';
|
|
|
10
11
|
* transcript. v1 is log+warn only (events.jsonl + stderr WARN); exit 2
|
|
11
12
|
* (blocking) is reserved for a future hard-gate.
|
|
12
13
|
*
|
|
14
|
+
* OFF by default (opt-in) — issue #567's original acceptance criterion. The
|
|
15
|
+
* #690 flip to ON (2026-06-25) was reverted 2026-09-02 (#1191) after fleet
|
|
16
|
+
* measurement: 6,946 `discovery_validator_violation` events accumulated in 18
|
|
17
|
+
* repos that never declared the block.
|
|
18
|
+
*
|
|
13
19
|
* Returns `{ enabled }`.
|
|
14
20
|
* Tolerant parser: malformed values silently fall back to defaults.
|
|
15
21
|
*
|
|
@@ -21,17 +27,17 @@ import { matchBlockHeader } from './block-header.mjs';
|
|
|
21
27
|
* Independent of the `## Session Config` section boundary.
|
|
22
28
|
*
|
|
23
29
|
* Defaults:
|
|
24
|
-
* enabled:
|
|
30
|
+
* enabled: false (opt-in)
|
|
25
31
|
*
|
|
26
32
|
* @param {string} content — full file contents
|
|
27
33
|
* @returns {{ enabled: boolean }}
|
|
28
34
|
*/
|
|
29
35
|
export function _parseDiscoveryValidator(content) {
|
|
30
36
|
const defaults = {
|
|
31
|
-
enabled:
|
|
37
|
+
enabled: false,
|
|
32
38
|
};
|
|
33
39
|
|
|
34
|
-
const lines = content
|
|
40
|
+
const lines = preprocessBlockLines(content);
|
|
35
41
|
let inBlock = false;
|
|
36
42
|
const blockLines = [];
|
|
37
43
|
|