session-orchestrator 5.1.0 → 5.3.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 +3 -1
- package/.agents/skills/autopilot/SKILL.md +6 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +7 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +8 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +21 -0
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +7 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +7 -2
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +7 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +7 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +7 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +8 -3
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +22 -0
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +7 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +20 -0
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +6 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +6 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +8 -2
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +21 -0
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +6 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +22 -0
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +6 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +6 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +7 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +21 -0
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +7 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +13 -4
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +197 -0
- package/README.md +11 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +17 -3
- package/docs/baseline.md +12 -6
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +15 -3
- package/docs/components.md +13 -6
- package/docs/events-schema.md +59 -9
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +135 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +106 -11
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +125 -8
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +72 -25
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +211 -23
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +6 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +200 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +18 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +119 -28
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +385 -5
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/hooks/subagent-telemetry.mjs +15 -19
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +2 -2
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +171 -15
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +77 -32
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +220 -11
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +408 -33
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +76 -13
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +524 -5
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +332 -50
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +122 -91
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/rule-loader.mjs +41 -12
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +429 -7
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +91 -12
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
- package/scripts/lib/user-invocable-skills.mjs +205 -0
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +329 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +35 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
- package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
- package/scripts/lib/validate/check-unwired-features.mjs +220 -33
- package/scripts/lib/validate/check-validator-registration.mjs +36 -12
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +39 -12
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +109 -39
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +227 -0
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +11 -4
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +80 -11
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +52 -23
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +9 -2
- package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +2 -2
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +21 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +18 -6
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +20 -2
- package/skills/wave-executor/SKILL.md +14 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
|
@@ -104,10 +104,17 @@
|
|
|
104
104
|
// Import-safety: importing this module MUST NOT execute anything — the isMain
|
|
105
105
|
// guard at the bottom is the only side-effecting path.
|
|
106
106
|
|
|
107
|
-
import { pathToFileURL } from 'node:url';
|
|
108
107
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
109
108
|
import { spawnSync } from 'node:child_process';
|
|
110
109
|
import path from 'node:path';
|
|
110
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
111
|
+
import { maskSource } from '../js-mask.mjs';
|
|
112
|
+
|
|
113
|
+
// `maskSource` moved to `scripts/lib/js-mask.mjs` (#1388 — three other modules
|
|
114
|
+
// needed the same lexer). `package.json` carries no `exports` map, so every
|
|
115
|
+
// `scripts/lib/**` path is public API: this re-export keeps the OLD import path
|
|
116
|
+
// working for any consumer that already reaches in here.
|
|
117
|
+
export { maskSource } from '../js-mask.mjs';
|
|
111
118
|
|
|
112
119
|
// ---------------------------------------------------------------------------
|
|
113
120
|
// Constants
|
|
@@ -167,108 +174,6 @@ const NON_CALLEES = new Set([
|
|
|
167
174
|
'await', 'new', 'do', 'else', 'in', 'of', 'delete', 'void', 'yield', 'import',
|
|
168
175
|
]);
|
|
169
176
|
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
// Source masking — string/template/comment/regex interiors become spaces
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Blank out the INTERIOR of every string literal, template literal, comment and
|
|
176
|
-
* regex literal, preserving byte offsets and newlines. Delimiters are kept so
|
|
177
|
-
* paren/comma balance and identifier boundaries survive.
|
|
178
|
-
*
|
|
179
|
-
* This is what separates CODE from DATA. A test that embeds fixture source in a
|
|
180
|
-
* template literal (this check's own test file does) must not be scanned as
|
|
181
|
-
* though that fixture were its own body — masking makes that structural instead
|
|
182
|
-
* of relying on a self-exemption marker.
|
|
183
|
-
*
|
|
184
|
-
* @param {string} text
|
|
185
|
-
* @returns {string} same-length masked source
|
|
186
|
-
*/
|
|
187
|
-
export function maskSource(text) {
|
|
188
|
-
const out = text.split('');
|
|
189
|
-
const n = out.length;
|
|
190
|
-
let i = 0;
|
|
191
|
-
// Last significant (non-space) char before `i`, used for the regex/division
|
|
192
|
-
// disambiguation below.
|
|
193
|
-
let prev = '';
|
|
194
|
-
|
|
195
|
-
const blank = (from, to) => {
|
|
196
|
-
for (let k = from; k < to && k < n; k++) if (out[k] !== '\n') out[k] = ' ';
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
while (i < n) {
|
|
200
|
-
const c = text[i];
|
|
201
|
-
const next = text[i + 1];
|
|
202
|
-
|
|
203
|
-
// Line comment.
|
|
204
|
-
if (c === '/' && next === '/') {
|
|
205
|
-
let j = i;
|
|
206
|
-
while (j < n && text[j] !== '\n') j++;
|
|
207
|
-
blank(i, j);
|
|
208
|
-
i = j;
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
// Block comment.
|
|
212
|
-
if (c === '/' && next === '*') {
|
|
213
|
-
let j = i + 2;
|
|
214
|
-
while (j < n && !(text[j] === '*' && text[j + 1] === '/')) j++;
|
|
215
|
-
blank(i, Math.min(j + 2, n));
|
|
216
|
-
i = Math.min(j + 2, n);
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
// Single/double-quoted string — keep the quotes, blank the interior.
|
|
220
|
-
if (c === "'" || c === '"') {
|
|
221
|
-
let j = i + 1;
|
|
222
|
-
while (j < n && text[j] !== c) {
|
|
223
|
-
if (text[j] === '\\') j++;
|
|
224
|
-
if (text[j] === '\n') break; // unterminated — bail at EOL
|
|
225
|
-
j++;
|
|
226
|
-
}
|
|
227
|
-
blank(i + 1, j);
|
|
228
|
-
i = Math.min(j + 1, n);
|
|
229
|
-
prev = c;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
// Template literal — blank everything including `${…}` substitutions.
|
|
233
|
-
if (c === '`') {
|
|
234
|
-
let j = i + 1;
|
|
235
|
-
while (j < n && text[j] !== '`') {
|
|
236
|
-
if (text[j] === '\\') j++;
|
|
237
|
-
j++;
|
|
238
|
-
}
|
|
239
|
-
blank(i + 1, j);
|
|
240
|
-
i = Math.min(j + 1, n);
|
|
241
|
-
prev = c;
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
// Regex literal — only when the previous significant char cannot end an
|
|
245
|
-
// expression, AND a closing unescaped `/` exists on the SAME line. The
|
|
246
|
-
// same-line bound keeps a misread division from swallowing the file.
|
|
247
|
-
if (c === '/' && (prev === '' || '(,=:[!&|?{};+-*%~^<>'.includes(prev))) {
|
|
248
|
-
let j = i + 1;
|
|
249
|
-
let inClass = false;
|
|
250
|
-
let closed = -1;
|
|
251
|
-
while (j < n && text[j] !== '\n') {
|
|
252
|
-
if (text[j] === '\\') { j += 2; continue; }
|
|
253
|
-
if (text[j] === '[') inClass = true;
|
|
254
|
-
else if (text[j] === ']') inClass = false;
|
|
255
|
-
else if (text[j] === '/' && !inClass) { closed = j; break; }
|
|
256
|
-
j++;
|
|
257
|
-
}
|
|
258
|
-
if (closed > i) {
|
|
259
|
-
blank(i + 1, closed);
|
|
260
|
-
i = closed + 1;
|
|
261
|
-
prev = '/';
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!/\s/.test(c)) prev = c;
|
|
267
|
-
i++;
|
|
268
|
-
}
|
|
269
|
-
return out.join('');
|
|
270
|
-
}
|
|
271
|
-
|
|
272
177
|
// ---------------------------------------------------------------------------
|
|
273
178
|
// Tiny source scanners (offset-aligned against the masked text)
|
|
274
179
|
// ---------------------------------------------------------------------------
|
|
@@ -938,7 +843,7 @@ export function runCheckUntrackedTestDeps(repoRoot) {
|
|
|
938
843
|
return findings.length > 0 ? 1 : 0;
|
|
939
844
|
}
|
|
940
845
|
|
|
941
|
-
const isMain =
|
|
846
|
+
const isMain =isMainModule(import.meta.url);
|
|
942
847
|
if (isMain) {
|
|
943
848
|
const root = process.argv[2];
|
|
944
849
|
if (!root) {
|
|
@@ -53,6 +53,30 @@
|
|
|
53
53
|
* a rule that only cites live instances loses its explanation the moment it works.
|
|
54
54
|
* If you need a CURRENT S2 hit, run the checker; do not assume this one.
|
|
55
55
|
*
|
|
56
|
+
* S6 `parser-only-config-key` — the key IS read, but the ONLY file that
|
|
57
|
+
* reads it is its own block parser
|
|
58
|
+
* `scripts/lib/config/<root>.mjs`.
|
|
59
|
+
*
|
|
60
|
+
* S6 is the mirror image of S2, and it exists because S1 counts the PARSER as a
|
|
61
|
+
* consumer: `CONSUMER_DIRS` is `scripts/` + `hooks/`, and every block parser
|
|
62
|
+
* lives under `scripts/lib/config/`. A key its parser resolves and nothing else
|
|
63
|
+
* reads is `compact-nudge` wearing a value object. Measured 2026-09-22 on this
|
|
64
|
+
* tree: `reaper.max-hook-latency-ms` and `reaper.false-alarm-window` both passed
|
|
65
|
+
* S1 on `scripts/lib/config/reaper.mjs` alone while `scripts/lib/orphan-reaper.mjs`
|
|
66
|
+
* used same-named HARDCODED defaults and never saw the parsed value.
|
|
67
|
+
*
|
|
68
|
+
* Why the signal is this narrow — one read file, and it is the key's OWN parser.
|
|
69
|
+
* The blunt rule ("the parser layer never counts") was measured first and
|
|
70
|
+
* REJECTED: it flips 102 of 263 declared keys to unwired, because the ordinary
|
|
71
|
+
* shape here is a consumer that reads the camelCase FIELD (`cfg.driftCheck.mode`)
|
|
72
|
+
* while this census matches the kebab TOKEN. 102 of 263 is the broken instrument
|
|
73
|
+
* `.claude/rules/host-resources.md` HR-101 forbids. The narrow rule reports 32 —
|
|
74
|
+
* a backlog, printed as an aggregate like S4 and enumerated under `--list`.
|
|
75
|
+
*
|
|
76
|
+
* S6 carries no camelCase fallback ON PURPOSE: adding one re-hides both reaper
|
|
77
|
+
* keys, whose camelCase names exist in `orphan-reaper.mjs` as that module's own
|
|
78
|
+
* defaults. A name is not a read.
|
|
79
|
+
*
|
|
56
80
|
* S2 applies to TOP-LEVEL keys only — a nested key reaches code through its
|
|
57
81
|
* parent — and its premise is structural: every Session Config key has to pass
|
|
58
82
|
* through the parser layer to become a value. Measured 2026-08-08: 84 of 89
|
|
@@ -265,9 +289,9 @@
|
|
|
265
289
|
|
|
266
290
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
267
291
|
import path from 'node:path';
|
|
268
|
-
import { pathToFileURL } from 'node:url';
|
|
269
292
|
|
|
270
293
|
import { sizingSubject } from '../learnings/sizing-subject.mjs';
|
|
294
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
271
295
|
|
|
272
296
|
/** Documented config surface — every `yaml` fence in this file is a declaration. */
|
|
273
297
|
const TEMPLATE_REL = 'docs/session-config-template.md';
|
|
@@ -393,6 +417,29 @@ const PARSER_PATHS = Object.freeze([
|
|
|
393
417
|
path.join('scripts', 'parse-config.mjs'),
|
|
394
418
|
]);
|
|
395
419
|
|
|
420
|
+
/**
|
|
421
|
+
* The per-block parser directory: `scripts/lib/config/<block>.mjs` turns ONE
|
|
422
|
+
* Session Config block's YAML into a value object and does nothing else.
|
|
423
|
+
*
|
|
424
|
+
* Signal S6 below is about this directory specifically, which is why it is not
|
|
425
|
+
* folded into {@link PARSER_PATHS}.
|
|
426
|
+
*/
|
|
427
|
+
const BLOCK_PARSER_DIR = path.join('scripts', 'lib', 'config');
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* The key's OWN block parser, by convention `scripts/lib/config/<root>.mjs`.
|
|
431
|
+
*
|
|
432
|
+
* Deliberately an exact name match and not "anything under the parser dir": a
|
|
433
|
+
* sibling block's parser reading the key is still a second file, and S6 only
|
|
434
|
+
* fires when there is exactly ONE reader and it is the key's own.
|
|
435
|
+
*
|
|
436
|
+
* @param {string} root top-level key of the block
|
|
437
|
+
* @returns {string} plugin-root-relative path
|
|
438
|
+
*/
|
|
439
|
+
function ownBlockParserPath(root) {
|
|
440
|
+
return path.join(BLOCK_PARSER_DIR, `${root}.mjs`);
|
|
441
|
+
}
|
|
442
|
+
|
|
396
443
|
/**
|
|
397
444
|
* Declared-but-unread keys accepted on purpose. Key = full dotted path (S1/S2)
|
|
398
445
|
* or module path relative to the plugin root (S4), value = REASON naming the
|
|
@@ -420,7 +467,8 @@ const ALLOWLIST = Object.freeze({
|
|
|
420
467
|
|
|
421
468
|
/**
|
|
422
469
|
* @typedef {{
|
|
423
|
-
* kind: 'unwired-config-key' | 'parser-orphan-config-key' | '
|
|
470
|
+
* kind: 'unwired-config-key' | 'parser-orphan-config-key' | 'parser-only-config-key'
|
|
471
|
+
* | 'allowlist-missing-reason'
|
|
424
472
|
* | 'allowlist-stale' | 'orphaned-prose-module' | 'unreachable-library-module'
|
|
425
473
|
* | 'coordinator-invoked-module' | 'hand-keyed-learning-subject'
|
|
426
474
|
* | 'tool-error',
|
|
@@ -652,19 +700,57 @@ export function collectExportedSymbols(body) {
|
|
|
652
700
|
return [...names];
|
|
653
701
|
}
|
|
654
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Re-export targets of a barrel: the specifiers of its `export * from '…'`
|
|
705
|
+
* (and `export * as ns from '…'`) lines.
|
|
706
|
+
*
|
|
707
|
+
* `collectExportedSymbols` cannot see these — a star re-export names no symbol —
|
|
708
|
+
* so a PURE barrel reads as `exports: []`. That does not merely hide the barrel:
|
|
709
|
+
* the S4 population predicate below requires at least one export, so the barrel
|
|
710
|
+
* never enters `unreachable` at all, and is therefore unavailable as the CLUSTER
|
|
711
|
+
* ROOT of the module it re-exports. The target is then reported as its own root.
|
|
712
|
+
*
|
|
713
|
+
* Measured 2026-09-16 on the live tree: `scripts/lib/worktree.mjs` is exactly
|
|
714
|
+
* `export * from './worktree/index.mjs';`, its only importers (`workspace.mjs`,
|
|
715
|
+
* `worktree-freshness.mjs`) are themselves unreachable — yet S4 reported
|
|
716
|
+
* `scripts/lib/worktree/index.mjs`, an interior file, instead of the cluster.
|
|
717
|
+
*
|
|
718
|
+
* Only the POPULATION is affected. Edge propagation already works: the BFS walks
|
|
719
|
+
* `mentions`, and a star re-export line is not a comment, so a barrel that IS
|
|
720
|
+
* reachable already marks its target reachable (measured the same day:
|
|
721
|
+
* `worktree.mjs` carries `index.mjs` in `mentions`).
|
|
722
|
+
*
|
|
723
|
+
* Anchored at column 0 like the export-symbol grammar above, so a docblock line
|
|
724
|
+
* (`* export * from …`) and an indented string never count.
|
|
725
|
+
*
|
|
726
|
+
* @param {string} body module source
|
|
727
|
+
* @returns {string[]} re-exported module specifiers, as written
|
|
728
|
+
*/
|
|
729
|
+
export function collectReExportTargets(body) {
|
|
730
|
+
return [
|
|
731
|
+
...body.matchAll(/^export\s+\*(?:\s+as\s+[A-Za-z0-9_$]+)?\s+from\s+['"]([^'"]+)['"]/gm),
|
|
732
|
+
].map((match) => match[1]);
|
|
733
|
+
}
|
|
734
|
+
|
|
655
735
|
/**
|
|
656
736
|
* Whether a module is a CLI entrypoint rather than a library.
|
|
657
737
|
*
|
|
658
738
|
* An entrypoint is invoked by path (npm script, hook wiring, CI job), so having
|
|
659
739
|
* no importer is its normal state and says nothing about being wired.
|
|
660
740
|
*
|
|
741
|
+
* `isMainModule(` is part of the grammar because the #1371 sweep replaced ~50
|
|
742
|
+
* hand-written `process.argv[1] === import.meta.url` guards with the shared
|
|
743
|
+
* `scripts/lib/is-main-module.mjs` predicate. Without this alternative a swept
|
|
744
|
+
* CLI matches none of the other branches and silently reclassifies as a library
|
|
745
|
+
* candidate, so S4 reports it as an `unreachable-library-module` root.
|
|
746
|
+
*
|
|
661
747
|
* @param {string} body module source
|
|
662
748
|
* @returns {boolean}
|
|
663
749
|
*/
|
|
664
750
|
export function isCliEntrypoint(body) {
|
|
665
751
|
return (
|
|
666
752
|
body.startsWith('#!') ||
|
|
667
|
-
/import\.meta\.url\s*===|require\.main\s*===\s*module|process\.argv\[1\]/.test(body)
|
|
753
|
+
/import\.meta\.url\s*===|require\.main\s*===\s*module|process\.argv\[1\]|isMainModule\(/.test(body)
|
|
668
754
|
);
|
|
669
755
|
}
|
|
670
756
|
|
|
@@ -845,7 +931,9 @@ function mentionedModuleTokens(lines) {
|
|
|
845
931
|
* switched off. Revisit if a real module-resolver (import-specifier resolution
|
|
846
932
|
* relative to the importing file) becomes cheap, or if a collided basename is
|
|
847
933
|
* ever confirmed to mask a true positive. The `coordinator-invoked-module`
|
|
848
|
-
* DOWNGRADE is exempt, and since #1293 so is the CLUSTER-ROOT filter
|
|
934
|
+
* DOWNGRADE is exempt, and since #1293 so is the CLUSTER-ROOT filter (since
|
|
935
|
+
* #1298 its predicate also drives the "drags N" count, which had collapsed
|
|
936
|
+
* two dragged `index.mjs` into one): in both
|
|
849
937
|
* a colliding basename must be named with its `dirname/base` suffix, because
|
|
850
938
|
* those matches move a module OUT of the reportable class and would otherwise
|
|
851
939
|
* hide a true unreachable sibling. Measured cost of leaving the root filter
|
|
@@ -890,6 +978,10 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
890
978
|
edgeOnly,
|
|
891
979
|
entrypoint: isCliEntrypoint(body),
|
|
892
980
|
exports: collectExportedSymbols(body),
|
|
981
|
+
// Star re-exports name no symbol, so `exports` is empty for a pure
|
|
982
|
+
// barrel — see collectReExportTargets for why that silently promoted an
|
|
983
|
+
// interior module to a root.
|
|
984
|
+
reExports: collectReExportTargets(body),
|
|
893
985
|
// This file contributes NO edges — the S4 counterpart of the SELF_REL
|
|
894
986
|
// exclusion the S1/S2 corpus already applies, and for the identical
|
|
895
987
|
// reason. Every S4 `ALLOWLIST` key is a module path written here as a
|
|
@@ -947,8 +1039,15 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
947
1039
|
}
|
|
948
1040
|
}
|
|
949
1041
|
|
|
1042
|
+
// A module with NO public surface at all is not a finding — nothing can be
|
|
1043
|
+
// wired to it. A pure `export * from` barrel HAS a surface (everything its
|
|
1044
|
+
// target exports), so it belongs in the population: otherwise it cannot be
|
|
1045
|
+
// the cluster root of the module it re-exports (collectReExportTargets).
|
|
950
1046
|
const unreachable = modules.filter(
|
|
951
|
-
(module) =>
|
|
1047
|
+
(module) =>
|
|
1048
|
+
!reachable.has(module.relative) &&
|
|
1049
|
+
!module.entrypoint &&
|
|
1050
|
+
(module.exports.length > 0 || module.reExports.length > 0),
|
|
952
1051
|
);
|
|
953
1052
|
const unreachableSet = new Set(unreachable.map((module) => module.relative));
|
|
954
1053
|
// Basename census, shared by the root filter below and the downgrade half
|
|
@@ -965,23 +1064,69 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
965
1064
|
const basenameCount = new Map();
|
|
966
1065
|
for (const module of modules) basenameCount.set(module.base, (basenameCount.get(module.base) ?? 0) + 1);
|
|
967
1066
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1067
|
+
// One `dirname/base` matcher per unreachable module whose basename collides,
|
|
1068
|
+
// built once: `references` below runs per (mentioner, target) pair.
|
|
1069
|
+
/** @type {Map<string, RegExp>} */
|
|
1070
|
+
const qualifiedMatchers = new Map();
|
|
1071
|
+
for (const module of unreachable) {
|
|
1072
|
+
if ((basenameCount.get(module.base) ?? 0) < 2) continue;
|
|
1073
|
+
qualifiedMatchers.set(module.relative, tokenMatcher(module.relative.split(path.sep).slice(-2).join('/')));
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Does `mentioner` reference the unreachable module `target`? A bare-basename
|
|
1077
|
+
* mention only counts when the basename is UNIQUE (see the census above).
|
|
1078
|
+
* When it collides, the mentioning module must name the `dirname/base` form
|
|
1079
|
+
* in its body — otherwise ONE unreachable module mentioning bare `index.mjs`
|
|
1080
|
+
* references EVERY differently-pathed `index.mjs` at once (#1293:
|
|
1081
|
+
* `locks/index.mjs` + `worktree/index.mjs` were masked as roots by a third
|
|
1082
|
+
* unreachable module until that module was deleted for an unrelated reason).
|
|
1083
|
+
*
|
|
1084
|
+
* The ONE predicate for both consumers — the cluster-root filter and the drag
|
|
1085
|
+
* count of the finding message — so the two cannot disagree about who
|
|
1086
|
+
* references whom. They did (#1298): the drag count compared bare basenames,
|
|
1087
|
+
* so a root importing `a/index.mjs` AND `b/index.mjs` read "drags 1".
|
|
1088
|
+
*
|
|
1089
|
+
* @param {(typeof modules)[number]} mentioner
|
|
1090
|
+
* @param {(typeof modules)[number]} target an `unreachable` module
|
|
1091
|
+
* @returns {boolean}
|
|
1092
|
+
*/
|
|
1093
|
+
const references = (mentioner, target) => {
|
|
1094
|
+
if (!mentioner.mentions.has(target.base)) return false;
|
|
1095
|
+
const qualifiedRe = qualifiedMatchers.get(target.relative);
|
|
1096
|
+
return qualifiedRe === undefined || qualifiedRe.test(mentioner.rawBody);
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
const roots = unreachable.filter(
|
|
1100
|
+
(module) => !unreachable.some((other) => other.relative !== module.relative && references(other, module)),
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
// A pure `export *` barrel enters the population above so it can HEAD its own
|
|
1104
|
+
// cluster, but reporting it needs one more condition. S3 already exempts this
|
|
1105
|
+
// exact shape by name (`autopilot-telemetry.mjs`, condition 4): a star
|
|
1106
|
+
// re-export has zero NAMED symbols, so every "does prose name one of its
|
|
1107
|
+
// exports?" test is vacuously FALSE — including S4's own coordinator-invoked
|
|
1108
|
+
// downgrade below, which iterates `module.exports`. A reported pure barrel
|
|
1109
|
+
// could therefore never be downgraded, only allowlisted: a finding with no
|
|
1110
|
+
// legitimate exit. The category split is the barrel's TARGET. A barrel over a
|
|
1111
|
+
// module that is itself unreachable heads a genuinely dead cluster and is
|
|
1112
|
+
// reported; a barrel over LIVE code is a backward-compat shim, which is a
|
|
1113
|
+
// different (and much weaker) finding than "nothing can reach this feature".
|
|
1114
|
+
// Measured 2026-09-16: without this split `scripts/lib/autopilot-telemetry.mjs`
|
|
1115
|
+
// — `export * from './autopilot/telemetry.mjs'`, target reachable, sole
|
|
1116
|
+
// importer a test — became a new permanent WARN.
|
|
1117
|
+
// Ceiling: a dead shim over live code is now invisible to S4 as well as to S3.
|
|
1118
|
+
// Revisit if a stale back-compat shim is ever confirmed to have outlived its
|
|
1119
|
+
// last importer unnoticed — that wants its own check, not a looser S4.
|
|
1120
|
+
const reportableRoots = roots.filter((module) => {
|
|
1121
|
+
if (module.exports.length > 0) return true;
|
|
1122
|
+
return module.reExports.some((spec) => {
|
|
1123
|
+
if (!spec.startsWith('.')) {
|
|
1124
|
+
// Aliased/bare specifier: fall back to the basename granularity this
|
|
1125
|
+
// file already documents as its named residual.
|
|
1126
|
+
return [...unreachableSet].some((rel) => path.basename(rel) === path.basename(spec));
|
|
1127
|
+
}
|
|
1128
|
+
return unreachableSet.has(path.normalize(path.join(path.dirname(module.relative), spec)));
|
|
1129
|
+
});
|
|
985
1130
|
});
|
|
986
1131
|
|
|
987
1132
|
// Category split (see § Category split in the doc block above): an INSTRUCTION
|
|
@@ -997,7 +1142,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
997
1142
|
.map((file) => ({ relative: path.relative(pluginRoot, file), body: readFileSync(file, 'utf8') }));
|
|
998
1143
|
|
|
999
1144
|
let coordinatorInvoked = 0;
|
|
1000
|
-
const findings =
|
|
1145
|
+
const findings = reportableRoots.map((module) => {
|
|
1001
1146
|
// Docs write POSIX separators regardless of host; `path.relative` does not.
|
|
1002
1147
|
const relativePosix = module.relative.split(path.sep).join('/');
|
|
1003
1148
|
const qualified = relativePosix.split('/').slice(-2).join('/');
|
|
@@ -1028,15 +1173,22 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
1028
1173
|
'architecture. Re-check only if that instruction is ever removed',
|
|
1029
1174
|
});
|
|
1030
1175
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
)
|
|
1176
|
+
// Counted per MODULE through the root filter's own predicate, never per
|
|
1177
|
+
// basename token: `mentions` is a Set of basenames, so two dragged
|
|
1178
|
+
// `index.mjs` files would collapse into one (#1298).
|
|
1179
|
+
const dragged = unreachable.filter((other) => other.relative !== module.relative && references(module, other));
|
|
1034
1180
|
const tail = dragged.length > 0 ? `, and drags ${dragged.length} further unreachable module(s)` : '';
|
|
1181
|
+
// A pure barrel reports its re-export surface; naming "0 symbol(s)" there
|
|
1182
|
+
// would read as a checker bug rather than as the barrel it is.
|
|
1183
|
+
const surface =
|
|
1184
|
+
module.exports.length > 0
|
|
1185
|
+
? `exports ${module.exports.length} symbol(s) (${module.exports.slice(0, 3).join(', ')})`
|
|
1186
|
+
: `re-exports ${module.reExports.length} module(s) (${module.reExports.slice(0, 3).join(', ')})`;
|
|
1035
1187
|
return /** @type {Finding} */ ({
|
|
1036
1188
|
kind: 'unreachable-library-module',
|
|
1037
1189
|
key: module.relative,
|
|
1038
1190
|
message:
|
|
1039
|
-
|
|
1191
|
+
`${surface} but no hook, ` +
|
|
1040
1192
|
`npm script, CI job or husky stage reaches it — transitively${tail}. No instruction surface names ` +
|
|
1041
1193
|
'one of its exports either: wire it, delete it, or allowlist it with a reason',
|
|
1042
1194
|
});
|
|
@@ -1046,7 +1198,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
1046
1198
|
findings,
|
|
1047
1199
|
scanned: {
|
|
1048
1200
|
modules: modules.length,
|
|
1049
|
-
roots:
|
|
1201
|
+
roots: reportableRoots.length,
|
|
1050
1202
|
unreachable: unreachable.length,
|
|
1051
1203
|
coordinatorInvoked,
|
|
1052
1204
|
},
|
|
@@ -1175,6 +1327,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
1175
1327
|
orphanedModules: 0,
|
|
1176
1328
|
unreachableModules: 0,
|
|
1177
1329
|
coordinatorInvokedModules: 0,
|
|
1330
|
+
parserOnly: 0,
|
|
1178
1331
|
handKeyedSubjects: 0,
|
|
1179
1332
|
judgedSubjects: 0,
|
|
1180
1333
|
},
|
|
@@ -1234,7 +1387,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
1234
1387
|
|
|
1235
1388
|
for (const key of [...declared.keys.keys()].sort()) {
|
|
1236
1389
|
const meta = /** @type {DeclaredKey} */ (declared.keys.get(key));
|
|
1237
|
-
const { code, comment } = countReadSites(meta, corpus);
|
|
1390
|
+
const { code, comment, files: readFiles } = countReadSites(meta, corpus);
|
|
1238
1391
|
|
|
1239
1392
|
/** @type {Finding | null} */
|
|
1240
1393
|
let issue = null;
|
|
@@ -1247,6 +1400,21 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
1247
1400
|
`declared in ${meta.sources.join(' + ')} but no .mjs under ${CONSUMER_DIRS.join('/ or ')}/ ` +
|
|
1248
1401
|
`reads it${commentNote} — wire it, delete it, or allowlist it with a reason`,
|
|
1249
1402
|
};
|
|
1403
|
+
} else if (readFiles.length === 1 && readFiles[0] === ownBlockParserPath(meta.root)) {
|
|
1404
|
+
// S6 — the ONLY file that reads this key is the parser that produced it.
|
|
1405
|
+
// A parser is not a consumer: it turns YAML into a field nobody then
|
|
1406
|
+
// reads, which is `compact-nudge` wearing a value object. Measured
|
|
1407
|
+
// 2026-09-22: `reaper.max-hook-latency-ms` and `reaper.false-alarm-window`
|
|
1408
|
+
// both passed S1 on the strength of `scripts/lib/config/reaper.mjs`
|
|
1409
|
+
// alone, while `orphan-reaper.mjs` used its OWN hardcoded defaults of the
|
|
1410
|
+
// same name and never received the parsed value.
|
|
1411
|
+
issue = {
|
|
1412
|
+
kind: 'parser-only-config-key',
|
|
1413
|
+
key,
|
|
1414
|
+
message:
|
|
1415
|
+
`read only by its own block parser (${readFiles[0]}) — a parser is not a consumer; ` +
|
|
1416
|
+
'nothing outside the config layer reads the parsed value',
|
|
1417
|
+
};
|
|
1250
1418
|
} else if (key === meta.root && !tokenMatcher(key).test(parserBody)) {
|
|
1251
1419
|
issue = {
|
|
1252
1420
|
kind: 'parser-orphan-config-key',
|
|
@@ -1272,7 +1440,8 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
1272
1440
|
continue;
|
|
1273
1441
|
}
|
|
1274
1442
|
|
|
1275
|
-
result.summary.
|
|
1443
|
+
if (issue.kind === 'parser-only-config-key') result.summary.parserOnly += 1;
|
|
1444
|
+
else result.summary.unwired += 1;
|
|
1276
1445
|
findings.push(issue);
|
|
1277
1446
|
}
|
|
1278
1447
|
|
|
@@ -1342,6 +1511,7 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1342
1511
|
consumerFiles,
|
|
1343
1512
|
unwired,
|
|
1344
1513
|
allowlisted,
|
|
1514
|
+
parserOnly,
|
|
1345
1515
|
orphanedModules,
|
|
1346
1516
|
unreachableModules,
|
|
1347
1517
|
coordinatorInvokedModules,
|
|
@@ -1361,8 +1531,16 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1361
1531
|
// architecture — an instruction surface tells an LLM to call the module.
|
|
1362
1532
|
// Measured 2026-09-07: 46 of the 52 findings the single S4 class carried.
|
|
1363
1533
|
// Printing 46 WARN lines for the design is the broken instrument HR-101 forbids.
|
|
1364
|
-
|
|
1534
|
+
//
|
|
1535
|
+
// `parser-only-config-key` (S6) is deferred on the S4 terms too: 32 findings
|
|
1536
|
+
// on the live tree is a backlog to work down, not a per-run alarm.
|
|
1537
|
+
const DEFERRED = Object.freeze([
|
|
1538
|
+
'unreachable-library-module',
|
|
1539
|
+
'coordinator-invoked-module',
|
|
1540
|
+
'parser-only-config-key',
|
|
1541
|
+
]);
|
|
1365
1542
|
const s4 = inspection.findings.filter((item) => item.kind === 'unreachable-library-module');
|
|
1543
|
+
const s6 = inspection.findings.filter((item) => item.kind === 'parser-only-config-key');
|
|
1366
1544
|
for (const item of inspection.findings) {
|
|
1367
1545
|
if (!list && DEFERRED.includes(item.kind)) continue;
|
|
1368
1546
|
console.log(` WARN: [${item.kind}] ${item.key} — ${item.message}`);
|
|
@@ -1381,10 +1559,19 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1381
1559
|
);
|
|
1382
1560
|
}
|
|
1383
1561
|
|
|
1562
|
+
if (!list && s6.length > 0) {
|
|
1563
|
+
console.log(
|
|
1564
|
+
` WARN: [parser-only-config-key] ${s6.length} declared key(s) whose only reader is their own ` +
|
|
1565
|
+
`block parser — e.g. ${s6.slice(0, 3).map((item) => item.key).join(', ')}. ` +
|
|
1566
|
+
'Re-run with --list for the full census.',
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1384
1570
|
console.log(
|
|
1385
1571
|
` PASS: censused ${declaredKeys} declared key(s) from ${inspection.sourcesScanned.join(' + ') || '(no source)'} ` +
|
|
1386
1572
|
`against ${consumerFiles} consumer file(s) — ${unwired} unwired, ${allowlisted} allowlisted, ` +
|
|
1387
|
-
`${
|
|
1573
|
+
`${parserOnly} parser-only, ${orphanedModules} prose-orphaned module(s), ` +
|
|
1574
|
+
`${unreachableModules} unreachable module(s), ` +
|
|
1388
1575
|
`${coordinatorInvokedModules} coordinator-invoked module(s), ${handKeyedSubjects} hand-keyed ` +
|
|
1389
1576
|
`learning subject(s) of ${judgedSubjects} judged`,
|
|
1390
1577
|
);
|
|
@@ -1393,7 +1580,7 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1393
1580
|
return 0;
|
|
1394
1581
|
}
|
|
1395
1582
|
|
|
1396
|
-
const isMain = import.meta.url
|
|
1583
|
+
const isMain = isMainModule(import.meta.url);
|
|
1397
1584
|
if (isMain) {
|
|
1398
1585
|
const args = process.argv.slice(2);
|
|
1399
1586
|
const pluginRoot = args.find((arg) => !arg.startsWith('-'));
|
|
@@ -51,15 +51,21 @@
|
|
|
51
51
|
* skill body) and nowhere in the three RUN surfaces above still reports
|
|
52
52
|
* UNREGISTERED — being documented is not being run. REVISIT if a fourth run
|
|
53
53
|
* surface (a new CI job file, a different git hook) is ever added: extend
|
|
54
|
-
* `RUN_SURFACES`, do not special-case it here.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
54
|
+
* `RUN_SURFACES`, do not special-case it here.
|
|
55
|
+
*
|
|
56
|
+
* THE "NEVER A FALSE UNREGISTERED" CLAIM WAS FALSE (#1388, measured
|
|
57
|
+
* 2026-09-18 @ 20a4cbff). This header used to argue that the hand-rolled
|
|
58
|
+
* stripper could only ever MISS a comment, never manufacture a false
|
|
59
|
+
* UNREGISTERED. It had no regex-literal branch, so
|
|
60
|
+
* `const re = /\/*$/;` in `scripts/validate-plugin.mjs` opened a BLOCK
|
|
61
|
+
* COMMENT at the regex's `/*` and swallowed every `runCheck(...)` line below
|
|
62
|
+
* it — precisely a false UNREGISTERED, for every checker registered after
|
|
63
|
+
* that point. The `.mjs` surface is therefore lexed by the shared
|
|
64
|
+
* `maskSource` (`scripts/lib/js-mask.mjs`), which recognises regex literals;
|
|
65
|
+
* only the `#`-comment shell/YAML surfaces (`.husky/pre-commit`,
|
|
66
|
+
* `.gitlab-ci.yml`) still use the small local walker, whose flat
|
|
67
|
+
* quote-tracking ceiling (no `\"` escape handling, no `${...}` walk) is
|
|
68
|
+
* real but bounded to those two files.
|
|
63
69
|
*
|
|
64
70
|
* Usage: check-validator-registration.mjs <repo-root>
|
|
65
71
|
* Output: ` PASS: …` / ` FAIL: …` lines (two leading spaces), then
|
|
@@ -72,8 +78,9 @@
|
|
|
72
78
|
|
|
73
79
|
import { existsSync, readFileSync } from 'node:fs';
|
|
74
80
|
import path from 'node:path';
|
|
75
|
-
import { pathToFileURL } from 'node:url';
|
|
76
81
|
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
82
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
83
|
+
import { maskSource } from '../js-mask.mjs';
|
|
77
84
|
|
|
78
85
|
/** Marker line inside a checker's own header — declares deliberate CLI-only status. */
|
|
79
86
|
export const STANDALONE_MARKER = /^\s*\/\/\s*registration:\s*standalone\b(?:\s+(.*))?$/m;
|
|
@@ -113,11 +120,28 @@ function commentStyleForSurface(rel) {
|
|
|
113
120
|
* truncating the line early. See the header NAMED CEILING for what this
|
|
114
121
|
* quote-tracking deliberately does not attempt.
|
|
115
122
|
*
|
|
123
|
+
* The JS style (`//` + `/* *\/`) is delegated to the shared lexer
|
|
124
|
+
* {@link maskSource} in `keepLiterals` mode — it is the only one of the three
|
|
125
|
+
* RUN_SURFACES that can contain a REGEX LITERAL, and a stripper without a
|
|
126
|
+
* regex branch reads `/\/*$/` as a block-comment opener and swallows every
|
|
127
|
+
* registration below it (#1388, see the header). `maskSource` blanks comment
|
|
128
|
+
* bytes to SPACES rather than deleting them, which is strictly safer for the
|
|
129
|
+
* substring match below: deletion could splice two fragments into a basename
|
|
130
|
+
* that was never written. The `#` style stays local — `maskSource` models
|
|
131
|
+
* JavaScript, not sh/YAML.
|
|
132
|
+
*
|
|
116
133
|
* @param {string} text
|
|
117
134
|
* @param {{lineComment: string, blockComment: boolean, quoteChars: string[]}} style
|
|
118
|
-
* @returns {string}
|
|
135
|
+
* @returns {string} TWO comment models behind one signature, and they differ in
|
|
136
|
+
* shape: the JS style (`//` + `/* *\/`, delegated to {@link maskSource})
|
|
137
|
+
* returns a string of the SAME LENGTH as `text`, with comment bytes blanked to
|
|
138
|
+
* spaces; the `#` style (sh/YAML, the local walker) returns a SHORTER string,
|
|
139
|
+
* with the comment text deleted to end-of-line (the newline itself is kept).
|
|
140
|
+
* Only the substring match below is common to both — do not rely on offsets
|
|
141
|
+
* surviving the `#` style.
|
|
119
142
|
*/
|
|
120
143
|
export function stripComments(text, { lineComment, blockComment, quoteChars }) {
|
|
144
|
+
if (lineComment === '//' && blockComment) return maskSource(text, { keepLiterals: true });
|
|
121
145
|
let out = '';
|
|
122
146
|
let i = 0;
|
|
123
147
|
let inQuote = null;
|
|
@@ -243,7 +267,7 @@ export function runCheckValidatorRegistration(repoRoot) {
|
|
|
243
267
|
return fail > 0 ? 1 : 0;
|
|
244
268
|
}
|
|
245
269
|
|
|
246
|
-
const isMain =
|
|
270
|
+
const isMain =isMainModule(import.meta.url);
|
|
247
271
|
if (isMain) {
|
|
248
272
|
const root = process.argv[2];
|
|
249
273
|
if (!root) {
|
|
@@ -193,8 +193,8 @@
|
|
|
193
193
|
|
|
194
194
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
195
195
|
import path from 'node:path';
|
|
196
|
-
import { pathToFileURL } from 'node:url';
|
|
197
196
|
import { SHELL_LANGS, forEachLine } from './markdown-fences.mjs';
|
|
197
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
198
198
|
|
|
199
199
|
/** Directories whose content is scanned. Root-level `*.md` is added separately. */
|
|
200
200
|
const SCAN_DIRS = Object.freeze([
|
|
@@ -906,7 +906,7 @@ export function runCheckVcsRepoFlag(pluginRoot) {
|
|
|
906
906
|
return 0;
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
-
const isMain =
|
|
909
|
+
const isMain =isMainModule(import.meta.url);
|
|
910
910
|
if (isMain) {
|
|
911
911
|
const argv = process.argv.slice(2);
|
|
912
912
|
const flags = new Set(argv.filter((a) => a.startsWith('--')));
|