session-orchestrator 3.24.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +30 -0
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1488 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +60 -14
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +88 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/host-paths.mjs +20 -4
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +30 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +51 -11
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +164 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/SKILL.md +10 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -81,11 +81,12 @@
|
|
|
81
81
|
* 1 — at least one failure (or usage error / unreadable root)
|
|
82
82
|
*/
|
|
83
83
|
|
|
84
|
-
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
84
|
+
import { readFileSync, readdirSync, statSync, existsSync, realpathSync } from 'node:fs';
|
|
85
85
|
import { join, extname, relative, basename, sep, resolve } from 'node:path';
|
|
86
86
|
import { execFileSync } from 'node:child_process';
|
|
87
87
|
import { argv } from 'node:process';
|
|
88
88
|
import { fileURLToPath } from 'node:url';
|
|
89
|
+
import { createRequire } from 'node:module';
|
|
89
90
|
// NOTE: the two host-local helper modules (../config/host-paths.mjs and
|
|
90
91
|
// ./confidential-names.mjs) are imported DYNAMICALLY inside
|
|
91
92
|
// getConfidentialNamePatterns(), NOT statically here. This scanner is a
|
|
@@ -93,7 +94,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
93
94
|
// "Reuse the same scanner") — the .husky/pre-commit E2E and any consumer that
|
|
94
95
|
// copies ONLY this file into a fresh tree would otherwise crash at module load
|
|
95
96
|
// with ERR_MODULE_NOT_FOUND, blocking clean commits. Dynamic import lets CP11 go
|
|
96
|
-
//
|
|
97
|
+
// inert (one WARN line, exit unchanged) when the helpers are absent while CP1–CP10
|
|
98
|
+
// run unchanged. That degrade is scoped to ERR_MODULE_NOT_FOUND ALONE (#1244) —
|
|
99
|
+
// every other CP11 load failure fails CLOSED; see getConfidentialNamePatterns().
|
|
97
100
|
|
|
98
101
|
// ---------------------------------------------------------------------------
|
|
99
102
|
// CLI / import-mode detection (#661)
|
|
@@ -103,7 +106,25 @@ import { fileURLToPath } from 'node:url';
|
|
|
103
106
|
// AND an importable library (the canonicalization helpers are unit-tested in
|
|
104
107
|
// isolation). When imported, the top-level scan + process.exit() must NOT run.
|
|
105
108
|
// `isMain` is true only when this file is the node entry point.
|
|
106
|
-
|
|
109
|
+
// REALPATH BOTH SIDES (#1244 / same class as the #1153 argv[1] main-guard finding).
|
|
110
|
+
// `fileURLToPath(import.meta.url)` is already canonicalized by Node's ESM loader,
|
|
111
|
+
// so a plain `resolve(argv[1])` comparison silently fails whenever the invocation
|
|
112
|
+
// path traverses a symlink — on macOS every `/tmp/...` path does (`/tmp` →
|
|
113
|
+
// `/private/tmp`). The failure mode is the worst one a security guard has: isMain
|
|
114
|
+
// stays false, runScan() never runs, and the process prints NOTHING and exits 0,
|
|
115
|
+
// i.e. a clean-looking pass that never scanned a byte. realpathSync both sides so
|
|
116
|
+
// the two spellings of the same file compare equal; if realpathSync throws (path
|
|
117
|
+
// gone, permission denied) fall back to the historical comparison.
|
|
118
|
+
function canonicalPath(p) {
|
|
119
|
+
try {
|
|
120
|
+
return realpathSync(p);
|
|
121
|
+
} catch {
|
|
122
|
+
return p;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const isMain =
|
|
126
|
+
argv[1] !== undefined &&
|
|
127
|
+
canonicalPath(resolve(argv[1])) === canonicalPath(fileURLToPath(import.meta.url));
|
|
107
128
|
|
|
108
129
|
// CLI: single positional arg required (only enforced when run directly).
|
|
109
130
|
const pluginRoot = argv[2];
|
|
@@ -568,8 +589,10 @@ function escapeRegex(s) {
|
|
|
568
589
|
// its cases red (#974).
|
|
569
590
|
//
|
|
570
591
|
// The dynamic-import degrade used by getConfidentialNamePatterns() is NOT
|
|
571
|
-
// available here. That helper degrades to `[]`
|
|
572
|
-
//
|
|
592
|
+
// available here. That helper degrades to `[]` for the standalone-copy case alone
|
|
593
|
+
// — CP11 goes inert, CP1–CP10 keep running, nothing leaks (every OTHER load
|
|
594
|
+
// failure there is now a counted FAIL, #1244). A failed REDACTION has the opposite
|
|
595
|
+
// failure direction:
|
|
573
596
|
// it prints confidential names verbatim into a PUBLIC GitHub-Actions log, which
|
|
574
597
|
// is precisely the exposure this function exists to prevent (Fix 1 below). The
|
|
575
598
|
// redaction sink must be unconditionally present, so it lives inline.
|
|
@@ -645,6 +668,78 @@ function redactSpans(line, patterns) {
|
|
|
645
668
|
return out;
|
|
646
669
|
}
|
|
647
670
|
|
|
671
|
+
/**
|
|
672
|
+
* The THREE modules getConfidentialNamePatterns() imports directly, as absolute
|
|
673
|
+
* URLs resolved against THIS file. Used only to classify an ERR_MODULE_NOT_FOUND:
|
|
674
|
+
* `err.url` carries the URL of the module that could not be found (measured on
|
|
675
|
+
* Node 24 — a relative specifier yields `url`, a bare package specifier yields
|
|
676
|
+
* none), so a miss on one of these three is the standalone single-file copy,
|
|
677
|
+
* while a miss anywhere DEEPER (a transitive of an in-tree helper, or a bare
|
|
678
|
+
* package) is a broken install that must fail CLOSED rather than go inert.
|
|
679
|
+
*/
|
|
680
|
+
const CP11_DIRECT_SIBLING_URLS = new Set(
|
|
681
|
+
['../config/host-paths.mjs', './confidential-names.mjs', '../owner-yaml.mjs'].map(
|
|
682
|
+
(spec) => new URL(spec, import.meta.url).href,
|
|
683
|
+
),
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* True when an ERR_MODULE_NOT_FOUND names one of this scanner's own three DIRECT
|
|
688
|
+
* helper imports — i.e. the documented standalone-vendoring shape. False for a
|
|
689
|
+
* transitive relative module or a bare package (no `err.url` at all), which is a
|
|
690
|
+
* broken in-tree install: CP11 then fails closed instead of silently returning
|
|
691
|
+
* zero patterns while names ARE configured.
|
|
692
|
+
*
|
|
693
|
+
* @param {{ url?: string }} err
|
|
694
|
+
* @returns {boolean}
|
|
695
|
+
*/
|
|
696
|
+
function isMissingDirectSibling(err) {
|
|
697
|
+
return typeof err?.url === 'string' && CP11_DIRECT_SIBLING_URLS.has(err.url);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Was `paths.confidential-names-file` actually written into owner.yaml?
|
|
702
|
+
*
|
|
703
|
+
* WHY THIS RE-READS THE FILE. `loadOwnerConfig()` reports an invalid OPTIONAL
|
|
704
|
+
* section only as `droppedSections: [{ section: 'paths', errors }]` and replaces
|
|
705
|
+
* `config.paths` with the DEFAULTS — the raw keys of the dropped section are not
|
|
706
|
+
* recoverable from its return value, and its `errors[]` name a key only for the
|
|
707
|
+
* per-key `paths.<key> must be a string` case (a `paths: 42` shape names none).
|
|
708
|
+
* So the one question CP11's verdict turns on — did the operator configure a
|
|
709
|
+
* names file AT ALL — has no answer in the loader's contract today. Re-parsing
|
|
710
|
+
* the file for that single key is the minimal derivation; widening the loader's
|
|
711
|
+
* return shape would touch every one of its callers.
|
|
712
|
+
*
|
|
713
|
+
* Only reached when the YAML already parsed once inside loadOwnerConfig (the
|
|
714
|
+
* dropped-section branch implies that), so `js-yaml` is resolvable here; 'unknown'
|
|
715
|
+
* is the defensive residue and makes the caller fail closed.
|
|
716
|
+
*
|
|
717
|
+
* Returns a CLASS, never the value: the configured path is host-local and this
|
|
718
|
+
* scanner's output is captured by a PUBLIC CI mirror (see the no-path rule above).
|
|
719
|
+
*
|
|
720
|
+
* @param {string} ownerYamlPath
|
|
721
|
+
* @returns {'configured'|'absent'|'unknown'}
|
|
722
|
+
*/
|
|
723
|
+
function rawConfidentialNamesKeyState(ownerYamlPath) {
|
|
724
|
+
let parsed;
|
|
725
|
+
try {
|
|
726
|
+
const yaml = createRequire(import.meta.url)('js-yaml');
|
|
727
|
+
parsed = yaml.load(readFileSync(ownerYamlPath, 'utf8'));
|
|
728
|
+
} catch {
|
|
729
|
+
return 'unknown';
|
|
730
|
+
}
|
|
731
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return 'unknown';
|
|
732
|
+
const paths = parsed.paths;
|
|
733
|
+
// `paths:` absent, null, or not a mapping at all → the key cannot be in there.
|
|
734
|
+
if (paths === null || typeof paths !== 'object' || Array.isArray(paths)) return 'absent';
|
|
735
|
+
const value = paths['confidential-names-file'];
|
|
736
|
+
if (value === undefined || value === null) return 'absent';
|
|
737
|
+
// A non-string (or empty-string) value is still an ATTEMPT to configure CP11 —
|
|
738
|
+
// except '' , which is the schema's documented "no override" spelling.
|
|
739
|
+
if (typeof value === 'string' && value.trim() === '') return 'absent';
|
|
740
|
+
return 'configured';
|
|
741
|
+
}
|
|
742
|
+
|
|
648
743
|
/**
|
|
649
744
|
* CP11: build word-boundary, case-insensitive regexes from the host-local
|
|
650
745
|
* confidential-names list (#728a). Mirrors CP6_PATTERNS (private slugs), but the
|
|
@@ -656,7 +751,7 @@ function redactSpans(line, patterns) {
|
|
|
656
751
|
* be called only from runScan() — NOT at module top-level. This module is
|
|
657
752
|
* dual-mode (CLI entry point AND importable library, `isMain` guard); a top-level
|
|
658
753
|
* fs read would fire on every library import (e.g. pseudonym-map.mjs importing
|
|
659
|
-
* isOwnerLeakySegment).
|
|
754
|
+
* isOwnerLeakySegment). inspectConfidentialNames caches per path, so calling this
|
|
660
755
|
* once per scan reads the file at most once.
|
|
661
756
|
*
|
|
662
757
|
* STANDALONE-SAFE: the two host-local helper modules are DYNAMICALLY imported
|
|
@@ -667,23 +762,198 @@ function redactSpans(line, patterns) {
|
|
|
667
762
|
* with ERR_MODULE_NOT_FOUND. When the helpers are unresolvable (or throw), CP11
|
|
668
763
|
* degrades to inert ([] patterns) and CP1–CP10 run unchanged.
|
|
669
764
|
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
765
|
+
* FAIL CLOSED WHEN CP11 WAS EXPECTED (GitLab #1244). The single bare
|
|
766
|
+
* `try { … } catch { return [] }` this function used to be conflated three
|
|
767
|
+
* outcomes that must not share a verdict, and printed `PASS` for all three:
|
|
768
|
+
* (a) the helpers are unresolvable — the STANDALONE single-file copy. The
|
|
769
|
+
* intended degrade, and the ONLY one: inert + one WARN line, exit unchanged.
|
|
770
|
+
* (b) CP11 is not configured at all (no names file, no env) — the ~99% default:
|
|
771
|
+
* inactive, silent, PASS. Unchanged. This INCLUDES an owner.yaml whose
|
|
772
|
+
* `paths:` section was dropped as invalid for some OTHER key (the #1244
|
|
773
|
+
* fix over-reached here and failed every commit on such a host): the raw
|
|
774
|
+
* key is re-read (rawConfidentialNamesKeyState) and, when absent, CP11 is
|
|
775
|
+
* INACTIVE — one WARN naming the dropped section, no FAIL, exit unchanged.
|
|
776
|
+
* (c) CP11 IS configured — env set, a names file resolved, or the raw
|
|
777
|
+
* `paths.confidential-names-file` key present in a `paths:` section that
|
|
778
|
+
* was dropped as invalid — or its configuration is unknowable because
|
|
779
|
+
* owner.yaml exists but cannot be parsed (e.g. `js-yaml` missing) — and
|
|
780
|
+
* could not be loaded. Previously indistinguishable from (b): the scanner matched
|
|
781
|
+
* nothing and printed `PASS: no owner-privacy leakage found`. A guard that
|
|
782
|
+
* cannot run must say so and FAIL, never report the clean verdict it did
|
|
783
|
+
* not earn — so this returns a `disabledReason` that runScan turns into a
|
|
784
|
+
* `CP11 DISABLED` stderr line plus a counted FAIL (exit 1).
|
|
785
|
+
*
|
|
786
|
+
* The reason strings deliberately carry NO PATH. This scanner's stdout+stderr are
|
|
787
|
+
* captured by a PUBLIC GitHub-Actions mirror, and the confidential-names path is
|
|
788
|
+
* host-local — echoing it there would leak the very `/Users/<name>/…` shape CP1
|
|
789
|
+
* exists to block. The operator knows their own path; the CLASS of failure is what
|
|
790
|
+
* this line has to convey.
|
|
791
|
+
*
|
|
792
|
+
* @returns {Promise<{ patterns: RegExp[], disabledReason?: string, inertWarn?: string }>}
|
|
672
793
|
*/
|
|
673
794
|
async function getConfidentialNamePatterns() {
|
|
795
|
+
let helpers;
|
|
674
796
|
try {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
} catch {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
797
|
+
helpers = {
|
|
798
|
+
hostPaths: await import('../config/host-paths.mjs'),
|
|
799
|
+
confidentialNames: await import('./confidential-names.mjs'),
|
|
800
|
+
// Already a transitive dependency (host-paths.mjs imports it statically), so
|
|
801
|
+
// this adds no file to the standalone-copy chain the husky E2E mirrors.
|
|
802
|
+
ownerYaml: await import('../owner-yaml.mjs'),
|
|
803
|
+
};
|
|
804
|
+
} catch (err) {
|
|
805
|
+
if (err?.code === 'ERR_MODULE_NOT_FOUND' && isMissingDirectSibling(err)) {
|
|
806
|
+
// (a) standalone single-file vendoring. The degrade is documented — but it
|
|
807
|
+
// must not swallow a CP11 that the operator DID configure (GitLab #1262).
|
|
808
|
+
// No helper resolved here, so the only configuration signal still readable
|
|
809
|
+
// is the raw env var: a literal mirror of ENV_KEYS['confidential-names-file']
|
|
810
|
+
// in scripts/lib/config/host-paths.mjs, which cannot be imported on this
|
|
811
|
+
// branch by construction.
|
|
812
|
+
//
|
|
813
|
+
// LIMIT (named, not hidden): an owner.yaml-configured names file is
|
|
814
|
+
// STRUCTURALLY undetectable in a standalone copy — resolveOwnerYamlPath()
|
|
815
|
+
// lives in the very module chain that failed to resolve, so there is no way
|
|
816
|
+
// to learn that owner.yaml even exists, let alone what it configures. Only
|
|
817
|
+
// the env route can fail closed here; the owner.yaml route degrades inert.
|
|
818
|
+
const envNamesPath = process.env.SO_CONFIDENTIAL_NAMES_FILE;
|
|
819
|
+
if (typeof envNamesPath === 'string' && envNamesPath.trim() !== '') {
|
|
820
|
+
return {
|
|
821
|
+
patterns: [],
|
|
822
|
+
disabledReason:
|
|
823
|
+
'CP11 configured via SO_CONFIDENTIAL_NAMES_FILE but the confidential-names helpers are not resolvable (standalone copy) — failing closed',
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
patterns: [],
|
|
828
|
+
inertWarn: 'CP11 inert — confidential-names helpers not resolvable (standalone copy)',
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
// Any OTHER import failure is a broken in-tree install, not the vendoring case.
|
|
832
|
+
return { patterns: [], disabledReason: `confidential-names helpers failed to load (${err?.name ?? 'Error'})` };
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
try {
|
|
836
|
+
const { loadHostPaths, resolveHostPath } = helpers.hostPaths;
|
|
837
|
+
const { inspectConfidentialNames } = helpers.confidentialNames;
|
|
838
|
+
const { resolveOwnerYamlPath } = helpers.ownerYaml;
|
|
839
|
+
|
|
840
|
+
// ONE owner.yaml load. Since #1251 loadHostPaths() passes the loader's own
|
|
841
|
+
// health (source / reason / droppedSections) straight through, so the
|
|
842
|
+
// env>owner.yaml>default precedence and the load's diagnosis come from the
|
|
843
|
+
// same read — no second loadOwnerConfig() call to recover what was discarded.
|
|
844
|
+
const hostCtx = loadHostPaths();
|
|
845
|
+
|
|
846
|
+
// FAIL-CLOSED on an unknowable load (LOW-2). `loadHostPaths` has its own
|
|
847
|
+
// defensive catch that swallows a THROWING owner loader and returns
|
|
848
|
+
// `{ ownerConfig: undefined, env, source: undefined, reason: undefined }`.
|
|
849
|
+
// Without this branch that shape resolves `namesPath` to '' , fires no
|
|
850
|
+
// reason branch, and returns `{ patterns: [] }` — a silent PASS.
|
|
851
|
+
// Only the throw produces all-undefined: `loadOwnerConfig` returns
|
|
852
|
+
// `{ config: getDefaults(), source: 'defaults', errors: [] }` for a
|
|
853
|
+
// genuinely ABSENT owner.yaml (owner-yaml.mjs:587-590) and `source:
|
|
854
|
+
// 'defaults'` with a `reason` for every read/parse failure — so a missing
|
|
855
|
+
// file keeps taking the normal path below. Latent today (the loader
|
|
856
|
+
// returns on every error path), load-bearing if that ever changes.
|
|
857
|
+
if (
|
|
858
|
+
hostCtx.ownerConfig === undefined &&
|
|
859
|
+
hostCtx.source === undefined &&
|
|
860
|
+
hostCtx.reason === undefined
|
|
861
|
+
) {
|
|
862
|
+
return {
|
|
863
|
+
patterns: [],
|
|
864
|
+
disabledReason:
|
|
865
|
+
'CP11 could not determine whether a confidential-names file is configured (owner config loader failed) — failing closed',
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const namesPath = resolveHostPath('confidential-names-file', '', hostCtx);
|
|
870
|
+
|
|
871
|
+
if (typeof namesPath !== 'string' || namesPath.trim() === '') {
|
|
872
|
+
// Nothing resolves a names file. That is either (b) — genuinely
|
|
873
|
+
// unconfigured — or a state in which the answer is UNKNOWABLE because the
|
|
874
|
+
// owner.yaml that would carry it could not be read. Unknowable is (c).
|
|
875
|
+
if (existsSync(resolveOwnerYamlPath())) {
|
|
876
|
+
if (hostCtx.reason === 'yaml-parser-missing') {
|
|
877
|
+
return {
|
|
878
|
+
patterns: [],
|
|
879
|
+
disabledReason:
|
|
880
|
+
"owner.yaml exists but 'js-yaml' is not installed, so a configured confidential-names-file cannot be resolved (run 'npm install')",
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
if (hostCtx.reason === 'unparseable') {
|
|
884
|
+
return {
|
|
885
|
+
patterns: [],
|
|
886
|
+
disabledReason:
|
|
887
|
+
'owner.yaml exists but could not be parsed, so a configured confidential-names-file cannot be resolved',
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
if (hostCtx.droppedSections?.some((d) => d.section === 'paths')) {
|
|
891
|
+
// The paths: section was replaced by its default because SOME key in it
|
|
892
|
+
// is invalid — which says nothing yet about whether CP11 was configured.
|
|
893
|
+
// Re-read the RAW file for that one key (the loader does not expose it on
|
|
894
|
+
// this branch; see rawConfidentialNamesKeyState) and only then decide.
|
|
895
|
+
const rawKey = rawConfidentialNamesKeyState(resolveOwnerYamlPath());
|
|
896
|
+
if (rawKey === 'configured') {
|
|
897
|
+
return {
|
|
898
|
+
patterns: [],
|
|
899
|
+
disabledReason:
|
|
900
|
+
'owner.yaml has an invalid paths: section, so a configured confidential-names-file cannot be resolved',
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (rawKey === 'unknown') {
|
|
904
|
+
return {
|
|
905
|
+
patterns: [],
|
|
906
|
+
disabledReason:
|
|
907
|
+
'owner.yaml has an invalid paths: section and could not be re-read, so a configured confidential-names-file cannot be ruled out',
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
// 'absent' — CP11 was never configured here. Inactive, not disabled:
|
|
911
|
+
// ONE WARN naming the dropped section, no FAIL, exit unchanged.
|
|
912
|
+
return {
|
|
913
|
+
patterns: [],
|
|
914
|
+
inertWarn:
|
|
915
|
+
'CP11 inactive — owner.yaml\'s paths: section was dropped as invalid, but it configures no confidential-names-file',
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return { patterns: [] }; // (b) the ~99% default — inactive, silent.
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// A names file IS configured. Since #1250 the loader REPORTS the class it
|
|
923
|
+
// used to collapse into `null`, so the verdict reads straight off `status` —
|
|
924
|
+
// no second read of the file to re-derive it. Only `[]` ('empty') is an
|
|
925
|
+
// operator choice (inactive, silent); 'missing'/'malformed'/'all-dropped'
|
|
926
|
+
// mean a configured guard cannot run, which is (c) and fails closed. The
|
|
927
|
+
// reasons carry no path (see above). `inspectConfidentialNames` is the
|
|
928
|
+
// discriminated entry point; `loadConfidentialNames` keeps its 4.0.0
|
|
929
|
+
// `string[] | null` shape for external deep-importers and cannot express
|
|
930
|
+
// this distinction.
|
|
931
|
+
const { status, names } = inspectConfidentialNames({ namesPath });
|
|
932
|
+
if (status === 'missing') {
|
|
933
|
+
return { patterns: [], disabledReason: 'a confidential-names-file is configured but does not exist' };
|
|
934
|
+
}
|
|
935
|
+
if (status === 'malformed') {
|
|
936
|
+
return {
|
|
937
|
+
patterns: [],
|
|
938
|
+
disabledReason: 'the configured confidential-names-file is unreadable or not a JSON array of names',
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (status === 'all-dropped') {
|
|
942
|
+
// W4 finding F3 (fail-open): the file listed entries and validation rejected
|
|
943
|
+
// every one of them (non-strings, blanks, over-long). That is a CORRUPTED
|
|
944
|
+
// list, not the `[]` opt-out — CP11 would run with zero patterns and report
|
|
945
|
+
// the clean verdict it never earned. Same class as 'malformed'.
|
|
946
|
+
return {
|
|
947
|
+
patterns: [],
|
|
948
|
+
disabledReason:
|
|
949
|
+
'every entry in the configured confidential-names-file was rejected as invalid or oversized',
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
if (status !== 'ok') return { patterns: [] }; // 'empty' — the operator's `[]` opt-out.
|
|
953
|
+
return { patterns: names.map((name) => new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')) };
|
|
954
|
+
} catch (err) {
|
|
955
|
+
// The helpers resolved but something below threw. CP11 cannot run — fail closed.
|
|
956
|
+
return { patterns: [], disabledReason: `confidential-names resolution failed (${err?.name ?? 'Error'})` };
|
|
687
957
|
}
|
|
688
958
|
}
|
|
689
959
|
|
|
@@ -839,6 +1109,10 @@ function isAllowlisted(relPath, line) {
|
|
|
839
1109
|
'site/index.html',
|
|
840
1110
|
'site/impressum/index.html',
|
|
841
1111
|
'site/datenschutz/index.html',
|
|
1112
|
+
// The German landing page (2026-09-07 redesign) carries the same publisher
|
|
1113
|
+
// identity (JSON-LD @id + footer rel="author") as site/index.html. Exact
|
|
1114
|
+
// path, never a site/de/ prefix — site/guide/index.html stays outside.
|
|
1115
|
+
'site/de/index.html',
|
|
842
1116
|
]);
|
|
843
1117
|
const inAllowlistedFile = ALLOWLISTED_URL_PATHS.has(norm);
|
|
844
1118
|
|
|
@@ -945,7 +1219,24 @@ const scanFiles = textFiles.filter((f) => {
|
|
|
945
1219
|
// unresolvable (standalone single-file copy) → the CP11 block below is a no-op.
|
|
946
1220
|
// Awaited once, before the per-line loop, because the helpers are now dynamically
|
|
947
1221
|
// imported (standalone-safe) — CP1–CP10 behaviour is unchanged.
|
|
948
|
-
|
|
1222
|
+
//
|
|
1223
|
+
// #1244: a CP11 that was EXPECTED but could not load is a DISABLED guard, not a
|
|
1224
|
+
// clean scan — it is announced on stderr and counted as a FAIL so the run exits
|
|
1225
|
+
// non-zero. CP1–CP10 still run to completion either way: a disabled CP11 must not
|
|
1226
|
+
// suppress the findings the other ten rules can still make.
|
|
1227
|
+
const cp11 = await getConfidentialNamePatterns();
|
|
1228
|
+
const cp11Patterns = cp11.patterns;
|
|
1229
|
+
if (cp11.inertWarn) {
|
|
1230
|
+
console.error(`WARN: ${cp11.inertWarn}`);
|
|
1231
|
+
}
|
|
1232
|
+
if (cp11.disabledReason) {
|
|
1233
|
+
console.error(`CP11 DISABLED: ${cp11.disabledReason}`);
|
|
1234
|
+
// Shaped like the per-file FAIL lines (`<where> — <CPn label>: <content>`) so the
|
|
1235
|
+
// report's `— CPn` attribution parser sees CP11 here too; `<scan-wide>` stands in
|
|
1236
|
+
// for the file position because a disabled guard is a property of the RUN, not of
|
|
1237
|
+
// any one file. The reason carries no path (see getConfidentialNamePatterns).
|
|
1238
|
+
fail(`<scan-wide> — CP11 (guard disabled): ${cp11.disabledReason}`);
|
|
1239
|
+
}
|
|
949
1240
|
|
|
950
1241
|
/** @type {Array<{relPath: string, lineNum: number, pattern: string, lineContent: string}>} */
|
|
951
1242
|
const violations = [];
|
|
@@ -1089,7 +1380,13 @@ for (const filePath of scanFiles) {
|
|
|
1089
1380
|
// The spec does not say to deduplicate, so keep as-is.
|
|
1090
1381
|
|
|
1091
1382
|
if (violations.length === 0) {
|
|
1092
|
-
|
|
1383
|
+
// #1244: only claim the clean verdict the run actually earned. With CP11
|
|
1384
|
+
// disabled, ten of eleven rules ran — say that instead of "no leakage found".
|
|
1385
|
+
if (cp11.disabledReason) {
|
|
1386
|
+
console.log(` (CP1–CP10 found no leakage across ${scanFiles.length} scanned files; CP11 did not run)`);
|
|
1387
|
+
} else {
|
|
1388
|
+
pass(`no owner-privacy leakage found across ${scanFiles.length} scanned files`);
|
|
1389
|
+
}
|
|
1093
1390
|
} else {
|
|
1094
1391
|
for (const v of violations) {
|
|
1095
1392
|
// Choke-point redaction (Fix 1 + Fix 2): scrub every configured confidential
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-skill-links.mjs — every relative markdown link under an instruction surface must resolve.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS (measured 2026-09-06, session main-2026-09-06-deep-1):
|
|
6
|
+
* The #1157 `references/` splits moved 16 phase blocks out of three oversized skill bodies and
|
|
7
|
+
* verified each move with a sha256 of the moved block plus a full-reconstruction hash. That method
|
|
8
|
+
* proves the CONTENT is unchanged — and is blind by construction to the one defect class a move
|
|
9
|
+
* creates: a relative link whose correctness depends on the file's DEPTH in the tree.
|
|
10
|
+
*
|
|
11
|
+
* Three links broke and no gate saw it. The worst was
|
|
12
|
+
* `skills/session-end/references/phase-3-documentation-updates.md` → `./phase-3-6-tail.md`, which is
|
|
13
|
+
* the dispatcher for the six session-end tail phases (memory proposals, expired-learnings sweep,
|
|
14
|
+
* auto-dream, skill judge, auto-dialectic, reconciliation). From inside `references/` that path
|
|
15
|
+
* resolves one directory too deep; the target sits a level up. A coordinator following the prose
|
|
16
|
+
* would have found nothing there and silently skipped the tail.
|
|
17
|
+
*
|
|
18
|
+
* The existing neighbours cannot cover this: `check-skill-script-paths.mjs` scans only
|
|
19
|
+
* `scripts/**.mjs|.sh` and `hooks/**.sh` TARGETS, and `claude-md-drift-check` counts dead script
|
|
20
|
+
* citations, not intra-surface markdown links. Different predicate, different blind spot.
|
|
21
|
+
*
|
|
22
|
+
* WHAT IT CHECKS
|
|
23
|
+
* For every `.md` under the scanned surfaces, every inline link `[text](target)` whose target is
|
|
24
|
+
* relative (not http(s):, not mailto:, not `#anchor`, not an absolute path) must exist on disk,
|
|
25
|
+
* resolved against the LINKING FILE's own directory. A `#fragment` suffix is stripped before the
|
|
26
|
+
* existence check — anchors are out of scope (no heading index here); the path half is not.
|
|
27
|
+
*
|
|
28
|
+
* DELIBERATE NON-CHECKS
|
|
29
|
+
* - Link text, anchors, and http(s) reachability (a network check in a validator is a flake).
|
|
30
|
+
* - Reference-style links and bare `<...>` autolinks: not used by this repo's instruction files
|
|
31
|
+
* (measured: 0 occurrences). If one appears, this checker stays silent rather than guessing —
|
|
32
|
+
* recorded here so the gap is known rather than assumed absent.
|
|
33
|
+
* - Fenced code blocks AND inline-code spans are skipped: a path inside an example command or
|
|
34
|
+
* inside backticks is illustrative, not a link. The inline-code carve-out is not cosmetic —
|
|
35
|
+
* `skills/memory-cleanup/SKILL.md:163` documents the MEMORY.md index FORMAT as
|
|
36
|
+
* `` `- [Title](file.md) — hook` ``, and without it that literal template is the checker's
|
|
37
|
+
* only "finding", i.e. the guard's first act would be to demand a doc be made wrong.
|
|
38
|
+
*
|
|
39
|
+
* Exit 0 = every relative link resolves. Exit 1 = at least one does not; each is printed as
|
|
40
|
+
* `file:line target` so it can be fixed without a search.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
44
|
+
import { join, dirname, resolve, relative, sep } from 'node:path';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Surfaces whose markdown is instruction, i.e. read and acted on.
|
|
48
|
+
*
|
|
49
|
+
* `docs/` joined the list in #1258 after the annotate-before-widen step the sibling
|
|
50
|
+
* `check-skill-script-paths.mjs` ran for #1208: a read-only re-scan (`dirs: ['docs']`,
|
|
51
|
+
* 2026-09-07) returned 92 files / 111 relative links / 8 findings — six of them GitLab
|
|
52
|
+
* `-/issues/N` renderer links now carved out by `SKIP_TARGET`, two of them genuinely dangling
|
|
53
|
+
* links to PRDs that had been archived to the private Meta-Vault. Both classes were resolved
|
|
54
|
+
* before the widening, so the widening itself lands at 0 findings.
|
|
55
|
+
*/
|
|
56
|
+
export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', '.claude/rules', 'docs']);
|
|
57
|
+
|
|
58
|
+
/** Path segments that end the walk: vendored or machine-owned trees, never instruction. */
|
|
59
|
+
export const PRUNE_DIRS = new Set(['node_modules', '.git', 'coverage', 'dist', '.pnpm']);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Targets this checker never resolves against the filesystem.
|
|
63
|
+
*
|
|
64
|
+
* The `-/issues/N` · `-/merge_requests/N` branch is a GitLab RENDERER convention, not a link
|
|
65
|
+
* defect: `../../../-/issues/174` is how a doc nested two levels deep points at the project's
|
|
66
|
+
* issue tracker, and GitLab resolves it correctly in its own Markdown view. It leaves the repo
|
|
67
|
+
* by construction, so a filesystem checker can only ever call it dangling. Measured 2026-09-07:
|
|
68
|
+
* 6 of the 8 `docs/` findings were exactly this shape, all in `docs/owner-config-schema.md`.
|
|
69
|
+
*
|
|
70
|
+
* The branch is anchored at BOTH ends. Left-unanchored it would still be a carve-out, but
|
|
71
|
+
* right-unanchored it swallowed every path that merely PASSES THROUGH such a segment —
|
|
72
|
+
* `docs/-/issues/12/../../secrets.md` is a genuinely dangling link and was SKIPped. The
|
|
73
|
+
* optional trailing `#note_123` is kept because GitLab issue links legitimately carry one.
|
|
74
|
+
*/
|
|
75
|
+
const SKIP_TARGET = /^(https?:|mailto:|#|\/)|(^|\/)-\/(issues|merge_requests)\/\d+(#[\w-]*)?$/i;
|
|
76
|
+
const LINK_RE = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
77
|
+
const INLINE_CODE_RE = /(`+)[^`]*?\1/g;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Blank out inline-code spans, preserving column count so a reported line number still lines up.
|
|
81
|
+
* A `[x](y)` inside backticks is quoted TEXT, not a link — see DELIBERATE NON-CHECKS above.
|
|
82
|
+
*/
|
|
83
|
+
function stripInlineCode(line) {
|
|
84
|
+
return line.replace(INLINE_CODE_RE, (m) => ' '.repeat(m.length));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Does the path exist? A stat error (ENOENT, ELOOP, EACCES) is a non-resolving link, not a crash. */
|
|
88
|
+
function statOk(abs) {
|
|
89
|
+
try { statSync(abs); return true; } catch { return false; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Lines inside fenced code blocks — a path in an example is not a link. */
|
|
93
|
+
function fencedLineNumbers(text) {
|
|
94
|
+
const fenced = new Set();
|
|
95
|
+
let inFence = false;
|
|
96
|
+
text.split('\n').forEach((line, i) => {
|
|
97
|
+
if (/^\s*(```|~~~)/.test(line)) { inFence = !inFence; fenced.add(i + 1); return; }
|
|
98
|
+
if (inFence) fenced.add(i + 1);
|
|
99
|
+
});
|
|
100
|
+
return fenced;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Markdown files under the scanned surfaces, enumerated from the FILESYSTEM.
|
|
105
|
+
*
|
|
106
|
+
* Deliberately not `git ls-files`: that lists tracked files only, so a brand-new instruction file
|
|
107
|
+
* — the exact moment a split or a new skill lands — is invisible to the sweep until it is staged,
|
|
108
|
+
* and the check would report clean on the tree that carries the defect. This repo has the incident
|
|
109
|
+
* on record (`.claude/rules/measurement-discipline.md` § "A `git grep` drift sweep cannot see
|
|
110
|
+
* untracked files": a release sweep passed, then failed after the commit, from the same working
|
|
111
|
+
* tree with no edit in between). The cost of the filesystem walk is that a gitignored `.md` under
|
|
112
|
+
* these four directories would also be checked — there are none, and one would be a finding worth
|
|
113
|
+
* seeing anyway.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} repoRoot
|
|
116
|
+
* @param {{dirs?: readonly string[]}} [options] `dirs` defaults to {@link SCAN_DIRS}; it exists so
|
|
117
|
+
* an annotate-before-widen dry-run over a candidate surface needs no re-implementation of this
|
|
118
|
+
* predicate (same shape as `scanSkillScriptPaths({ dirs })`).
|
|
119
|
+
* @returns {string[]} repo-relative paths, sorted for stable output
|
|
120
|
+
*/
|
|
121
|
+
export function listMarkdown(repoRoot, { dirs = SCAN_DIRS } = {}) {
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const dir of dirs) {
|
|
124
|
+
const abs = join(repoRoot, dir);
|
|
125
|
+
let entries;
|
|
126
|
+
try { entries = readdirSync(abs, { recursive: true, withFileTypes: true }); } catch { continue; }
|
|
127
|
+
for (const e of entries) {
|
|
128
|
+
if (!e.isFile() || !e.name.endsWith('.md')) continue;
|
|
129
|
+
// parentPath is absolute; make the record repo-relative with POSIX separators.
|
|
130
|
+
const rel = relative(repoRoot, join(e.parentPath ?? abs, e.name));
|
|
131
|
+
const posix = sep === '/' ? rel : rel.split(sep).join('/');
|
|
132
|
+
// Vendored trees are not an instruction surface: `skills/vault-sync/node_modules/` is real
|
|
133
|
+
// and its bundled READMEs link into their own upstream repo layout, which is not ours to fix.
|
|
134
|
+
if (posix.split('/').some((s) => PRUNE_DIRS.has(s))) continue;
|
|
135
|
+
out.push(posix);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out.sort();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} [repoRoot]
|
|
143
|
+
* @param {{dirs?: readonly string[]}} [options] `dirs` defaults to {@link SCAN_DIRS} — see
|
|
144
|
+
* {@link listMarkdown}.
|
|
145
|
+
* @returns {{ok: boolean, checked: number, files: number, findings: Array<{file: string, line: number, target: string}>}}
|
|
146
|
+
*/
|
|
147
|
+
export function checkSkillLinks(repoRoot = process.cwd(), { dirs = SCAN_DIRS } = {}) {
|
|
148
|
+
const findings = [];
|
|
149
|
+
let checked = 0;
|
|
150
|
+
const files = listMarkdown(repoRoot, { dirs });
|
|
151
|
+
|
|
152
|
+
for (const rel of files) {
|
|
153
|
+
const abs = join(repoRoot, rel);
|
|
154
|
+
let text;
|
|
155
|
+
try { text = readFileSync(abs, 'utf8'); } catch { continue; }
|
|
156
|
+
const fenced = fencedLineNumbers(text);
|
|
157
|
+
const baseDir = dirname(abs);
|
|
158
|
+
|
|
159
|
+
text.split('\n').forEach((line, idx) => {
|
|
160
|
+
const lineNo = idx + 1;
|
|
161
|
+
if (fenced.has(lineNo)) return;
|
|
162
|
+
for (const m of stripInlineCode(line).matchAll(LINK_RE)) {
|
|
163
|
+
const raw = m[1];
|
|
164
|
+
if (!raw || SKIP_TARGET.test(raw)) continue;
|
|
165
|
+
const target = raw.split('#')[0];
|
|
166
|
+
if (!target) continue; // pure fragment
|
|
167
|
+
checked += 1;
|
|
168
|
+
const resolved = resolve(baseDir, target);
|
|
169
|
+
// Never let a link escape the repo: an out-of-tree target is a finding, not a pass.
|
|
170
|
+
const inside = !relative(repoRoot, resolved).startsWith('..');
|
|
171
|
+
if (!inside || !statOk(resolved)) findings.push({ file: rel, line: lineNo, target: raw });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { ok: findings.length === 0, checked, files: files.length, findings };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function main() {
|
|
180
|
+
const repoRoot = process.argv[2] ? resolve(process.argv[2]) : process.cwd();
|
|
181
|
+
const { ok, checked, files, findings } = checkSkillLinks(repoRoot);
|
|
182
|
+
if (!ok) {
|
|
183
|
+
for (const f of findings) {
|
|
184
|
+
process.stderr.write(` FAIL: ${f.file}:${f.line} → ${f.target} — relative link does not resolve from this file's directory\n`);
|
|
185
|
+
}
|
|
186
|
+
process.stderr.write(`Results: ${findings.length} unresolved relative link(s) in ${files} markdown file(s) under ${SCAN_DIRS.join(', ')}\n`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
// Two-space indent: validate-plugin.mjs tallies on /^ {2}(PASS|FAIL):/m.
|
|
190
|
+
process.stdout.write(` PASS: ${checked} relative link(s) in ${files} markdown file(s) resolve\n`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (import.meta.url === `file://${process.argv[1]}`) main();
|