session-orchestrator 5.2.0 → 5.4.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 +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -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 +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- 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 +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- 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 +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- 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 +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- 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 +5 -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 +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -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 +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/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 +5 -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/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -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 +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- 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-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -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 +202 -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 +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- 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 +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- 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/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- 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 +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- 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/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- 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/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- 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/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- 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 +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- 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 +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- 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-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- 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/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Check: no instruction prose tells an agent to write `learnings.jsonl` with a
|
|
4
|
+
* shell redirect (GitLab #1446 / GitHub #69). BLOCKING.
|
|
5
|
+
*
|
|
6
|
+
* ## Why
|
|
7
|
+
*
|
|
8
|
+
* The store is gitignored, so a bad write has no VCS restore. The sanctioned
|
|
9
|
+
* write paths — `scripts/apply-session-learnings.mjs` (session-end, retro) and
|
|
10
|
+
* `scripts/sweep-expired-learnings.mjs --prune` (`/evolve`) — validate every
|
|
11
|
+
* record, snapshot `.bak-<ISO>`, and archive what leaves the store. A shell
|
|
12
|
+
* `>` / `>>` does none of that. The prose producers this change closed, all
|
|
13
|
+
* measured at ebf63bd1:
|
|
14
|
+
*
|
|
15
|
+
* - `skills/session-end/learning-patterns.md` Phase 3.6 step 4g ("atomic
|
|
16
|
+
* rewrite with `>`") — the path that wrote a file PATH into `scope` (GH#69);
|
|
17
|
+
* - `skills/plan/mode-retro.md` § 3.3 — an unnamed "Append new patterns to
|
|
18
|
+
* `learnings.jsonl`" with no mechanism at all;
|
|
19
|
+
* - `.cursor/rules/060-evolve.mdc` — three sites ("Write entire result back
|
|
20
|
+
* with `>`", "write all with `>`" twice). None of the three names
|
|
21
|
+
* `learnings.jsonl` on its own line, which is why R2 is windowed.
|
|
22
|
+
*
|
|
23
|
+
* ## Rules
|
|
24
|
+
*
|
|
25
|
+
* R1 shell redirect — `>`/`>>` directly in front of a `…learnings.jsonl`
|
|
26
|
+
* target on the same line. The leading boundary (start,
|
|
27
|
+
* whitespace, `|`, `;`, `&`, `(`) is load-bearing:
|
|
28
|
+
* without it 9 of 12 hits were `<state-dir>/…`
|
|
29
|
+
* placeholders. A Markdown blockquote marker (`> ` at
|
|
30
|
+
* line start, outside a fence) is not a redirect and is
|
|
31
|
+
* stripped first; inside a fence it IS a truncation.
|
|
32
|
+
* R3 tee — `tee [-a] …learnings.jsonl` on the same line.
|
|
33
|
+
* R2 prose operator — a backticked `` `>` `` / `` `>>` `` within ±3 lines
|
|
34
|
+
* (WINDOW) of an anchor: `learnings.jsonl`, "the store",
|
|
35
|
+
* "learnings store", "result back", "atomic rewrite".
|
|
36
|
+
* The last two are the retired producers' own wording.
|
|
37
|
+
* An operator inside an ASCII double-quoted span is a
|
|
38
|
+
* quotation of former prose, not an instruction.
|
|
39
|
+
*
|
|
40
|
+
* Exempt (per operator): a prohibition keyword (NEVER / do not / FORBIDDEN /
|
|
41
|
+
* verboten …) BEFORE the operator in the same sentence — a keyword after it
|
|
42
|
+
* (`… > learnings.jsonl # never run this twice`) or in an earlier sentence
|
|
43
|
+
* (`Never skip review. Then run: … >> learnings.jsonl`) does not count.
|
|
44
|
+
* Exempt (whole line): the last sentence of the previous non-empty line states
|
|
45
|
+
* a prohibition before any operator, an empty-file create (`: > file`), or the
|
|
46
|
+
* marker `<!-- learnings-write-check: prohibition -->` on the line or the
|
|
47
|
+
* previous non-empty line. Fenced blocks ARE scanned — a fenced shell snippet
|
|
48
|
+
* is exactly a shell write.
|
|
49
|
+
*
|
|
50
|
+
* @module scripts/lib/validate/check-learnings-shell-writes
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { readFileSync } from 'node:fs';
|
|
54
|
+
import path from 'node:path';
|
|
55
|
+
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
56
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
57
|
+
|
|
58
|
+
/** Instruction corpora that tell an agent what to run. */
|
|
59
|
+
export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', 'docs', '.claude/rules', '.cursor/rules']);
|
|
60
|
+
|
|
61
|
+
/** Lines on either side of an R2 operator searched for an anchor. */
|
|
62
|
+
const WINDOW = 3;
|
|
63
|
+
|
|
64
|
+
const MENTION_RE = /learnings\.jsonl/;
|
|
65
|
+
const ANCHOR_RE = /learnings\.jsonl|\b(?:the|learnings) store\b|\bresult back\b|\batomic rewrite\b/i;
|
|
66
|
+
const SHELL_REDIRECT_RE = /(?:^|[\s|;&(])(>{1,2})[ \t]*["'`]?[^\s"'`<>]*learnings\.jsonl\b/dg;
|
|
67
|
+
const TEE_RE = /\btee\b(?:[ \t]+-{1,2}[\w-]+)*[ \t]+["'`]?[^\s"'`|]*learnings\.jsonl\b/g;
|
|
68
|
+
const PROSE_OPERATOR_RE = /`>{1,2}`/g;
|
|
69
|
+
const ANY_OPERATOR_RE = /`>{1,2}`|(?:^|[\s|;&(])>{1,2}|\btee\b/;
|
|
70
|
+
const PROHIBITION_RE = /\b(NEVER|[Nn]ever|DO NOT|[Dd]o not|[Dd]on't|FORBIDDEN|forbidden|verboten)\b/;
|
|
71
|
+
// A sentence ends at `.`/`!`/`?` + whitespace + a capital (markup allowed):
|
|
72
|
+
// `jq . next` and `jq | ... >` are not sentence breaks.
|
|
73
|
+
const SENTENCE_BREAK_RE = /[.!?]\s+(?=[*_`"']*[A-Z])/;
|
|
74
|
+
const EMPTY_CREATE_RE = /^\s*:\s*>/;
|
|
75
|
+
const BLOCKQUOTE_RE = /^\s*(?:>[ \t])+/;
|
|
76
|
+
const FENCE_RE = /^\s*(?:```|~~~)/;
|
|
77
|
+
const MARKER = '<!-- learnings-write-check: prohibition -->';
|
|
78
|
+
|
|
79
|
+
/** The sentence a position sits in, cut off at that position. */
|
|
80
|
+
function clauseBefore(text, pos) {
|
|
81
|
+
return text.slice(0, pos).split(SENTENCE_BREAK_RE).pop();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True when a prohibition keyword precedes `pos` within its own sentence. */
|
|
85
|
+
function prohibitedBefore(text, pos) {
|
|
86
|
+
return PROHIBITION_RE.test(clauseBefore(text, pos));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** True when the last sentence of `line` states a prohibition before any operator. */
|
|
90
|
+
function isProhibitionSentence(line) {
|
|
91
|
+
const last = line.replace(BLOCKQUOTE_RE, '').trimEnd().split(SENTENCE_BREAK_RE).pop();
|
|
92
|
+
const k = last.search(PROHIBITION_RE);
|
|
93
|
+
if (k < 0) return false;
|
|
94
|
+
const o = last.search(ANY_OPERATOR_RE);
|
|
95
|
+
return o < 0 || k < o;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** True when `pos` sits inside an ASCII double-quoted span of `text`. */
|
|
99
|
+
function insideDoubleQuotes(text, pos) {
|
|
100
|
+
return (text.slice(0, pos).match(/"/g) ?? []).length % 2 === 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Every operator on the line as `{rule, pos}`, positions relative to `body`.
|
|
105
|
+
* R2 candidates are returned only when the ±WINDOW neighbourhood holds an anchor.
|
|
106
|
+
*/
|
|
107
|
+
function operatorsOf(body, lines, i) {
|
|
108
|
+
const ops = [];
|
|
109
|
+
if (MENTION_RE.test(body)) {
|
|
110
|
+
for (const m of body.matchAll(SHELL_REDIRECT_RE)) ops.push({ rule: 'R1', pos: m.indices[1][0] });
|
|
111
|
+
for (const m of body.matchAll(TEE_RE)) ops.push({ rule: 'R3', pos: m.index });
|
|
112
|
+
}
|
|
113
|
+
const prose = [...body.matchAll(PROSE_OPERATOR_RE)];
|
|
114
|
+
if (prose.length > 0) {
|
|
115
|
+
const from = Math.max(0, i - WINDOW);
|
|
116
|
+
const anchored = lines.slice(from, i + WINDOW + 1).some((l) => ANCHOR_RE.test(l));
|
|
117
|
+
if (anchored) {
|
|
118
|
+
for (const m of prose) {
|
|
119
|
+
if (!insideDoubleQuotes(body, m.index)) ops.push({ rule: 'R2', pos: m.index });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return ops;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Scan the instruction corpora for shell writes to `learnings.jsonl`.
|
|
128
|
+
*
|
|
129
|
+
* @param {{ pluginRoot: string, dirs?: readonly string[] }} opts
|
|
130
|
+
* @returns {{ ok: boolean, findings: {file: string, line: number, rule: string, text: string}[],
|
|
131
|
+
* summary: { filesScanned: number, mentions: number, exempt: number } }}
|
|
132
|
+
*/
|
|
133
|
+
export function scanLearningsShellWrites({ pluginRoot, dirs = SCAN_DIRS }) {
|
|
134
|
+
const files = enumerateRepoFiles({ repoRoot: pluginRoot, dirs: [...dirs], exts: ['.md', '.mdc'] });
|
|
135
|
+
const findings = [];
|
|
136
|
+
let mentions = 0;
|
|
137
|
+
let exempt = 0;
|
|
138
|
+
for (const abs of files) {
|
|
139
|
+
const lines = readFileSync(abs, 'utf8').split('\n');
|
|
140
|
+
const rel = path.relative(pluginRoot, abs);
|
|
141
|
+
let inFence = false;
|
|
142
|
+
let prev = '';
|
|
143
|
+
lines.forEach((text, i) => {
|
|
144
|
+
const prevNonEmpty = prev;
|
|
145
|
+
if (text.trim().length > 0) prev = text;
|
|
146
|
+
if (FENCE_RE.test(text)) {
|
|
147
|
+
inFence = !inFence;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (MENTION_RE.test(text)) mentions += 1;
|
|
151
|
+
const body = inFence ? text : text.replace(BLOCKQUOTE_RE, '');
|
|
152
|
+
const ops = operatorsOf(body, lines, i);
|
|
153
|
+
if (ops.length === 0) return;
|
|
154
|
+
const lineExempt =
|
|
155
|
+
EMPTY_CREATE_RE.test(body) ||
|
|
156
|
+
text.includes(MARKER) ||
|
|
157
|
+
prevNonEmpty.includes(MARKER) ||
|
|
158
|
+
isProhibitionSentence(prevNonEmpty);
|
|
159
|
+
const failing = lineExempt ? [] : ops.filter((op) => !prohibitedBefore(body, op.pos));
|
|
160
|
+
if (failing.length === 0) {
|
|
161
|
+
exempt += 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const rule = ['R1', 'R3', 'R2'].find((r) => failing.some((op) => op.rule === r));
|
|
165
|
+
findings.push({ file: rel, line: i + 1, rule, text: text.trim() });
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { ok: findings.length === 0, findings, summary: { filesScanned: files.length, mentions, exempt } };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const RULE_TEXT = Object.freeze({
|
|
172
|
+
R1: 'shell redirect into learnings.jsonl',
|
|
173
|
+
R2: 'redirect operator near a learnings.jsonl / store-write mention',
|
|
174
|
+
R3: 'tee into learnings.jsonl',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run the human-readable validator CLI.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} pluginRoot absolute plugin root
|
|
181
|
+
* @returns {number} 0 = clean, 1 = findings
|
|
182
|
+
*/
|
|
183
|
+
export function runCheckLearningsShellWrites(pluginRoot) {
|
|
184
|
+
console.log('--- Check: no shell-redirect writes to learnings.jsonl in instruction prose ---');
|
|
185
|
+
const inspection = scanLearningsShellWrites({ pluginRoot });
|
|
186
|
+
for (const f of inspection.findings) {
|
|
187
|
+
console.log(
|
|
188
|
+
` FAIL: [${f.rule}] ${f.file}:${f.line} ${RULE_TEXT[f.rule]} — route the write through ` +
|
|
189
|
+
'`scripts/apply-session-learnings.mjs` or `sweep-expired-learnings.mjs --prune`',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const s = inspection.summary;
|
|
193
|
+
if (inspection.ok) {
|
|
194
|
+
console.log(
|
|
195
|
+
` PASS: ${s.mentions} learnings.jsonl mention(s) in ${s.filesScanned} file(s), ` +
|
|
196
|
+
`${s.exempt} exempt as prohibition`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(`Results: ${inspection.ok ? 1 : 0} passed, ${inspection.findings.length} failed`);
|
|
201
|
+
return inspection.ok ? 0 : 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (isMainModule(import.meta.url)) {
|
|
205
|
+
const args = process.argv.slice(2).filter((arg) => arg !== '--json');
|
|
206
|
+
const root = path.resolve(args[0] || process.cwd());
|
|
207
|
+
if (process.argv.includes('--json')) {
|
|
208
|
+
const inspection = scanLearningsShellWrites({ pluginRoot: root });
|
|
209
|
+
process.stdout.write(`${JSON.stringify(inspection, null, 2)}\n`);
|
|
210
|
+
process.exitCode = inspection.ok ? 0 : 1;
|
|
211
|
+
} else {
|
|
212
|
+
process.exitCode = runCheckLearningsShellWrites(root);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -35,6 +35,7 @@ if (!existsSync(generator)) {
|
|
|
35
35
|
} else {
|
|
36
36
|
const detail = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
|
|
37
37
|
fail(`pi prompt wrappers are stale${detail ? `: ${detail}` : ''}`);
|
|
38
|
+
console.log(' Remedy: node scripts/generate-pi-prompts.mjs');
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
// Order and duplicates are irrelevant (a glob list is matched
|
|
61
61
|
// any-of), so the comparison is over the sorted unique set.
|
|
62
62
|
// NOT flagged: `paths:` alone. It is the form the native
|
|
63
|
-
// documentation prescribes and the
|
|
64
|
-
//
|
|
65
|
-
//
|
|
63
|
+
// documentation prescribes, and the primary downstream consumer
|
|
64
|
+
// (projects-baseline) carries `paths:` on every scoped rule —
|
|
65
|
+
// measured count: docs/baseline.md. The
|
|
66
66
|
// `globs:`-is-canonical preference for VENDORED rules is a separate,
|
|
67
67
|
// warn-level concern already owned by
|
|
68
68
|
// scripts/lib/validate-vendored-rules.mjs (~:289, issue #742) and is
|
|
@@ -330,8 +330,10 @@ for (const name of mdFiles.sort()) {
|
|
|
330
330
|
'("rules without a paths field are loaded unconditionally and apply to all files" — ' +
|
|
331
331
|
'code.claude.com/docs/en/memory § Path-specific rules). `globs:` is the Cursor field name, so this rule ' +
|
|
332
332
|
'is scoped for rule-loader.mjs and Cursor but loads ALWAYS-ON in Claude Code — a silent instruction-budget ' +
|
|
333
|
-
'failure (#1108). Add a paths: key carrying the same patterns as globs
|
|
334
|
-
'canonical
|
|
333
|
+
'failure (#1108). Add a paths: key carrying the same patterns as globs:. Under .claude/rules/ paths: is ' +
|
|
334
|
+
'the canonical scope key (docs/rule-authoring.md) — keep globs: alongside it only if this rule is ALSO ' +
|
|
335
|
+
'vendored out through rules/, where globs: is the vendored form (#742); otherwise paths: alone is enough ' +
|
|
336
|
+
'and globs: can be dropped.',
|
|
335
337
|
);
|
|
336
338
|
} else if (!parity.ok && parity.kind === 'divergent') {
|
|
337
339
|
fail(
|
|
@@ -62,7 +62,15 @@ import { isMainModule } from '../is-main-module.mjs';
|
|
|
62
62
|
* and 0 checked links today and would NOT by itself have caught that defect — those citations are
|
|
63
63
|
* INLINE-CODE (`` `commands/go.md` ``), which this checker deliberately skips (see DELIBERATE
|
|
64
64
|
* NON-CHECKS). It is a forward guard for the day a real link is authored there, not the catcher
|
|
65
|
-
* for the backticked-citation class
|
|
65
|
+
* for the backticked-citation class.
|
|
66
|
+
*
|
|
67
|
+
* That last clause read "and that class has no gate in this repo" until #1384 P3, when it stopped
|
|
68
|
+
* being true: `check-skill-script-paths.mjs` — which DOES judge complete inline-code spans against
|
|
69
|
+
* `MARKDOWN_CITATION_RE` — took the same two roots and `.mdc` into its own `SCAN_DIRS`, and is
|
|
70
|
+
* blocking via `scripts/validate-plugin.mjs`. Measured 2026-09-18: its corpus went 295 → 329 files
|
|
71
|
+
* and it reported the `commands/{go,close}.md` class of defect (4 blocking findings on first run).
|
|
72
|
+
* So the division of labour is: LINKS here, backticked CITATIONS there — neither surface is
|
|
73
|
+
* ungated.
|
|
66
74
|
*/
|
|
67
75
|
export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', '.claude/rules', 'docs', '.cursor/rules']);
|
|
68
76
|
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* #1241 adds blocking repo-rooted `.md` paths quoted as complete inline-code
|
|
12
12
|
* spans, with the same annotations and fence handling. Scan roots stay fixed.
|
|
13
13
|
*
|
|
14
|
+
* ## The runtime-artefact carve-out needs BOTH halves
|
|
15
|
+
*
|
|
16
|
+
* A missing citation is excused as a runtime artefact only when it sits under
|
|
17
|
+
* a harness STATE root ({@link RUNTIME_STATE_PREFIXES}) *and* git ignores it.
|
|
18
|
+
* The git half alone is not the claim it looks like: `.gitignore` also
|
|
19
|
+
* declines to track real local documents (`docs/specs/`, `docs/_private/`) and
|
|
20
|
+
* build output (`coverage/`, `node_modules/`), so "git ignores it" would have
|
|
21
|
+
* silenced a typo in a `docs/specs/…` citation forever — in the very check
|
|
22
|
+
* whose subject is "ships in the tarball, target missing".
|
|
23
|
+
*
|
|
14
24
|
* ## Why
|
|
15
25
|
*
|
|
16
26
|
* Prose is not executed. A skill body that tells the coordinator to run
|
|
@@ -98,14 +108,60 @@
|
|
|
98
108
|
* @module scripts/lib/validate/check-skill-script-paths
|
|
99
109
|
*/
|
|
100
110
|
|
|
111
|
+
import { execFileSync } from 'node:child_process';
|
|
101
112
|
import { existsSync, readFileSync } from 'node:fs';
|
|
102
113
|
import path from 'node:path';
|
|
103
114
|
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
104
115
|
import { forEachLine } from './markdown-fences.mjs';
|
|
105
116
|
import { isMainModule } from '../is-main-module.mjs';
|
|
106
117
|
|
|
107
|
-
/**
|
|
108
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Documentation roots whose prose is treated as a claim about the repo.
|
|
120
|
+
*
|
|
121
|
+
* `.claude/rules` and `.cursor/rules` joined in #1384 P3. Both are instruction
|
|
122
|
+
* corpora loaded by every session, and both cite repo paths in backticks with
|
|
123
|
+
* exactly the grammar {@link MARKDOWN_CITATION_RE} already judges — but until
|
|
124
|
+
* this widening neither had a gate. The real incident:
|
|
125
|
+
* `.cursor/rules/010-session-workflow.mdc` kept citing `commands/go.md` and
|
|
126
|
+
* `commands/close.md` after `3ebf0e9d` folded those files into their skills,
|
|
127
|
+
* and that surface SHIPS (npm tarball, symlinked into consumer repos by
|
|
128
|
+
* `scripts/cursor-install.mjs`). Same widening, mirrored in
|
|
129
|
+
* `check-skill-links.mjs` § SCAN_DIRS.
|
|
130
|
+
*
|
|
131
|
+
* `rules` and `output-styles` joined in #1384 P3 for the same reason one step
|
|
132
|
+
* further out: both are in `package.json` `files[]`, so both SHIP to every
|
|
133
|
+
* consumer, and neither had a gate. The census that motivated it (whole-repo
|
|
134
|
+
* probe, 2026-09-18 @ 20a4cbff) found 6 dangling citations across them —
|
|
135
|
+
* `output-styles/wave-summary.md` cited `hooks/on-stop.sh` for a file that has
|
|
136
|
+
* been `.mjs` for two migrations, and `rules/opt-in-stack/*` cited
|
|
137
|
+
* consumer-side example paths with no marker.
|
|
138
|
+
*
|
|
139
|
+
* `CHANGELOG.md` stays OUT, and root files are not a scan root at all: a
|
|
140
|
+
* changelog cites the paths a release TOUCHED, so a path deleted afterwards is
|
|
141
|
+
* still a correct historical statement — 88 such hits, all correct by
|
|
142
|
+
* construction. Annotating them would be noise on every future entry.
|
|
143
|
+
*/
|
|
144
|
+
export const SCAN_DIRS = Object.freeze([
|
|
145
|
+
'skills',
|
|
146
|
+
'commands',
|
|
147
|
+
'agents',
|
|
148
|
+
'docs',
|
|
149
|
+
'rules',
|
|
150
|
+
'output-styles',
|
|
151
|
+
'.claude/rules',
|
|
152
|
+
'.cursor/rules',
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extensions treated as instruction markdown.
|
|
157
|
+
*
|
|
158
|
+
* `.mdc` is Cursor's own rule extension and is the ONLY reason `.cursor/rules`
|
|
159
|
+
* is scannable at all: every file there is `<nnn>-<name>.mdc`, so a `.md`-only
|
|
160
|
+
* filter would have added the directory to {@link SCAN_DIRS} and enumerated
|
|
161
|
+
* zero files — a widening that looks live and checks nothing. Same constant,
|
|
162
|
+
* same reason, as `check-skill-links.mjs` § MD_EXTENSIONS.
|
|
163
|
+
*/
|
|
164
|
+
export const MD_EXTENSIONS = Object.freeze(['.md', '.mdc']);
|
|
109
165
|
|
|
110
166
|
/**
|
|
111
167
|
* A cited script path. One regex, one alternation, reused for every
|
|
@@ -114,8 +170,35 @@ export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', 'docs'])
|
|
|
114
170
|
* `hooks/**.sh`. `hooks/**.mjs` is deliberately NOT part of this grammar —
|
|
115
171
|
* the `.mjs` half of the citation surface stays exactly `scripts/`, matching
|
|
116
172
|
* every existing annotation and fence-skip test unchanged.
|
|
173
|
+
*
|
|
174
|
+
* Ceiling (BV-004, #1390 P2): the match is UNANCHORED on the left, so a path
|
|
175
|
+
* NESTED under another directory is read from its `scripts/` / `hooks/`
|
|
176
|
+
* segment onward. `templates/shared/hooks/check-csp-single-source.sh` is judged
|
|
177
|
+
* as `hooks/check-csp-single-source.sh` — a path nobody cited. That cuts both
|
|
178
|
+
* ways: a finding names the truncated tail (fail-closed noise), and a nested
|
|
179
|
+
* path whose tail HAPPENS to exist at the repo root passes although the cited
|
|
180
|
+
* file is missing (fail-open).
|
|
181
|
+
*
|
|
182
|
+
* A lookbehind cannot tell the two readings apart, because the character
|
|
183
|
+
* before the tail is `/` in both: `templates/shared/hooks/…` is a real
|
|
184
|
+
* subdirectory, while `$PLUGIN_ROOT/scripts/…`, `${CLAUDE_PLUGIN_ROOT}/…`,
|
|
185
|
+
* `../../scripts/…` and `…/blob/main/hooks/…` all resolve to the plugin root,
|
|
186
|
+
* so their tail IS the right repo-rooted path. Only the prefix token's meaning
|
|
187
|
+
* differs, and the regex cannot evaluate a variable. Measured 2026-09-19 @
|
|
188
|
+
* 8f6ac022 (prose lines of every SCAN_DIRS file, fences skipped — `forEachLine`
|
|
189
|
+
* + this regex, the character before each hit): 1505 hits in 351 files, 42
|
|
190
|
+
* preceded by `/` — 16 variable-rooted, 21 relative, 5 literal, and of those
|
|
191
|
+
* five exactly ONE is a genuine nested directory (the CSP hook above, in
|
|
192
|
+
* `rules/opt-in-stack/security-web.md`). A `(?<![\w./}-])` guard would drop 37
|
|
193
|
+
* correctly-judged citations to fix one.
|
|
194
|
+
*
|
|
195
|
+
* Escape hatch until then: the nested citation carries
|
|
196
|
+
* `<!-- path-check: example -->` (or `planned #<iid>`), which the one live
|
|
197
|
+
* occurrence already does. Revisit when a second real nested-path citation
|
|
198
|
+
* appears that the annotation cannot reasonably cover — then the grammar needs
|
|
199
|
+
* a list of known plugin-root prefixes, not a lookbehind.
|
|
117
200
|
*/
|
|
118
|
-
const CITATION_RE
|
|
201
|
+
const CITATION_RE =/scripts\/[a-zA-Z0-9_/-]*\.(?:mjs|sh)|hooks\/[a-zA-Z0-9_/-]*\.sh/g;
|
|
119
202
|
|
|
120
203
|
/**
|
|
121
204
|
* A complete inline-code span, including spans delimited by multiple backticks.
|
|
@@ -166,6 +249,107 @@ export function isPlaceholderCitation(citedPath) {
|
|
|
166
249
|
return PLACEHOLDER_FRAGMENTS.some((fragment) => lower.includes(fragment));
|
|
167
250
|
}
|
|
168
251
|
|
|
252
|
+
/**
|
|
253
|
+
* The per-harness STATE roots. A missing citation may be excused as a runtime
|
|
254
|
+
* artefact ONLY from inside one of these — and only when git also ignores it
|
|
255
|
+
* (see {@link isRuntimeArtifact}).
|
|
256
|
+
*
|
|
257
|
+
* Each is a harness's own session-state directory: `.orchestrator/` (this
|
|
258
|
+
* plugin's ledgers, locks and metrics), plus the four harness mirrors that
|
|
259
|
+
* carry a per-session `STATE.md` / `wave-scope.json` / `filescopes/` —
|
|
260
|
+
* `.claude/`, `.codex/`, `.cursor/` (all three gitignored file-by-file in this
|
|
261
|
+
* repo's `.gitignore`) and `.pi/` (`skills/_shared/platform-tools.md` § Pi;
|
|
262
|
+
* absent from this checkout, listed so the Pi harness is covered wherever the
|
|
263
|
+
* plugin is installed).
|
|
264
|
+
*
|
|
265
|
+
* Ceiling (BV-004): exactly these five prefixes, and ONLY in conjunction with
|
|
266
|
+
* the git test. `.gitignore` here also declines to track real, hand-authored
|
|
267
|
+
* documents that simply do not ship (`docs/specs/`, `docs/_private/`) and
|
|
268
|
+
* whole build outputs (`coverage/`, `node_modules/`) — a typo in a
|
|
269
|
+
* `docs/specs/…` citation is a defect this check exists to catch, so those
|
|
270
|
+
* must keep REPORTING. Revisit when a harness adds a sixth state root, or
|
|
271
|
+
* when a state root stops being a dot-directory at the repo root.
|
|
272
|
+
*/
|
|
273
|
+
export const RUNTIME_STATE_PREFIXES = Object.freeze([
|
|
274
|
+
'.orchestrator/',
|
|
275
|
+
'.claude/',
|
|
276
|
+
'.codex/',
|
|
277
|
+
'.cursor/',
|
|
278
|
+
'.pi/',
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Does `citedPath` live under a harness state root?
|
|
283
|
+
*
|
|
284
|
+
* Necessary but NOT sufficient for the runtime-artefact carve-out — the git
|
|
285
|
+
* test in {@link gitIgnoredPaths} is the second half. `.claude/rules/x.md` is
|
|
286
|
+
* under a state root and TRACKED, so it still reports.
|
|
287
|
+
*
|
|
288
|
+
* @param {string} citedPath repo-relative POSIX path
|
|
289
|
+
* @returns {boolean}
|
|
290
|
+
*/
|
|
291
|
+
export function isRuntimeStatePath(citedPath) {
|
|
292
|
+
return RUNTIME_STATE_PREFIXES.some((prefix) => citedPath.startsWith(prefix));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Which of `candidates` git IGNORES — one half of the runtime-artefact test
|
|
297
|
+
* (the other is {@link isRuntimeStatePath}).
|
|
298
|
+
*
|
|
299
|
+
* The rules corpora widened into by #1384 P3 cite paths that never exist in a
|
|
300
|
+
* checkout and are not supposed to: `.cursor/STATE.md`, `.orchestrator/…`
|
|
301
|
+
* ledgers, per-session state files. Rather than a hand-kept name list (which
|
|
302
|
+
* would rot the moment a new ledger is added, and which nothing measures), the
|
|
303
|
+
* test is MECHANICAL and matches the repo's own definition: a path the
|
|
304
|
+
* repository declines to track, which does not exist, is by construction
|
|
305
|
+
* written at runtime — so there is nothing for a citation gate to catch.
|
|
306
|
+
*
|
|
307
|
+
* That predicate alone is too wide, which is why the prefix half exists: "git
|
|
308
|
+
* ignores it" and "it is written at runtime" are not the same claim. Measured
|
|
309
|
+
* 2026-09-18, `gitIgnoredPaths` matched `docs/specs/foo.md`,
|
|
310
|
+
* `docs/_private/x.md`, `coverage/report.md` and `node_modules/x/y.mjs` just
|
|
311
|
+
* as readily as `.orchestrator/metrics/events.jsonl` — real local documents
|
|
312
|
+
* whose dead citations this check must still report.
|
|
313
|
+
*
|
|
314
|
+
* ONE `git check-ignore --stdin` for the whole run, never one per path: the
|
|
315
|
+
* corpus carries hundreds of citations and a per-path spawn would dominate the
|
|
316
|
+
* check's runtime.
|
|
317
|
+
*
|
|
318
|
+
* Fails OPEN toward REPORTING (the safe direction here): a missing `git`, a
|
|
319
|
+
* non-git root, or exit 128 yields an empty set, so every absent citation is
|
|
320
|
+
* still reported and no real defect is suppressed by a tooling failure. Exit 1
|
|
321
|
+
* means "none of them are ignored" and is NOT an error.
|
|
322
|
+
*
|
|
323
|
+
* @param {string} repoRoot absolute repo root
|
|
324
|
+
* @param {string[]} candidates repo-relative POSIX paths (already known absent)
|
|
325
|
+
* @returns {Set<string>} the subset git ignores
|
|
326
|
+
*/
|
|
327
|
+
export function gitIgnoredPaths(repoRoot, candidates) {
|
|
328
|
+
const unique = [...new Set(candidates)].filter((p) => p.length > 0 && !p.includes('\n'));
|
|
329
|
+
if (unique.length === 0) return new Set();
|
|
330
|
+
let stdout;
|
|
331
|
+
try {
|
|
332
|
+
stdout = execFileSync('git', ['check-ignore', '--stdin'], {
|
|
333
|
+
cwd: repoRoot,
|
|
334
|
+
input: `${unique.join('\n')}\n`,
|
|
335
|
+
encoding: 'utf8',
|
|
336
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
337
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
338
|
+
});
|
|
339
|
+
} catch (error) {
|
|
340
|
+
// Exit 1 = nothing matched, and git still wrote (empty) stdout. Anything
|
|
341
|
+
// else (128, ENOENT, non-git root) leaves `stdout` undefined → empty set.
|
|
342
|
+
if (error?.status !== 1) return new Set();
|
|
343
|
+
stdout = typeof error.stdout === 'string' ? error.stdout : '';
|
|
344
|
+
}
|
|
345
|
+
return new Set(
|
|
346
|
+
String(stdout)
|
|
347
|
+
.split('\n')
|
|
348
|
+
.map((line) => line.trim())
|
|
349
|
+
.filter(Boolean),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
169
353
|
/** The annotation marker, in any of its three classes. */
|
|
170
354
|
const ANNOTATION_RE = /<!--\s*path-check:\s*([^>]*?)\s*-->/;
|
|
171
355
|
|
|
@@ -274,10 +458,20 @@ export function scanSkillScriptPaths({ pluginRoot, dirs = SCAN_DIRS, strictSh =
|
|
|
274
458
|
existing: 0,
|
|
275
459
|
annotated: 0,
|
|
276
460
|
placeholders: 0,
|
|
461
|
+
runtimeArtifacts: 0,
|
|
277
462
|
findings: 0,
|
|
278
463
|
warnings: 0,
|
|
279
464
|
};
|
|
280
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Citations that are absent AND unannotated — judged only after the corpus
|
|
468
|
+
* loop, when the single `git check-ignore` call can separate a dead
|
|
469
|
+
* reference from a path written at runtime.
|
|
470
|
+
*
|
|
471
|
+
* @type {{file: string, citation: {line: number, path: string}}[]}
|
|
472
|
+
*/
|
|
473
|
+
const pending = [];
|
|
474
|
+
|
|
281
475
|
/** @type {string[]} */
|
|
282
476
|
let files;
|
|
283
477
|
try {
|
|
@@ -290,7 +484,7 @@ export function scanSkillScriptPaths({ pluginRoot, dirs = SCAN_DIRS, strictSh =
|
|
|
290
484
|
// honours `.gitignore`, so the #1143 exposure a bare `readdirSync` walk
|
|
291
485
|
// would reintroduce (a worktree under `.claude/worktrees/`, gitignored
|
|
292
486
|
// `docs/specs/*.md`) stays closed — see that module's header.
|
|
293
|
-
files = enumerateRepoFiles({ repoRoot: pluginRoot, dirs, exts:
|
|
487
|
+
files = enumerateRepoFiles({ repoRoot: pluginRoot, dirs, exts: MD_EXTENSIONS });
|
|
294
488
|
} catch (error) {
|
|
295
489
|
findings.push({
|
|
296
490
|
kind: 'tool-error',
|
|
@@ -389,30 +583,48 @@ export function scanSkillScriptPaths({ pluginRoot, dirs = SCAN_DIRS, strictSh =
|
|
|
389
583
|
}
|
|
390
584
|
if (marker && !marker.ok) continue; // already reported as bad-annotation
|
|
391
585
|
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
'create it, fix the path, or annotate the citation with ' +
|
|
411
|
-
'`<!-- path-check: planned #<iid> | historical | example -->` on this line or the ' +
|
|
412
|
-
'line directly above',
|
|
413
|
-
severity,
|
|
414
|
-
});
|
|
586
|
+
// Deferred, not reported yet: the runtime-artefact test below needs ONE
|
|
587
|
+
// `git check-ignore` for the whole corpus, not one per citation.
|
|
588
|
+
pending.push({ file: relative, citation });
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// BOTH halves, never one: under a harness state root AND untracked by git.
|
|
593
|
+
// Only state-root candidates are even offered to git, so the subprocess
|
|
594
|
+
// shrinks to the paths that could possibly qualify.
|
|
595
|
+
const ignored = gitIgnoredPaths(
|
|
596
|
+
pluginRoot,
|
|
597
|
+
pending.map((p) => p.citation.path).filter(isRuntimeStatePath),
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
for (const { file: relative, citation } of pending) {
|
|
601
|
+
if (isRuntimeStatePath(citation.path) && ignored.has(citation.path)) {
|
|
602
|
+
summary.runtimeArtifacts += 1;
|
|
603
|
+
continue;
|
|
415
604
|
}
|
|
605
|
+
// `.mjs` and `.md` are blocking.
|
|
606
|
+
// `.sh` is advisory (`warn`) unless the caller opted into `strictSh`.
|
|
607
|
+
const isSh = path.extname(citation.path) === '.sh';
|
|
608
|
+
const severity = isSh && !strictSh ? 'warn' : 'fail';
|
|
609
|
+
if (severity === 'warn') summary.warnings += 1;
|
|
610
|
+
findings.push({
|
|
611
|
+
kind: 'missing-path',
|
|
612
|
+
file: relative,
|
|
613
|
+
line: citation.line,
|
|
614
|
+
path: citation.path,
|
|
615
|
+
annotation: null,
|
|
616
|
+
message:
|
|
617
|
+
(isSh
|
|
618
|
+
? severity === 'warn'
|
|
619
|
+
? `\`${citation.path}\` does not exist (advisory — .sh citations do not block ` +
|
|
620
|
+
'validate-plugin until re-run with --strict-sh; see #1187) — '
|
|
621
|
+
: `\`${citation.path}\` does not exist (--strict-sh) — `
|
|
622
|
+
: `\`${citation.path}\` does not exist — `) +
|
|
623
|
+
'create it, fix the path, or annotate the citation with ' +
|
|
624
|
+
'`<!-- path-check: planned #<iid> | historical | example -->` on this line or the ' +
|
|
625
|
+
'line directly above',
|
|
626
|
+
severity,
|
|
627
|
+
});
|
|
416
628
|
}
|
|
417
629
|
|
|
418
630
|
findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|