session-orchestrator 3.24.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +30 -0
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1488 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +60 -14
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +88 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/host-paths.mjs +20 -4
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +30 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +51 -11
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +164 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/SKILL.md +10 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -140,6 +140,31 @@
|
|
|
140
140
|
* census behind `--list` — see `runCheckUnwiredFeatures`. The `findings` array
|
|
141
141
|
* always carries every finding, so no programmatic consumer loses data.
|
|
142
142
|
*
|
|
143
|
+
* ### S4 category split, measured 2026-09-07 (#1239)
|
|
144
|
+
*
|
|
145
|
+
* The single S4 class had grown into a broken instrument. Live run at that date
|
|
146
|
+
* (`node scripts/lib/validate/check-unwired-features.mjs . --list`) reported 53
|
|
147
|
+
* findings: S1=0, S2=0, S3=1, S4=52. Classifying the 52 by hand:
|
|
148
|
+
*
|
|
149
|
+
* - **46 (88.5%)** were named by an INSTRUCTION surface (`skills/`, `commands/`,
|
|
150
|
+
* `agents/`, `.claude/rules/`) that ALSO named at least one of the module's
|
|
151
|
+
* exported symbols — i.e. an LLM is told to call it. That is this plugin's
|
|
152
|
+
* architecture, not a defect, and a class firing on 88.5% of its own
|
|
153
|
+
* population is what `.claude/rules/host-resources.md` § HR-101 forbids.
|
|
154
|
+
* They are now `coordinator-invoked-module`, an ADVISORY kind: aggregated
|
|
155
|
+
* into one CLI line, never a per-module WARN, and it does not change the
|
|
156
|
+
* exit code (which was already 0 — see § Mode below).
|
|
157
|
+
* - **1** was a corpus gap: `scripts/lib/vault-sync-baseline.mjs` is statically
|
|
158
|
+
* imported by `skills/vault-sync/validator.mjs:71`, but `skills/**` was not an
|
|
159
|
+
* edge source. Fixed by `S4_EDGE_DIRS` — code under `skills/` is code.
|
|
160
|
+
* - **5** were true positives and remain `unreachable-library-module`.
|
|
161
|
+
*
|
|
162
|
+
* After the split, on the same tree: 5 unreachable, 46 coordinator-invoked, exit
|
|
163
|
+
* 0 unchanged. Per `.claude/rules/development.md` § Guard & Threshold Design this
|
|
164
|
+
* is a category separation, never a raised threshold — nothing is suppressed,
|
|
165
|
+
* both classes stay in `findings`, and either half collapsing to zero is itself
|
|
166
|
+
* pinned by a test.
|
|
167
|
+
*
|
|
143
168
|
* ## Consumer scope, and why "prose-only" is a finding rather than an error
|
|
144
169
|
*
|
|
145
170
|
* Read sites are counted in `scripts/**` and `hooks/**` (`.mjs`/`.js`/`.cjs`),
|
|
@@ -213,6 +238,35 @@ const INSTRUCTION_FILES = Object.freeze(['CLAUDE.md', 'AGENTS.md']);
|
|
|
213
238
|
/** Directories whose code counts as a runtime consumer. */
|
|
214
239
|
const CONSUMER_DIRS = Object.freeze(['scripts', 'hooks']);
|
|
215
240
|
|
|
241
|
+
/**
|
|
242
|
+
* S4-only EDGE sources: directories whose `.mjs` files are real code with real
|
|
243
|
+
* static imports, but which are not themselves S4 candidates.
|
|
244
|
+
*
|
|
245
|
+
* `skills/**\/*.mjs` is the measured instance. `skills/vault-sync/validator.mjs:71`
|
|
246
|
+
* statically imports `scripts/lib/vault-sync-baseline.mjs`, yet before 2026-09-07
|
|
247
|
+
* `skills/` was not walked at all, so that import was invisible and the imported
|
|
248
|
+
* module was reported unreachable — a CORPUS GAP, not a defect in the module.
|
|
249
|
+
*
|
|
250
|
+
* They are edge sources only: their own reachability is not judged here (a skill
|
|
251
|
+
* body invokes them by path, which is the same design boundary CLI entrypoints
|
|
252
|
+
* get), so they seed the walk and never appear in a finding. This does NOT make
|
|
253
|
+
* `skills/` a prose surface for S4 — markdown under `skills/` still names, never
|
|
254
|
+
* calls.
|
|
255
|
+
*/
|
|
256
|
+
const S4_EDGE_DIRS = Object.freeze(['skills']);
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* INSTRUCTION surfaces: the directories whose markdown addresses an LLM that
|
|
260
|
+
* will act on it. Used only to split the S4 census (see
|
|
261
|
+
* `collectUnreachableLibraryModules` § Category split).
|
|
262
|
+
*/
|
|
263
|
+
const INSTRUCTION_DIRS = Object.freeze([
|
|
264
|
+
'skills',
|
|
265
|
+
'commands',
|
|
266
|
+
'agents',
|
|
267
|
+
path.join('.claude', 'rules'),
|
|
268
|
+
]);
|
|
269
|
+
|
|
216
270
|
/** Extensions that can hold a runtime read site. */
|
|
217
271
|
const CODE_EXTENSIONS = Object.freeze(['.mjs', '.js', '.cjs']);
|
|
218
272
|
|
|
@@ -302,8 +356,6 @@ const ALLOWLIST = Object.freeze({
|
|
|
302
356
|
'prose-only consumer — skills/wave-executor/wave-loop.md gates the per-wave commit step on this key; the commit itself is a coordinator action, not a script',
|
|
303
357
|
'instruction-budget':
|
|
304
358
|
'dedicated reader outside the parser layer — scripts/lib/instruction-budget-guard.mjs parses this block itself (S2 exemption only; S1 evidence is real)',
|
|
305
|
-
webhooks:
|
|
306
|
-
'dedicated reader outside the parser layer — scripts/lib/webhook-url.mjs resolves these URLs env-first (S2 exemption only; S1 evidence is real)',
|
|
307
359
|
});
|
|
308
360
|
|
|
309
361
|
/**
|
|
@@ -319,6 +371,7 @@ const ALLOWLIST = Object.freeze({
|
|
|
319
371
|
* @typedef {{
|
|
320
372
|
* kind: 'unwired-config-key' | 'parser-orphan-config-key' | 'allowlist-missing-reason'
|
|
321
373
|
* | 'allowlist-stale' | 'orphaned-prose-module' | 'unreachable-library-module'
|
|
374
|
+
* | 'coordinator-invoked-module'
|
|
322
375
|
* | 'tool-error',
|
|
323
376
|
* key: string,
|
|
324
377
|
* message: string,
|
|
@@ -740,7 +793,10 @@ function mentionedModuleTokens(lines) {
|
|
|
740
793
|
* one, which is the right direction for a check whose failure mode is being
|
|
741
794
|
* switched off. Revisit if a real module-resolver (import-specifier resolution
|
|
742
795
|
* relative to the importing file) becomes cheap, or if a collided basename is
|
|
743
|
-
* ever confirmed to mask a true positive.
|
|
796
|
+
* ever confirmed to mask a true positive. The `coordinator-invoked-module`
|
|
797
|
+
* DOWNGRADE is exempt: there a colliding basename must be named with its
|
|
798
|
+
* `dirname/base` suffix, because that match moves a module OUT of the
|
|
799
|
+
* reportable class and would otherwise hide a true unreachable sibling.
|
|
744
800
|
* - **Reachable ≠ executed.** A module imported by a hook that never takes that
|
|
745
801
|
* branch reads as wired here. Proving execution needs coverage data, not a graph.
|
|
746
802
|
* - **Reachable from SOME entrypoint is not reachable from the PROMISED one.**
|
|
@@ -757,14 +813,21 @@ function mentionedModuleTokens(lines) {
|
|
|
757
813
|
* @returns {{findings: Finding[], scanned: {modules: number, roots: number, unreachable: number}}}
|
|
758
814
|
*/
|
|
759
815
|
export function collectUnreachableLibraryModules(pluginRoot) {
|
|
760
|
-
const
|
|
761
|
-
|
|
816
|
+
const candidates = CONSUMER_DIRS.flatMap((dir) => walkCode(path.join(pluginRoot, dir)))
|
|
817
|
+
.sort()
|
|
818
|
+
.map((file) => ({ file, edgeOnly: false }));
|
|
819
|
+
// Edge-only sources contribute imports without being judged (see S4_EDGE_DIRS).
|
|
820
|
+
const edges = S4_EDGE_DIRS.flatMap((dir) => walkCode(path.join(pluginRoot, dir)))
|
|
821
|
+
.sort()
|
|
822
|
+
.map((file) => ({ file, edgeOnly: true }));
|
|
823
|
+
const modules = [...candidates, ...edges].map(({ file, edgeOnly }) => {
|
|
762
824
|
const body = readFileSync(file, 'utf8');
|
|
763
825
|
const lines = body.split('\n');
|
|
764
826
|
const relative = path.relative(pluginRoot, file);
|
|
765
827
|
return {
|
|
766
828
|
relative,
|
|
767
829
|
base: path.basename(file),
|
|
830
|
+
edgeOnly,
|
|
768
831
|
entrypoint: isCliEntrypoint(body),
|
|
769
832
|
exports: collectExportedSymbols(body),
|
|
770
833
|
// This file contributes NO edges — the S4 counterpart of the SELF_REL
|
|
@@ -793,7 +856,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
793
856
|
/** @type {string[]} */
|
|
794
857
|
const stack = [];
|
|
795
858
|
for (const module of modules) {
|
|
796
|
-
if (!module.entrypoint && !wiringTokens.has(module.base)) continue;
|
|
859
|
+
if (!module.edgeOnly && !module.entrypoint && !wiringTokens.has(module.base)) continue;
|
|
797
860
|
reachable.add(module.relative);
|
|
798
861
|
stack.push(module.relative);
|
|
799
862
|
}
|
|
@@ -818,7 +881,63 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
818
881
|
!unreachable.some((other) => other.relative !== module.relative && other.mentions.has(module.base)),
|
|
819
882
|
);
|
|
820
883
|
|
|
884
|
+
// Category split (see § Category split in the doc block above): an INSTRUCTION
|
|
885
|
+
// document that names both the module AND one of its exported symbols is an
|
|
886
|
+
// order addressed to a reader who will execute it — the same grammar
|
|
887
|
+
// discriminator S3 condition 5 uses, applied here to separate the plugin's
|
|
888
|
+
// architecture from the defect. Prose corpus is instruction surfaces only.
|
|
889
|
+
const instructionDocs = INSTRUCTION_DIRS.flatMap((dir) =>
|
|
890
|
+
walkCode(path.join(pluginRoot, dir), [], PROSE_EXTENSIONS, PROSE_EXCLUDED_DIRS),
|
|
891
|
+
)
|
|
892
|
+
.filter((file) => !PROSE_EXCLUDED_FILES.includes(path.basename(file)))
|
|
893
|
+
.sort()
|
|
894
|
+
.map((file) => ({ relative: path.relative(pluginRoot, file), body: readFileSync(file, 'utf8') }));
|
|
895
|
+
|
|
896
|
+
// Basename census for the downgrade half. A bare basename is only a valid
|
|
897
|
+
// module reference when it is UNIQUE in the corpus: `writer.mjs` names both
|
|
898
|
+
// `peer-cards/writer.mjs` and `reconcile/writer.mjs` (measured 2026-09-07),
|
|
899
|
+
// so a doc naming ONE of them would otherwise downgrade BOTH out of the
|
|
900
|
+
// reportable class — a true unreachable silently moved into the advisory
|
|
901
|
+
// half. For a colliding basename the doc must therefore carry at least the
|
|
902
|
+
// `dirname/base` suffix (`reconcile/writer.mjs`); unique basenames keep the
|
|
903
|
+
// cheaper bare match. Direction matters: this can only ever ADD findings back
|
|
904
|
+
// to the reportable class, never remove one.
|
|
905
|
+
/** @type {Map<string, number>} */
|
|
906
|
+
const basenameCount = new Map();
|
|
907
|
+
for (const module of modules) basenameCount.set(module.base, (basenameCount.get(module.base) ?? 0) + 1);
|
|
908
|
+
|
|
909
|
+
let coordinatorInvoked = 0;
|
|
821
910
|
const findings = roots.map((module) => {
|
|
911
|
+
// Docs write POSIX separators regardless of host; `path.relative` does not.
|
|
912
|
+
const relativePosix = module.relative.split(path.sep).join('/');
|
|
913
|
+
const qualified = relativePosix.split('/').slice(-2).join('/');
|
|
914
|
+
const ambiguous = (basenameCount.get(module.base) ?? 0) > 1;
|
|
915
|
+
// Whole-token match, not substring: `body.includes('writer.mjs')` also fires
|
|
916
|
+
// inside `config-writer.mjs`, which downgrades a genuinely unreachable
|
|
917
|
+
// module into the advisory class on a doc that never named it. `tokenMatcher`
|
|
918
|
+
// is the same boundary the export half already uses (it rejects
|
|
919
|
+
// `[A-Za-z0-9_$-]` on either side), applied to the module reference.
|
|
920
|
+
const nameRe = tokenMatcher(ambiguous ? qualified : module.base);
|
|
921
|
+
const namesThisModule = (/** @type {string} */ body) => nameRe.test(body);
|
|
922
|
+
const invokers = instructionDocs.filter(
|
|
923
|
+
(doc) =>
|
|
924
|
+
namesThisModule(doc.body) &&
|
|
925
|
+
module.exports.some((symbol) => tokenMatcher(symbol).test(doc.body)),
|
|
926
|
+
);
|
|
927
|
+
if (invokers.length > 0) {
|
|
928
|
+
coordinatorInvoked += 1;
|
|
929
|
+
return /** @type {Finding} */ ({
|
|
930
|
+
kind: 'coordinator-invoked-module',
|
|
931
|
+
key: module.relative,
|
|
932
|
+
message:
|
|
933
|
+
`no hook, npm script, CI job or husky stage reaches it, but ${invokers
|
|
934
|
+
.slice(0, 2)
|
|
935
|
+
.map((doc) => doc.relative)
|
|
936
|
+
.join(' + ')} instructs a coordinator to call ` +
|
|
937
|
+
`${module.exports.slice(0, 3).join(', ')} — advisory: LLM-dispatch IS this plugin's ` +
|
|
938
|
+
'architecture. Re-check only if that instruction is ever removed',
|
|
939
|
+
});
|
|
940
|
+
}
|
|
822
941
|
const dragged = [...module.mentions].filter(
|
|
823
942
|
(token) => token !== module.base && [...unreachableSet].some((rel) => path.basename(rel) === token),
|
|
824
943
|
);
|
|
@@ -828,14 +947,19 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
828
947
|
key: module.relative,
|
|
829
948
|
message:
|
|
830
949
|
`exports ${module.exports.length} symbol(s) (${module.exports.slice(0, 3).join(', ')}) but no hook, ` +
|
|
831
|
-
`npm script, CI job or husky stage reaches it — transitively${tail}.
|
|
832
|
-
'
|
|
950
|
+
`npm script, CI job or husky stage reaches it — transitively${tail}. No instruction surface names ` +
|
|
951
|
+
'one of its exports either: wire it, delete it, or allowlist it with a reason',
|
|
833
952
|
});
|
|
834
953
|
});
|
|
835
954
|
|
|
836
955
|
return {
|
|
837
956
|
findings,
|
|
838
|
-
scanned: {
|
|
957
|
+
scanned: {
|
|
958
|
+
modules: modules.length,
|
|
959
|
+
roots: roots.length,
|
|
960
|
+
unreachable: unreachable.length,
|
|
961
|
+
coordinatorInvoked,
|
|
962
|
+
},
|
|
839
963
|
};
|
|
840
964
|
}
|
|
841
965
|
|
|
@@ -846,7 +970,8 @@ export function collectUnreachableLibraryModules(pluginRoot) {
|
|
|
846
970
|
* @returns {{
|
|
847
971
|
* ok: boolean,
|
|
848
972
|
* summary: {declaredKeys: number, consumerFiles: number, unwired: number, allowlisted: number,
|
|
849
|
-
* orphanedModules: number
|
|
973
|
+
* orphanedModules: number, unreachableModules: number,
|
|
974
|
+
* coordinatorInvokedModules: number},
|
|
850
975
|
* sourcesScanned: string[],
|
|
851
976
|
* findings: Finding[],
|
|
852
977
|
* toolError: boolean,
|
|
@@ -864,6 +989,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
864
989
|
allowlisted: 0,
|
|
865
990
|
orphanedModules: 0,
|
|
866
991
|
unreachableModules: 0,
|
|
992
|
+
coordinatorInvokedModules: 0,
|
|
867
993
|
},
|
|
868
994
|
/** @type {string[]} */
|
|
869
995
|
sourcesScanned: [],
|
|
@@ -974,7 +1100,8 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
974
1100
|
flagged.add(finding.key);
|
|
975
1101
|
continue;
|
|
976
1102
|
}
|
|
977
|
-
result.summary.
|
|
1103
|
+
if (finding.kind === 'coordinator-invoked-module') result.summary.coordinatorInvokedModules += 1;
|
|
1104
|
+
else result.summary.unreachableModules += 1;
|
|
978
1105
|
findings.push(finding);
|
|
979
1106
|
}
|
|
980
1107
|
|
|
@@ -1013,8 +1140,15 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1013
1140
|
return 2;
|
|
1014
1141
|
}
|
|
1015
1142
|
|
|
1016
|
-
const {
|
|
1017
|
-
|
|
1143
|
+
const {
|
|
1144
|
+
declaredKeys,
|
|
1145
|
+
consumerFiles,
|
|
1146
|
+
unwired,
|
|
1147
|
+
allowlisted,
|
|
1148
|
+
orphanedModules,
|
|
1149
|
+
unreachableModules,
|
|
1150
|
+
coordinatorInvokedModules,
|
|
1151
|
+
} = inspection.summary;
|
|
1018
1152
|
|
|
1019
1153
|
// S4 is a BACKLOG, not a per-run alarm: 50 findings on the live tree against
|
|
1020
1154
|
// 1-2 WARN lines from every sibling check. Printing all 50 every run is the
|
|
@@ -1022,11 +1156,24 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1022
1156
|
// file's own header names. So the default carries the NUMBER (which ratchets,
|
|
1023
1157
|
// and which a reviewer can compare run to run) plus the first few paths; the
|
|
1024
1158
|
// full census is one `--list` away. Nothing is suppressed — only deferred.
|
|
1159
|
+
//
|
|
1160
|
+
// `coordinator-invoked-module` is deferred on the SAME terms and for a stronger
|
|
1161
|
+
// reason: it is not a backlog but an ADVISORY class describing this plugin's
|
|
1162
|
+
// architecture — an instruction surface tells an LLM to call the module.
|
|
1163
|
+
// Measured 2026-09-07: 46 of the 52 findings the single S4 class carried.
|
|
1164
|
+
// Printing 46 WARN lines for the design is the broken instrument HR-101 forbids.
|
|
1165
|
+
const DEFERRED = Object.freeze(['unreachable-library-module', 'coordinator-invoked-module']);
|
|
1025
1166
|
const s4 = inspection.findings.filter((item) => item.kind === 'unreachable-library-module');
|
|
1026
1167
|
for (const item of inspection.findings) {
|
|
1027
|
-
if (!list && item.kind
|
|
1168
|
+
if (!list && DEFERRED.includes(item.kind)) continue;
|
|
1028
1169
|
console.log(` WARN: [${item.kind}] ${item.key} — ${item.message}`);
|
|
1029
1170
|
}
|
|
1171
|
+
// No aggregate WARN for the advisory class: it describes this plugin's
|
|
1172
|
+
// architecture and therefore fires on every run with no action attached — the
|
|
1173
|
+
// 100%-firing instrument `.claude/rules/host-resources.md` HR-101 forbids,
|
|
1174
|
+
// which only trains the operator to skim past the sibling WARNs that DO act.
|
|
1175
|
+
// The PASS line below still carries its count (it ratchets, run to run), and
|
|
1176
|
+
// `--list` still prints the per-module census.
|
|
1030
1177
|
if (!list && s4.length > 0) {
|
|
1031
1178
|
console.log(
|
|
1032
1179
|
` WARN: [unreachable-library-module] ${s4.length} library module(s) that no hook, npm script, ` +
|
|
@@ -1038,7 +1185,8 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
|
1038
1185
|
console.log(
|
|
1039
1186
|
` PASS: censused ${declaredKeys} declared key(s) from ${inspection.sourcesScanned.join(' + ') || '(no source)'} ` +
|
|
1040
1187
|
`against ${consumerFiles} consumer file(s) — ${unwired} unwired, ${allowlisted} allowlisted, ` +
|
|
1041
|
-
`${orphanedModules} prose-orphaned module(s), ${unreachableModules} unreachable module(s)
|
|
1188
|
+
`${orphanedModules} prose-orphaned module(s), ${unreachableModules} unreachable module(s), ` +
|
|
1189
|
+
`${coordinatorInvokedModules} coordinator-invoked module(s)`,
|
|
1042
1190
|
);
|
|
1043
1191
|
console.log('');
|
|
1044
1192
|
console.log('Results: 1 passed, 0 failed');
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
import { existsSync, readFileSync } from 'node:fs';
|
|
74
74
|
import path from 'node:path';
|
|
75
75
|
import { pathToFileURL } from 'node:url';
|
|
76
|
-
import {
|
|
76
|
+
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
77
77
|
|
|
78
78
|
/** Marker line inside a checker's own header — declares deliberate CLI-only status. */
|
|
79
79
|
export const STANDALONE_MARKER = /^\s*\/\/\s*registration:\s*standalone\b(?:\s+(.*))?$/m;
|
|
@@ -165,9 +165,15 @@ export function stripComments(text, { lineComment, blockComment, quoteChars }) {
|
|
|
165
165
|
* @returns {RegistrationResult[]} sorted by basename
|
|
166
166
|
*/
|
|
167
167
|
export function scanValidatorRegistration(repoRoot) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
// "Exists in this directory, tracked or not" (#1248) — a checker written but
|
|
169
|
+
// not yet staged is precisely the one most likely to be unregistered, and the
|
|
170
|
+
// bare git index cannot see it. `enumerateRepoFiles` still honours
|
|
171
|
+
// `.gitignore`, so no ignored tree enters the census (#1143).
|
|
172
|
+
const checkerFiles = enumerateRepoFiles({
|
|
173
|
+
repoRoot,
|
|
174
|
+
dirs: [VALIDATE_DIR_REL],
|
|
175
|
+
exts: ['mjs'],
|
|
176
|
+
}).filter((f) => /^check-.*\.mjs$/.test(path.basename(f)));
|
|
171
177
|
|
|
172
178
|
// Comment-stripped before matching (HIGH, #1184 FX-C): a basename
|
|
173
179
|
// referenced only inside a `//`/`#`/`/* *\/` comment is NOT a real
|
|
@@ -15,12 +15,33 @@
|
|
|
15
15
|
* names and REDACTS any match from its output (a CP11 hit printed verbatim to the
|
|
16
16
|
* public CI log would be a WORSE leak than the one being guarded).
|
|
17
17
|
*
|
|
18
|
-
* Contract:
|
|
19
|
-
* loadConfidentialNames({ namesPath, deps? })
|
|
18
|
+
* Contract (#1250 + #1264 — TWO entry points; the discriminated one is ADDITIVE):
|
|
19
|
+
* loadConfidentialNames({ namesPath, deps? }) → string[] | null (4.0.0 shape)
|
|
20
|
+
* inspectConfidentialNames({ namesPath, deps? }) → { status, names } (discriminated)
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* status ∈ 'ok' | 'empty' | 'all-dropped' | 'missing' | 'malformed' | 'unconfigured'
|
|
23
|
+
* names is the validated list for 'ok', and `[]` for every other status.
|
|
24
|
+
*
|
|
25
|
+
* `loadConfidentialNames` is the 4.0.0 PUBLIC contract and is preserved verbatim
|
|
26
|
+
* (`string[] | null`, null-collapsing): `package.json` carries no `exports` map,
|
|
27
|
+
* so a consumer repo can deep-import this module and a PATCH release must not
|
|
28
|
+
* break it. It is a thin wrapper over `inspectConfidentialNames`, which reports
|
|
29
|
+
* the class the older shape collapsed into `null` — exactly the distinction
|
|
30
|
+
* CP11's fail-closed verdict turns on: 'unconfigured' and 'empty' are operator
|
|
31
|
+
* choices (inactive, PASS), while 'missing', 'malformed' and 'all-dropped' mean
|
|
32
|
+
* a configured guard could not run (fail closed). The scanner had to re-read and
|
|
33
|
+
* re-classify the file to recover a class the loader already knew; it no longer does.
|
|
34
|
+
*
|
|
35
|
+
* - `namesPath` empty/whitespace/non-string → 'unconfigured' (SILENT — this is
|
|
36
|
+
* the default for the ~99% of hosts without a list).
|
|
37
|
+
* - file missing → 'missing' + one stderr WARN.
|
|
38
|
+
* - unreadable / malformed-JSON / non-array → 'malformed' + one stderr WARN.
|
|
39
|
+
* - readable, well-formed, parsed array of length 0 → 'empty'. The operator
|
|
40
|
+
* deliberately wrote `[]` to switch CP11 off; that is a silent PASS.
|
|
41
|
+
* - readable, well-formed, parsed array NON-empty but every entry dropped by
|
|
42
|
+
* validation → 'all-dropped'. Distinct from 'empty' on purpose: the operator
|
|
43
|
+
* INTENDED names here, so a guard that ends up with zero patterns must fail
|
|
44
|
+
* closed rather than pass silently (W4 finding F3).
|
|
24
45
|
* - Each entry is validated: it must be a non-empty string within a length cap
|
|
25
46
|
* (MAX_NAME_LENGTH — a ReDoS/DoS guard against a manipulated host-local file;
|
|
26
47
|
* a real customer/repo name never exceeds it). Entries failing either check
|
|
@@ -29,11 +50,16 @@
|
|
|
29
50
|
* - Result is CACHED per process, keyed by namesPath (the scanner reads it once).
|
|
30
51
|
*
|
|
31
52
|
* Privacy: this module never writes the list anywhere; it only reads the operator's
|
|
32
|
-
* host-local file. WARN messages carry the
|
|
33
|
-
*
|
|
53
|
+
* host-local file. WARN messages carry NEITHER the confidential names NOR the file
|
|
54
|
+
* PATH — only `basename(namesPath)` (W4 finding F1). The full path is host-local
|
|
55
|
+
* (`/Users/<name>/…`), and these WARNs fire on exactly the branches the scanner turns
|
|
56
|
+
* into a `FAIL` + exit 1 — output an operator pastes into a PUBLIC CI log, where the
|
|
57
|
+
* path would leak the very shape CP1 exists to block. The scanner's own
|
|
58
|
+
* `disabledReason` strings have always been path-free; the loader now matches them.
|
|
34
59
|
*/
|
|
35
60
|
|
|
36
61
|
import { readFileSync, existsSync } from 'node:fs';
|
|
62
|
+
import { basename } from 'node:path';
|
|
37
63
|
|
|
38
64
|
/**
|
|
39
65
|
* Max characters for a single confidential name. A real customer / repo name is
|
|
@@ -43,7 +69,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
43
69
|
*/
|
|
44
70
|
const MAX_NAME_LENGTH = 256;
|
|
45
71
|
|
|
46
|
-
/** Per-process cache: namesPath → (
|
|
72
|
+
/** Per-process cache: namesPath → `{ status, names }` (shared by BOTH entry points). */
|
|
47
73
|
const _cache = new Map();
|
|
48
74
|
|
|
49
75
|
/**
|
|
@@ -63,14 +89,18 @@ const DEFAULT_DEPS = {
|
|
|
63
89
|
|
|
64
90
|
/**
|
|
65
91
|
* Parse + validate the raw JSON body into a list of confidential names.
|
|
66
|
-
*
|
|
92
|
+
*
|
|
93
|
+
* Distinguishes THREE zero-name outcomes, because the caller's verdict differs
|
|
94
|
+
* between them: 'malformed' (unparseable or not an array), 'empty' (a parsed array
|
|
95
|
+
* of length 0 — the operator deliberately switched CP11 off) and 'all-dropped'
|
|
96
|
+
* (the operator DID list entries, and validation rejected every one of them).
|
|
67
97
|
*
|
|
68
98
|
* @param {string} raw
|
|
69
|
-
* @param {string}
|
|
99
|
+
* @param {string} label - basename of the names file, for WARN text (never the path)
|
|
70
100
|
* @param {{ warn: (msg: string) => void }} d
|
|
71
|
-
* @returns {string[]
|
|
101
|
+
* @returns {{ status: 'ok'|'empty'|'all-dropped'|'malformed', names: string[] }}
|
|
72
102
|
*/
|
|
73
|
-
function parseNames(raw,
|
|
103
|
+
function parseNames(raw, label, d) {
|
|
74
104
|
let parsed;
|
|
75
105
|
try {
|
|
76
106
|
parsed = JSON.parse(raw);
|
|
@@ -78,19 +108,20 @@ function parseNames(raw, namesPath, d) {
|
|
|
78
108
|
// Fix 3 (security-reviewer): NEVER embed err.message — V8's JSON.parse error
|
|
79
109
|
// text echoes the first ~10 chars of the file body, which for a confidential-
|
|
80
110
|
// names file is a would-be confidential-name prefix. Log only the error CLASS
|
|
81
|
-
// (err.name, e.g. SyntaxError) + the
|
|
82
|
-
// ("WARN messages … NEVER
|
|
111
|
+
// (err.name, e.g. SyntaxError) + the file BASENAME (never the host-local path,
|
|
112
|
+
// W4 finding F1). Keeps the module-docstring invariant ("WARN messages … NEVER
|
|
113
|
+
// the confidential names") true.
|
|
83
114
|
d.warn(
|
|
84
|
-
`WARN validate/confidential-names: malformed JSON in ${
|
|
115
|
+
`WARN validate/confidential-names: malformed JSON in ${label} (${err.name}); CP11 inactive\n`,
|
|
85
116
|
);
|
|
86
|
-
return
|
|
117
|
+
return { status: 'malformed', names: [] };
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
if (!Array.isArray(parsed)) {
|
|
90
121
|
d.warn(
|
|
91
|
-
`WARN validate/confidential-names: ${
|
|
122
|
+
`WARN validate/confidential-names: ${label} must be a JSON array of strings; ignoring the list (CP11 inactive)\n`,
|
|
92
123
|
);
|
|
93
|
-
return
|
|
124
|
+
return { status: 'malformed', names: [] };
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
const names = [];
|
|
@@ -115,55 +146,89 @@ function parseNames(raw, namesPath, d) {
|
|
|
115
146
|
// Deliberately omit the offending entries — logging them would leak the very
|
|
116
147
|
// confidential names the list exists to keep host-local. COUNTS only.
|
|
117
148
|
d.warn(
|
|
118
|
-
`WARN validate/confidential-names: ignored ${ignoredInvalid} invalid and ${ignoredOversized} oversized (>${MAX_NAME_LENGTH} chars) name entr(ies) in ${
|
|
149
|
+
`WARN validate/confidential-names: ignored ${ignoredInvalid} invalid and ${ignoredOversized} oversized (>${MAX_NAME_LENGTH} chars) name entr(ies) in ${label}\n`,
|
|
119
150
|
);
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
|
|
153
|
+
if (names.length > 0) return { status: 'ok', names };
|
|
154
|
+
// F3 (W4 panel, fail-open): a file whose entries were ALL dropped by validation
|
|
155
|
+
// is NOT the operator's `[]` opt-out — they listed names and meant them to bind.
|
|
156
|
+
// Collapsing both into 'empty' made the scanner treat a corrupted list as a
|
|
157
|
+
// deliberate opt-out and PASS silently with CP11 inactive.
|
|
158
|
+
return parsed.length > 0
|
|
159
|
+
? { status: 'all-dropped', names: [] }
|
|
160
|
+
: { status: 'empty', names: [] };
|
|
123
161
|
}
|
|
124
162
|
|
|
125
163
|
/**
|
|
126
|
-
* Load and validate the host-local confidential-names list
|
|
164
|
+
* Load and validate the host-local confidential-names list, reporting WHY the list
|
|
165
|
+
* is unusable when it is. Defensive — never throws.
|
|
127
166
|
*
|
|
128
167
|
* @param {object} opts
|
|
129
168
|
* @param {string|null|undefined} opts.namesPath - absolute path to the names JSON, or
|
|
130
169
|
* empty/absent when no list is configured.
|
|
131
170
|
* @param {Partial<typeof DEFAULT_DEPS>} [opts.deps] - injected fs / warn (tests).
|
|
132
|
-
* @returns {
|
|
171
|
+
* @returns {{ status: 'ok'|'empty'|'all-dropped'|'missing'|'malformed'|'unconfigured', names: string[] }}
|
|
172
|
+
* the validated names under `status: 'ok'`; `names` is `[]` for every other status.
|
|
133
173
|
*/
|
|
134
|
-
export function
|
|
174
|
+
export function inspectConfidentialNames({ namesPath, deps = {} } = {}) {
|
|
135
175
|
const d = { ...DEFAULT_DEPS, ...deps };
|
|
136
176
|
|
|
137
177
|
// Unconfigured → no list, no noise. This is the normal case for public repos
|
|
138
178
|
// and for any host that has not opted into confidential-name scanning.
|
|
139
179
|
if (typeof namesPath !== 'string' || namesPath.trim() === '') {
|
|
140
|
-
return
|
|
180
|
+
return { status: 'unconfigured', names: [] };
|
|
141
181
|
}
|
|
142
182
|
|
|
143
183
|
if (_cache.has(namesPath)) {
|
|
144
184
|
return _cache.get(namesPath);
|
|
145
185
|
}
|
|
146
186
|
|
|
147
|
-
|
|
187
|
+
// Default when the file is missing; the read/parse branches below overwrite it.
|
|
188
|
+
let result = { status: 'missing', names: [] };
|
|
148
189
|
try {
|
|
149
190
|
if (!d.existsSync(namesPath)) {
|
|
150
191
|
d.warn(
|
|
151
|
-
`WARN validate/confidential-names: confidential-names-file is set but the file does not exist: ${namesPath}; CP11 inactive\n`,
|
|
192
|
+
`WARN validate/confidential-names: confidential-names-file is set but the file does not exist: ${basename(namesPath)}; CP11 inactive\n`,
|
|
152
193
|
);
|
|
153
194
|
} else {
|
|
154
195
|
const raw = d.readFileSync(namesPath, 'utf8');
|
|
155
|
-
result = parseNames(raw, namesPath, d);
|
|
196
|
+
result = parseNames(raw, basename(namesPath), d);
|
|
156
197
|
}
|
|
157
198
|
} catch (err) {
|
|
158
199
|
// Fix 3 (security-reviewer): log the error CLASS, not err.message. A filesystem
|
|
159
200
|
// error rarely embeds file content, but keeping the invariant uniform ("the WARN
|
|
160
|
-
// carries only counts / err-class + the
|
|
161
|
-
// err.message sink in this module.
|
|
201
|
+
// carries only counts / err-class + the file basename, never the path and never
|
|
202
|
+
// file body") removes the last err.message sink in this module.
|
|
162
203
|
d.warn(
|
|
163
|
-
`WARN validate/confidential-names: failed to read confidential-names file
|
|
204
|
+
`WARN validate/confidential-names: failed to read confidential-names file ${basename(namesPath)} (${err.name}); CP11 inactive\n`,
|
|
164
205
|
);
|
|
206
|
+
// An unreadable file is NOT 'missing' — existsSync said it is there. It shares
|
|
207
|
+
// the 'malformed' verdict (configured but unusable → the caller fails closed).
|
|
208
|
+
result = { status: 'malformed', names: [] };
|
|
165
209
|
}
|
|
166
210
|
|
|
167
211
|
_cache.set(namesPath, result);
|
|
168
212
|
return result;
|
|
169
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The 4.0.0 PUBLIC contract, preserved verbatim: the validated names, or `null`
|
|
217
|
+
* whenever no usable list could be loaded (unconfigured, missing, malformed,
|
|
218
|
+
* empty, all-dropped alike). `package.json` has no `exports` map, so a consumer
|
|
219
|
+
* repo may deep-import this function; a PATCH release must not change its shape.
|
|
220
|
+
*
|
|
221
|
+
* In-tree callers that need to distinguish an operator OPT-OUT from a guard that
|
|
222
|
+
* FAILED TO RUN must use `inspectConfidentialNames` instead — that distinction is
|
|
223
|
+
* precisely what this return type cannot express.
|
|
224
|
+
*
|
|
225
|
+
* @param {object} opts
|
|
226
|
+
* @param {string|null|undefined} opts.namesPath
|
|
227
|
+
* @param {Partial<typeof DEFAULT_DEPS>} [opts.deps]
|
|
228
|
+
* @returns {string[] | null}
|
|
229
|
+
*/
|
|
230
|
+
export function loadConfidentialNames({ namesPath, deps = {} } = {}) {
|
|
231
|
+
// Shares the one cache entry: inspect() keys it, this derives from the result.
|
|
232
|
+
const { names } = inspectConfidentialNames({ namesPath, deps });
|
|
233
|
+
return names.length > 0 ? names : null;
|
|
234
|
+
}
|