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
|
@@ -51,15 +51,21 @@
|
|
|
51
51
|
* skill body) and nowhere in the three RUN surfaces above still reports
|
|
52
52
|
* UNREGISTERED — being documented is not being run. REVISIT if a fourth run
|
|
53
53
|
* surface (a new CI job file, a different git hook) is ever added: extend
|
|
54
|
-
* `RUN_SURFACES`, do not special-case it here.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
54
|
+
* `RUN_SURFACES`, do not special-case it here.
|
|
55
|
+
*
|
|
56
|
+
* THE "NEVER A FALSE UNREGISTERED" CLAIM WAS FALSE (#1388, measured
|
|
57
|
+
* 2026-09-18 @ 20a4cbff). This header used to argue that the hand-rolled
|
|
58
|
+
* stripper could only ever MISS a comment, never manufacture a false
|
|
59
|
+
* UNREGISTERED. It had no regex-literal branch, so
|
|
60
|
+
* `const re = /\/*$/;` in `scripts/validate-plugin.mjs` opened a BLOCK
|
|
61
|
+
* COMMENT at the regex's `/*` and swallowed every `runCheck(...)` line below
|
|
62
|
+
* it — precisely a false UNREGISTERED, for every checker registered after
|
|
63
|
+
* that point. The `.mjs` surface is therefore lexed by the shared
|
|
64
|
+
* `maskSource` (`scripts/lib/js-mask.mjs`), which recognises regex literals;
|
|
65
|
+
* only the `#`-comment shell/YAML surfaces (`.husky/pre-commit`,
|
|
66
|
+
* `.gitlab-ci.yml`) still use the small local walker, whose flat
|
|
67
|
+
* quote-tracking ceiling (no `\"` escape handling, no `${...}` walk) is
|
|
68
|
+
* real but bounded to those two files.
|
|
63
69
|
*
|
|
64
70
|
* Usage: check-validator-registration.mjs <repo-root>
|
|
65
71
|
* Output: ` PASS: …` / ` FAIL: …` lines (two leading spaces), then
|
|
@@ -74,6 +80,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
74
80
|
import path from 'node:path';
|
|
75
81
|
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
76
82
|
import { isMainModule } from '../is-main-module.mjs';
|
|
83
|
+
import { maskSource } from '../js-mask.mjs';
|
|
77
84
|
|
|
78
85
|
/** Marker line inside a checker's own header — declares deliberate CLI-only status. */
|
|
79
86
|
export const STANDALONE_MARKER = /^\s*\/\/\s*registration:\s*standalone\b(?:\s+(.*))?$/m;
|
|
@@ -113,11 +120,28 @@ function commentStyleForSurface(rel) {
|
|
|
113
120
|
* truncating the line early. See the header NAMED CEILING for what this
|
|
114
121
|
* quote-tracking deliberately does not attempt.
|
|
115
122
|
*
|
|
123
|
+
* The JS style (`//` + `/* *\/`) is delegated to the shared lexer
|
|
124
|
+
* {@link maskSource} in `keepLiterals` mode — it is the only one of the three
|
|
125
|
+
* RUN_SURFACES that can contain a REGEX LITERAL, and a stripper without a
|
|
126
|
+
* regex branch reads `/\/*$/` as a block-comment opener and swallows every
|
|
127
|
+
* registration below it (#1388, see the header). `maskSource` blanks comment
|
|
128
|
+
* bytes to SPACES rather than deleting them, which is strictly safer for the
|
|
129
|
+
* substring match below: deletion could splice two fragments into a basename
|
|
130
|
+
* that was never written. The `#` style stays local — `maskSource` models
|
|
131
|
+
* JavaScript, not sh/YAML.
|
|
132
|
+
*
|
|
116
133
|
* @param {string} text
|
|
117
134
|
* @param {{lineComment: string, blockComment: boolean, quoteChars: string[]}} style
|
|
118
|
-
* @returns {string}
|
|
135
|
+
* @returns {string} TWO comment models behind one signature, and they differ in
|
|
136
|
+
* shape: the JS style (`//` + `/* *\/`, delegated to {@link maskSource})
|
|
137
|
+
* returns a string of the SAME LENGTH as `text`, with comment bytes blanked to
|
|
138
|
+
* spaces; the `#` style (sh/YAML, the local walker) returns a SHORTER string,
|
|
139
|
+
* with the comment text deleted to end-of-line (the newline itself is kept).
|
|
140
|
+
* Only the substring match below is common to both — do not rely on offsets
|
|
141
|
+
* surviving the `#` style.
|
|
119
142
|
*/
|
|
120
143
|
export function stripComments(text, { lineComment, blockComment, quoteChars }) {
|
|
144
|
+
if (lineComment === '//' && blockComment) return maskSource(text, { keepLiterals: true });
|
|
121
145
|
let out = '';
|
|
122
146
|
let i = 0;
|
|
123
147
|
let inQuote = null;
|
|
@@ -177,6 +177,16 @@ export function inspectConfidentialNames({ namesPath, deps = {} } = {}) {
|
|
|
177
177
|
// Unconfigured → no list, no noise. This is the normal case for public repos
|
|
178
178
|
// and for any host that has not opted into confidential-name scanning.
|
|
179
179
|
if (typeof namesPath !== 'string' || namesPath.trim() === '') {
|
|
180
|
+
// UNREACHABLE from the one production caller today (#1273 P1):
|
|
181
|
+
// `check-owner-leakage.mjs` resolves `namesPath` via `resolveHostPath()`
|
|
182
|
+
// and returns its own early `disabledReason` on an empty/non-string path
|
|
183
|
+
// BEFORE ever calling `inspectConfidentialNames` — so in production this
|
|
184
|
+
// branch never runs. It is reachable only for a direct caller (or a test)
|
|
185
|
+
// that skips that pre-filter. Kept regardless: `status` is part of the
|
|
186
|
+
// documented public contract every deep importer may switch on, and
|
|
187
|
+
// 'unconfigured' is the correct answer for exactly this input shape.
|
|
188
|
+
// REVISIT TRIGGER (BV-004): the production caller stops pre-filtering an
|
|
189
|
+
// empty/non-string `namesPath` before calling `inspectConfidentialNames`.
|
|
180
190
|
return { status: 'unconfigured', names: [] };
|
|
181
191
|
}
|
|
182
192
|
|
|
@@ -6,17 +6,21 @@
|
|
|
6
6
|
* Catches vendoring bugs that are invisible at the source-of-truth layer but
|
|
7
7
|
* become live footguns once synced into a target repo:
|
|
8
8
|
*
|
|
9
|
-
* - `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* - `frontmatter-not-at-top` — a `---` block preceded by anything (the
|
|
10
|
+
* provenance comment, a blank line). Claude Code reads frontmatter only
|
|
11
|
+
* from line 1 and reads only its `paths:` key, so such a rule silently
|
|
12
|
+
* loads ALWAYS-ON in every consumer repo (#1449).
|
|
13
|
+
* - `globs-paths-mismatch` — a library rule's frontmatter must carry
|
|
14
|
+
* `globs:` AND `paths:` with the identical list (order-insensitive), or
|
|
15
|
+
* neither. `paths:` is what Claude Code reads; `globs:` stays because
|
|
16
|
+
* `rule-loader.mjs` prefers it (#742/#795). One key alone, or two
|
|
17
|
+
* different lists, scopes the rule differently per loader.
|
|
15
18
|
* - Missing provenance header — `rules-sync.mjs` detects "plugin-owned vs.
|
|
16
|
-
* local override"
|
|
17
|
-
* `PLUGIN_HEADER_PREFIX
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* local override" via `isPluginOwnedContent()`: the header line starting
|
|
20
|
+
* with `PLUGIN_HEADER_PREFIX` on line 1 (a rule without frontmatter) or as
|
|
21
|
+
* the first non-blank line after a line-1 frontmatter block. A source file
|
|
22
|
+
* missing it gets mis-detected as a local override on the NEXT re-sync (it
|
|
23
|
+
* looks hand-authored), so the plugin can never update it again.
|
|
20
24
|
* - Unfilled placeholder tokens (`{{PROJECT_NAME}}`-style, `## TODO:
|
|
21
25
|
* Customize` headings, `<!-- TODO:` comments) leaking from an
|
|
22
26
|
* un-filled-in skeleton into a synced rule.
|
|
@@ -30,9 +34,10 @@
|
|
|
30
34
|
* Stdlib-only ESM, with the same picomatch-with-fallback resolution pattern
|
|
31
35
|
* as `rule-loader.mjs` (duplicated here rather than imported because those
|
|
32
36
|
* helpers are module-private there — the *approach* is reused, not
|
|
33
|
-
* reinvented).
|
|
34
|
-
*
|
|
35
|
-
* `
|
|
37
|
+
* reinvented). The globs-derived probes use the already-exported
|
|
38
|
+
* `parseGlobsFrontmatter` from `rule-loader.mjs` directly; only
|
|
39
|
+
* `globs-paths-mismatch` reads the two keys itself (`readScopeKeys`), because
|
|
40
|
+
* the loader merges them and cannot say which key carried which list.
|
|
36
41
|
*
|
|
37
42
|
* ## Scope: the `rules/` fleet library, never a repo's own `.claude/rules/`
|
|
38
43
|
*
|
|
@@ -43,15 +48,14 @@
|
|
|
43
48
|
* written target is never read back through the gate. The CLI's `--dir` is
|
|
44
49
|
* operator-supplied and means that same library.
|
|
45
50
|
*
|
|
46
|
-
* This
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* probe for `.claude/rules/`.
|
|
51
|
+
* This matters for `globs-paths-mismatch` and the provenance probe: they encode
|
|
52
|
+
* the LIBRARY convention (both scope keys, a provenance header). A repo's own
|
|
53
|
+
* CONSOLIDATED rules under `.claude/rules/` are `paths:`-only and header-free
|
|
54
|
+
* by design, because Claude Code's native loader reads ONLY `paths:`
|
|
55
|
+
* (`validate/check-rules.mjs` #1108; `docs/rule-authoring.md` § Consolidated
|
|
56
|
+
* rules point 3). Measured 2026-09-16 in this repo: 10 path-scoped rule files
|
|
57
|
+
* there, 9 of them `paths:`-only. Pointing `--dir` at such a tree reports
|
|
58
|
+
* findings about a convention those files were never meant to follow.
|
|
55
59
|
*
|
|
56
60
|
* @module validate-vendored-rules
|
|
57
61
|
*/
|
|
@@ -61,7 +65,7 @@ import { join, resolve, relative, basename } from 'node:path';
|
|
|
61
65
|
import { fileURLToPath } from 'node:url';
|
|
62
66
|
import { execFileSync } from 'node:child_process';
|
|
63
67
|
import { createRequire } from 'node:module';
|
|
64
|
-
import { parseGlobsFrontmatter } from './rule-loader.mjs';
|
|
68
|
+
import { parseGlobsFrontmatter, FRONTMATTER_NOT_AT_TOP } from './rule-loader.mjs';
|
|
65
69
|
import { isMainModule } from './is-main-module.mjs';
|
|
66
70
|
|
|
67
71
|
// Mirrors rules-sync.mjs's exported PLUGIN_HEADER_PREFIX (rules-sync.mjs
|
|
@@ -137,21 +141,26 @@ function matchGlob(filePath, globPattern) {
|
|
|
137
141
|
// Frontmatter helpers
|
|
138
142
|
// ---------------------------------------------------------------------------
|
|
139
143
|
|
|
144
|
+
// A line that may precede a MISPLACED frontmatter opener: blank, or one
|
|
145
|
+
// complete single-line HTML comment (the provenance header's shape).
|
|
140
146
|
const HEADER_LINE_RE = /^[ \t]*(?:<!--.*-->)?[ \t]*$/;
|
|
141
147
|
|
|
142
148
|
/**
|
|
143
|
-
*
|
|
149
|
+
* Locates a YAML frontmatter block delimited by `---` lines.
|
|
144
150
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
151
|
+
* Deliberately tolerant of a leading run of blank lines and/or single-line
|
|
152
|
+
* HTML comments before the opener — NOT because such a block is valid (Claude
|
|
153
|
+
* Code reads frontmatter only when `---` is line 1, and `rule-loader.mjs`
|
|
154
|
+
* no longer tolerates the prefix either), but so `frontmatter-not-at-top` can
|
|
155
|
+
* SEE the misplaced block and report it. Callers that judge what a loader
|
|
156
|
+
* reads must check `startLine === 0` first.
|
|
148
157
|
*
|
|
149
|
-
* @param {string} content
|
|
150
|
-
* @returns {{
|
|
151
|
-
* 0-based file-line
|
|
158
|
+
* @param {string[]} lines - file content split on `\r?\n`
|
|
159
|
+
* @returns {{ bodyLines: string[], startLine: number, endLine: number } | null}
|
|
160
|
+
* startLine / endLine are the 0-based file-line indices of the opening and
|
|
161
|
+
* closing `---`.
|
|
152
162
|
*/
|
|
153
|
-
function
|
|
154
|
-
const lines = content.split(/\r?\n/);
|
|
163
|
+
function locateFrontmatter(lines) {
|
|
155
164
|
let startLine = 0;
|
|
156
165
|
while (startLine < lines.length && HEADER_LINE_RE.test(lines[startLine])) {
|
|
157
166
|
startLine++;
|
|
@@ -159,22 +168,93 @@ function extractFrontmatter(content) {
|
|
|
159
168
|
if (lines[startLine] !== '---') return null;
|
|
160
169
|
for (let i = startLine + 1; i < lines.length; i++) {
|
|
161
170
|
if (lines[i] === '---') {
|
|
162
|
-
return {
|
|
171
|
+
return { bodyLines: lines.slice(startLine + 1, i), startLine, endLine: i };
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
174
|
return null;
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
178
|
+
* True when `content` carries the plugin provenance header in one of the two
|
|
179
|
+
* positions the vendoring convention allows (#1449):
|
|
180
|
+
*
|
|
181
|
+
* (a) line 1 — a rule WITHOUT frontmatter, and also every vendored copy
|
|
182
|
+
* written before #1449 (header on line 1, frontmatter after it);
|
|
183
|
+
* (b) the first non-blank line after the closing `---` of a frontmatter
|
|
184
|
+
* block that opens on line 1 — the current format for a rule WITH
|
|
185
|
+
* frontmatter, because Claude Code reads `paths:` only from a line-1
|
|
186
|
+
* block.
|
|
187
|
+
*
|
|
188
|
+
* This is the ownership predicate `rules-sync.mjs` uses to tell a
|
|
189
|
+
* plugin-owned vendored copy (rewrite it) from a local rule (preserve it), and
|
|
190
|
+
* the `provenance-header` probe's acceptance test. Accepting (a) with a
|
|
191
|
+
* frontmatter after it keeps old-format consumer copies plugin-owned, so the
|
|
192
|
+
* next sync REWRITES them into the new format instead of preserving them
|
|
193
|
+
* forever. Lives here rather than in rules-sync.mjs because rules-sync.mjs
|
|
194
|
+
* imports from this module; it re-exports this function.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} content - raw rule file content
|
|
197
|
+
* @returns {boolean}
|
|
175
198
|
*/
|
|
176
|
-
function
|
|
177
|
-
|
|
199
|
+
export function isPluginOwnedContent(content) {
|
|
200
|
+
const lines = content.split(/\r?\n/);
|
|
201
|
+
if ((lines[0] ?? '').startsWith(PLUGIN_HEADER_PREFIX)) return true;
|
|
202
|
+
const fm = locateFrontmatter(lines);
|
|
203
|
+
if (!fm || fm.startLine !== 0) return false;
|
|
204
|
+
const next = lines.slice(fm.endLine + 1).find((l) => l.trim() !== '');
|
|
205
|
+
return next !== undefined && next.startsWith(PLUGIN_HEADER_PREFIX);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Strips one pair of surrounding quotes, as rule-loader.mjs does for list items.
|
|
210
|
+
* @param {string} s
|
|
211
|
+
* @returns {string}
|
|
212
|
+
*/
|
|
213
|
+
function unquoteItem(s) {
|
|
214
|
+
return s.trim().replace(/^["']|["']$/g, '');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Reads the `globs:` and `paths:` sequences of a frontmatter body EACH ON ITS
|
|
219
|
+
* OWN. `parseGlobsFrontmatter()` cannot answer this: it merges the two keys by
|
|
220
|
+
* design (`globs:` wins, `paths:` is discarded), so it cannot say which key
|
|
221
|
+
* carried which list. Accepts the three value shapes rule-loader.mjs accepts —
|
|
222
|
+
* indented block list, flow list `[a, b]`, single inline value.
|
|
223
|
+
*
|
|
224
|
+
* @param {string[]} bodyLines - frontmatter lines between the `---` markers
|
|
225
|
+
* @returns {{ globs?: { values: string[], index: number }, paths?: { values: string[], index: number } }}
|
|
226
|
+
* `index` is the 0-based body line of the key.
|
|
227
|
+
*/
|
|
228
|
+
function readScopeKeys(bodyLines) {
|
|
229
|
+
/** @type {{ globs?: { values: string[], index: number }, paths?: { values: string[], index: number } }} */
|
|
230
|
+
const found = {};
|
|
231
|
+
let active = null;
|
|
232
|
+
bodyLines.forEach((raw, index) => {
|
|
233
|
+
const line = raw.replace(/\s+$/, '');
|
|
234
|
+
if (line === '' || /^\s*#/.test(line)) return;
|
|
235
|
+
if (active) {
|
|
236
|
+
const item = /^\s+-\s+(.*)$/.exec(line);
|
|
237
|
+
if (item) {
|
|
238
|
+
found[active].values.push(unquoteItem(item[1]));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
active = null;
|
|
242
|
+
}
|
|
243
|
+
const key = /^(globs|paths):\s*(.*)$/.exec(line);
|
|
244
|
+
if (!key) return;
|
|
245
|
+
const [, name, value] = key;
|
|
246
|
+
let values = [];
|
|
247
|
+
if (value === '') {
|
|
248
|
+
active = name;
|
|
249
|
+
} else if (value.startsWith('[') && value.endsWith(']')) {
|
|
250
|
+
const inner = value.slice(1, -1).trim();
|
|
251
|
+
values = inner === '' ? [] : inner.split(',').map(unquoteItem);
|
|
252
|
+
} else {
|
|
253
|
+
values = [unquoteItem(value)];
|
|
254
|
+
}
|
|
255
|
+
found[name] = { values, index };
|
|
256
|
+
});
|
|
257
|
+
return found;
|
|
178
258
|
}
|
|
179
259
|
|
|
180
260
|
/**
|
|
@@ -463,7 +543,7 @@ const FOREIGN_TOKEN_RE = /[A-Z][a-z]+[A-Z]/;
|
|
|
463
543
|
|
|
464
544
|
/**
|
|
465
545
|
* @typedef {object} RuleViolation
|
|
466
|
-
* @property {string} rule - check id (paths-
|
|
546
|
+
* @property {string} rule - check id (frontmatter-not-at-top | globs-paths-mismatch | provenance-header | placeholder | zero-match-globs | foreign-glob)
|
|
467
547
|
* @property {'error'|'warn'} severity
|
|
468
548
|
* @property {string} message
|
|
469
549
|
* @property {number} [line] - 1-based line number, when known
|
|
@@ -486,52 +566,94 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
|
|
|
486
566
|
/** @type {RuleViolation[]} */
|
|
487
567
|
const violations = [];
|
|
488
568
|
|
|
489
|
-
|
|
490
|
-
const fm =
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
569
|
+
const lines = content.split(/\r?\n/);
|
|
570
|
+
const fm = locateFrontmatter(lines);
|
|
571
|
+
|
|
572
|
+
// ---- frontmatter-not-at-top (error) ----
|
|
573
|
+
// Claude Code reads frontmatter only when `---` is line 1. Anything before
|
|
574
|
+
// it (the provenance comment, a blank line) makes it see NO frontmatter, so
|
|
575
|
+
// a path-scoped rule loads ALWAYS-ON in every consumer repo, with no error.
|
|
576
|
+
// Two detectors, one verdict (BV-003): `locateFrontmatter` sees a block
|
|
577
|
+
// behind blank lines / single-line comments (the provenance shape);
|
|
578
|
+
// `parseGlobsFrontmatter` (the loader — the same code the wave injector
|
|
579
|
+
// runs) additionally sees one behind ANY text, e.g. a heading. Either
|
|
580
|
+
// signal is the defect; the finding is reported once.
|
|
581
|
+
const loaderDefect = parseGlobsFrontmatter(content).defect === FRONTMATTER_NOT_AT_TOP;
|
|
582
|
+
if ((fm && fm.startLine !== 0) || (!(fm && fm.startLine === 0) && loaderDefect)) {
|
|
583
|
+
const openerLine = fm ? fm.startLine + 1 : lines.findIndex((l) => l === '---') + 1;
|
|
584
|
+
violations.push({
|
|
585
|
+
rule: 'frontmatter-not-at-top',
|
|
586
|
+
severity: 'error',
|
|
587
|
+
message:
|
|
588
|
+
`${relPath}: the frontmatter block opens on line ${openerLine}, not line 1. ` +
|
|
589
|
+
`Claude Code reads frontmatter only when '---' is the very first line, so it sees none ` +
|
|
590
|
+
`here and loads this rule always-on, ignoring its 'paths:' scope. Move the block to the ` +
|
|
591
|
+
`top; the provenance header goes on the first line after the closing '---'.`,
|
|
592
|
+
line: 1,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ---- globs-paths-mismatch (error) ----
|
|
597
|
+
// Judged only on a line-1 block: a misplaced block is read by no loader,
|
|
598
|
+
// and frontmatter-not-at-top already reports it.
|
|
599
|
+
if (fm && fm.startLine === 0) {
|
|
600
|
+
const { globs, paths } = readScopeKeys(fm.bodyLines);
|
|
601
|
+
// body index i sits on 1-based file line i + 2 when the opener is line 1.
|
|
602
|
+
const keyLine = (k) => k.index + 2;
|
|
603
|
+
let detail = null;
|
|
604
|
+
let line;
|
|
605
|
+
if (globs && !paths) {
|
|
606
|
+
detail =
|
|
607
|
+
`declares 'globs:' but no 'paths:'. Claude Code reads only 'paths:', so it loads this ` +
|
|
608
|
+
`rule always-on. Add 'paths:' carrying the identical list.`;
|
|
609
|
+
line = keyLine(globs);
|
|
610
|
+
} else if (paths && !globs) {
|
|
611
|
+
detail =
|
|
612
|
+
`declares 'paths:' but no 'globs:'. rule-loader.mjs prefers 'globs:' (#742/#795); a ` +
|
|
613
|
+
`rule vendored out through the plugin's rules/ library carries both keys. Add 'globs:' ` +
|
|
614
|
+
`carrying the identical list. This does NOT apply to a repo's own consolidated ` +
|
|
615
|
+
`.claude/rules/ files — those are paths:-only by design and this gate never scans them.`;
|
|
616
|
+
line = keyLine(paths);
|
|
617
|
+
} else if (globs && paths) {
|
|
618
|
+
const g = new Set(globs.values);
|
|
619
|
+
const p = new Set(paths.values);
|
|
620
|
+
const onlyGlobs = [...g].filter((x) => !p.has(x));
|
|
621
|
+
const onlyPaths = [...p].filter((x) => !g.has(x));
|
|
622
|
+
if (onlyGlobs.length > 0 || onlyPaths.length > 0) {
|
|
623
|
+
detail =
|
|
624
|
+
`'globs:' and 'paths:' carry different lists (only in globs: ${JSON.stringify(onlyGlobs)}; ` +
|
|
625
|
+
`only in paths: ${JSON.stringify(onlyPaths)}). Claude Code scopes by 'paths:', ` +
|
|
626
|
+
`rule-loader.mjs by 'globs:', so the two would activate this rule on different files. ` +
|
|
627
|
+
`Make both lists identical.`;
|
|
628
|
+
line = keyLine(globs.index <= paths.index ? globs : paths);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (detail) {
|
|
494
632
|
violations.push({
|
|
495
|
-
rule: 'paths-
|
|
633
|
+
rule: 'globs-paths-mismatch',
|
|
496
634
|
severity: 'error',
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// since #795 — `rule-loader.mjs:275` accepts `paths` alongside `globs`, and `:313`
|
|
500
|
-
// treats it as a fallback alias (`globs:` wins silently when both are present), so a
|
|
501
|
-
// `paths:`-only rule IS glob-scoped. The probe itself stays: it enforces the canonical
|
|
502
|
-
// vendoring form, which is a convention gate, not a loader-compatibility gate. That
|
|
503
|
-
// intent survives #795 and is the subject of the #742 fleet canonicalisation sweep.
|
|
504
|
-
// SCOPE (2026-09-16): this fires only over `rules/` library sources — the module doc's
|
|
505
|
-
// § Scope section names every caller. Do NOT re-point it at a repo's own
|
|
506
|
-
// `.claude/rules/`: those consolidated files are `paths:`-canonical and obeying this
|
|
507
|
-
// remedy there would undo the consolidation (docs/rule-authoring.md point 3).
|
|
508
|
-
message:
|
|
509
|
-
`${relPath}: frontmatter declares a top-level 'paths:' key. It is a recognized alias ` +
|
|
510
|
-
`for 'globs:' (issue #795), so the rule does load glob-scoped — but 'globs:' is the ` +
|
|
511
|
-
`canonical form for a rule VENDORED OUT through the plugin's rules/ library, which is ` +
|
|
512
|
-
`this probe's only population. Migrate to 'globs:' (see issue #742). This does NOT ` +
|
|
513
|
-
`apply to a repo's own consolidated .claude/rules/ files — those are paths:-canonical ` +
|
|
514
|
-
`(Claude Code's native loader reads only 'paths:') and this gate never scans them.`,
|
|
515
|
-
line: lineWithinFrontmatter(fm.startLine, fm.body, pathsMatch.index),
|
|
635
|
+
message: `${relPath}: frontmatter ${detail}`,
|
|
636
|
+
line,
|
|
516
637
|
});
|
|
517
638
|
}
|
|
518
639
|
}
|
|
519
640
|
|
|
520
641
|
// ---- provenance-header (error, opt-in) ----
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
line
|
|
533
|
-
|
|
534
|
-
|
|
642
|
+
// A header on line 1 followed by a frontmatter is accepted HERE (the file
|
|
643
|
+
// has a header, and isPluginOwnedContent must keep old vendored copies
|
|
644
|
+
// plugin-owned); frontmatter-not-at-top reports that shape, once.
|
|
645
|
+
if (requireProvenance && !isPluginOwnedContent(content)) {
|
|
646
|
+
violations.push({
|
|
647
|
+
rule: 'provenance-header',
|
|
648
|
+
severity: 'error',
|
|
649
|
+
message:
|
|
650
|
+
`${relPath}: missing provenance header — a line starting with ` +
|
|
651
|
+
`${JSON.stringify(PLUGIN_HEADER_PREFIX)} (see rules-sync.mjs PLUGIN_HEADER_PREFIX) must ` +
|
|
652
|
+
`stand either on line 1 (a rule without frontmatter) or on the first non-blank line after ` +
|
|
653
|
+
`the closing '---' of a frontmatter that opens on line 1. Without it, rules-sync.mjs ` +
|
|
654
|
+
`mis-detects this file as a local override on the next re-sync and will never update it again.`,
|
|
655
|
+
line: 1,
|
|
656
|
+
});
|
|
535
657
|
}
|
|
536
658
|
|
|
537
659
|
// ---- placeholder (error) — skip matches inside fenced code blocks ----
|
|
@@ -569,6 +691,9 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
|
|
|
569
691
|
}
|
|
570
692
|
|
|
571
693
|
// ---- globs: derived checks (zero-match-globs, foreign-glob) ----
|
|
694
|
+
// Driven by the loader's own parse, so these judge exactly the list
|
|
695
|
+
// rule-loader.mjs will scope by (`globs:`, else its `paths:` alias). On a
|
|
696
|
+
// misplaced block that yields no globs — frontmatter-not-at-top covers it.
|
|
572
697
|
let globs;
|
|
573
698
|
try {
|
|
574
699
|
({ globs } = parseGlobsFrontmatter(content));
|
|
@@ -617,8 +742,9 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
|
|
|
617
742
|
}
|
|
618
743
|
|
|
619
744
|
/**
|
|
620
|
-
* Validates every `*.md` rule file under `dir`, recursively (skips dotfiles
|
|
621
|
-
* and `
|
|
745
|
+
* Validates every `*.md` rule file under `dir`, recursively (skips dotfiles,
|
|
746
|
+
* `_index.md` and `README.md` — neither is a vendored rule: `rules-sync.mjs`
|
|
747
|
+
* vendors only the entries listed in `_index.md`, #1164).
|
|
622
748
|
*
|
|
623
749
|
* When `pluginRoot` is given, every scanned file is additionally passed through
|
|
624
750
|
* `scanVendoringLeaks()` and the findings are collected into the additive
|
|
@@ -642,7 +768,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
|
|
|
642
768
|
const absPath = join(absDir, entry.name);
|
|
643
769
|
if (entry.isDirectory()) {
|
|
644
770
|
files.push(...collectRuleFiles(absPath));
|
|
645
|
-
} else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md') {
|
|
771
|
+
} else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md' && entry.name !== 'README.md') {
|
|
646
772
|
files.push(relative(dir, absPath).replace(/\\/g, '/'));
|
|
647
773
|
}
|
|
648
774
|
}
|
|
@@ -5,10 +5,18 @@
|
|
|
5
5
|
* vault writes under `40-learnings/<repoNs>/` and `50-sessions/<repoNs>/`.
|
|
6
6
|
*
|
|
7
7
|
* Contract:
|
|
8
|
-
* resolveRepoNamespace({ vaultName?,
|
|
8
|
+
* resolveRepoNamespace({ vaultName?, repoRoot? }) → string
|
|
9
9
|
*
|
|
10
|
-
* - Pure + deterministic (given the same
|
|
11
|
-
* and the same host-local pseudonym
|
|
10
|
+
* - Pure + deterministic (given the same vaultName, the same `.vault.yaml`
|
|
11
|
+
* under `repoRoot`, the same git remote, and the same host-local pseudonym
|
|
12
|
+
* map).
|
|
13
|
+
* - `repoRoot` (#1389) selects the directory whose `.vault.yaml` is read;
|
|
14
|
+
* it defaults to `process.cwd()`. Named limit: it scopes ONLY that lookup —
|
|
15
|
+
* the {@link deriveRepo} fallback still resolves from the process cwd and
|
|
16
|
+
* is cached per process, so a `repoRoot` that differs from the cwd AND has
|
|
17
|
+
* no declared slug falls back to the cwd's git identity. Every production
|
|
18
|
+
* caller passes its own cwd today; revisit if a caller ever mirrors a repo
|
|
19
|
+
* other than the one it runs in.
|
|
12
20
|
* - Returns a lowercase kebab slug safe for use as a filesystem path segment.
|
|
13
21
|
* - Host-local pseudonym mapping (Epic #725 D5): consulted ONLY at the redaction
|
|
14
22
|
* site (only when a segment is owner-leaky). If such a repo (raw or sanitised)
|
|
@@ -47,6 +55,7 @@ import { isOwnerLeakySegment } from '../../lib/validate/check-owner-leakage.mjs'
|
|
|
47
55
|
import { loadPseudonymMap } from './pseudonym-map.mjs';
|
|
48
56
|
import { loadHostPaths, resolveHostPath } from '../config/host-paths.mjs';
|
|
49
57
|
import { isQueryFailure, resolvePreferredRemote } from '../vcs-repo-spec.mjs';
|
|
58
|
+
import { readVaultSlug } from '../vault-yaml.mjs';
|
|
50
59
|
|
|
51
60
|
// ── Lazy pseudonym-map path resolution (Epic #725 D5) ────────────────────────
|
|
52
61
|
// The map path comes from env SO_NAMESPACE_MAP > owner.yaml paths.namespace-map-path
|
|
@@ -237,19 +246,48 @@ function lookupPseudonym(base, seg) {
|
|
|
237
246
|
*
|
|
238
247
|
* @param {object} [opts]
|
|
239
248
|
* @param {string|null} [opts.vaultName] - Optional override for the repo identifier.
|
|
240
|
-
* When non-empty and non-whitespace, used in place of the git-derived
|
|
241
|
-
* When absent, the
|
|
249
|
+
* When non-empty and non-whitespace, used in place of the declared/git-derived
|
|
250
|
+
* repo name. When absent, the base is the repo's declared `.vault.yaml`
|
|
251
|
+
* `metadata.slug` (#1131), else the git origin via deriveRepo().
|
|
252
|
+
* @param {string|null} [opts.repoRoot] - Directory whose `.vault.yaml` supplies
|
|
253
|
+
* the declared slug (#1389). `undefined`/`null` → `process.cwd()`. Does NOT
|
|
254
|
+
* redirect the deriveRepo() fallback (see the module header's named limit).
|
|
242
255
|
* @returns {string} A single kebab-slug path segment, e.g. 'session-orchestrator'.
|
|
243
256
|
* Special returns:
|
|
244
257
|
* - 'unknown-repo' — slug derivation produced an empty string.
|
|
245
258
|
* - 'redacted-repo' — the raw or slugified value matched an owner-leakage pattern
|
|
246
259
|
* (CP1 personal home path / CP6 private slug / CP10 personal name in Projects path).
|
|
247
260
|
*/
|
|
248
|
-
export function resolveRepoNamespace({ vaultName = null } = {}) {
|
|
249
|
-
// Choose the base identifier: explicit override
|
|
261
|
+
export function resolveRepoNamespace({ vaultName = null, repoRoot = null } = {}) {
|
|
262
|
+
// Choose the base identifier. Precedence: explicit `vaultName` override >
|
|
263
|
+
// the repo's declared `.vault.yaml` `metadata.slug` (#1131) > git-derived.
|
|
264
|
+
//
|
|
265
|
+
// WHY the slug sits HERE and not in deriveRepo(): deriveRepo() is the RAW
|
|
266
|
+
// repo IDENTITY (`org/repo`) and has consumers that want exactly that and
|
|
267
|
+
// nothing else — `vault-relocation-rules.mjs`, `relocate-vault-corpus.mjs`,
|
|
268
|
+
// `named-vault-resolver.mjs` (which documents the `org/repo-name` format).
|
|
269
|
+
// #1131 concerns the vault FOLDER namespace, which is what THIS function
|
|
270
|
+
// decides, so the lookup belongs at this candidate-selection point.
|
|
271
|
+
//
|
|
272
|
+
// Note what this placement does NOT buy: the `source-repo:` frontmatter is
|
|
273
|
+
// this function's output, not deriveRepo()'s (#732; process.mjs deliberately
|
|
274
|
+
// shares ONE value between the write path and the rendered field), so a repo
|
|
275
|
+
// whose declared slug differs from its derived name gets a new `source-repo`
|
|
276
|
+
// either way. That is inherent to moving the folder, not a side effect of
|
|
277
|
+
// where the lookup lives.
|
|
278
|
+
//
|
|
279
|
+
// It sits BELOW `vaultName` (an explicit per-call/operator override must stay
|
|
280
|
+
// honourable) and ABOVE derivation (a declared registration beats a guess from
|
|
281
|
+
// the remote URL or the checkout directory: the remote answers "where does
|
|
282
|
+
// this code live?", the slug answers "which vault project IS this?", and a
|
|
283
|
+
// host-side rename legitimately diverges the two). It sits ABOVE the leak
|
|
284
|
+
// guard below deliberately — a declared slug is operator data flowing into a
|
|
285
|
+
// written path and is checked by CP1/CP6/CP10 and the pseudonym map exactly
|
|
286
|
+
// like any git-derived identifier.
|
|
287
|
+
const declaredSlug = readVaultSlug(repoRoot ?? process.cwd());
|
|
250
288
|
const base = (vaultName && typeof vaultName === 'string' && vaultName.trim())
|
|
251
289
|
? vaultName.trim()
|
|
252
|
-
: deriveRepo();
|
|
290
|
+
: (declaredSlug ?? deriveRepo());
|
|
253
291
|
|
|
254
292
|
// Sanitise: collapse to last path segment, lowercase, strip non-[a-z0-9-].
|
|
255
293
|
const seg = subjectToSlug(base);
|
|
@@ -666,8 +666,12 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
|
666
666
|
});
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
// #660: namespace new writes under a per-repo subdirectory.
|
|
670
|
-
|
|
669
|
+
// #660: namespace new writes under a per-repo subdirectory. #1389: `ctx.repoRoot`
|
|
670
|
+
// is optional — absent, resolveRepoNamespace falls back to process.cwd().
|
|
671
|
+
const repoNs = resolveRepoNamespace({
|
|
672
|
+
vaultName: ctx?.vaultName ?? null,
|
|
673
|
+
repoRoot: ctx?.repoRoot ?? null,
|
|
674
|
+
});
|
|
671
675
|
// #725 D2: thread the resolved repo namespace into the learning frontmatter as
|
|
672
676
|
// `source-repo` for cross-repo attribution. repoNs is already sanitised +
|
|
673
677
|
// leak-guarded by resolveRepoNamespace, so it is safe to interpolate as-is. The
|
|
@@ -898,7 +902,10 @@ export async function processSession(rawEntry, _lineNum, ctx) {
|
|
|
898
902
|
// owner-leaky repo's real name reached the vault through the session-note
|
|
899
903
|
// frontmatter even though the directory AND the learning `source-repo` field
|
|
900
904
|
// were already pseudonym-mapped/redacted (#732 leak-guard bypass).
|
|
901
|
-
const repoNs = resolveRepoNamespace({
|
|
905
|
+
const repoNs = resolveRepoNamespace({
|
|
906
|
+
vaultName: ctx?.vaultName ?? null,
|
|
907
|
+
repoRoot: ctx?.repoRoot ?? null,
|
|
908
|
+
});
|
|
902
909
|
|
|
903
910
|
// Quality gate (PRD F1.2): skip sessions whose rendered narrative is too short.
|
|
904
911
|
// Measure on the rendered markdown body so the check is schema-agnostic across
|
|
@@ -476,14 +476,24 @@ export function generateSessionNote(entry, options = {}) {
|
|
|
476
476
|
// #1276: lifecycle-only records omit every older count alias. Started
|
|
477
477
|
// measures participation; completed/planned-only counts retain their
|
|
478
478
|
// labels so a plan or a completion count never claims dispatch coverage.
|
|
479
|
-
|
|
480
|
-
|
|
479
|
+
// The `agents_*` triple is the same lifecycle measurement under the
|
|
480
|
+
// de-underscored producer spelling: `metrics-collection.md` documents
|
|
481
|
+
// `agent_count_{planned,started,completed}`, but the live ledger carries
|
|
482
|
+
// one session (`main-2026-09-18-session-1`, 5 waves, measured 2026-09-18
|
|
483
|
+
// over `.orchestrator/metrics/sessions.jsonl`) whose waves emit only
|
|
484
|
+
// `agents_planned` / `agents_started` / `agents_completed` — every one of
|
|
485
|
+
// its 15 counts rendered `?` beside a real number, exactly the #1074 bug
|
|
486
|
+
// class. Each alias sits at the precedence of its documented twin, so no
|
|
487
|
+
// cell that already rendered a value can change.
|
|
488
|
+
const completedAgents = waveCount(w.agent_count_completed) ?? waveCount(w.agents_completed);
|
|
489
|
+
const plannedAgents = waveCount(w.agent_count_planned) ?? waveCount(w.agents_planned);
|
|
481
490
|
const agentsCell =
|
|
482
491
|
waveCount(w.agent_count) ??
|
|
483
492
|
waveCount(w.agents) ??
|
|
484
493
|
waveCount(w.agents_dispatched) ??
|
|
485
494
|
waveCount(w.dispatched) ??
|
|
486
495
|
waveCount(w.agent_count_started) ??
|
|
496
|
+
waveCount(w.agents_started) ??
|
|
487
497
|
(completedAgents === undefined ? undefined : `${completedAgents} completed`) ??
|
|
488
498
|
(plannedAgents === undefined ? MISSING_CELL : `${plannedAgents} planned`);
|
|
489
499
|
const filesCell = waveCount(w.files_changed) ?? waveCount(w.files) ?? MISSING_CELL;
|