session-orchestrator 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +6 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +7 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +8 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +21 -0
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +7 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +7 -2
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +7 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +7 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +7 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +8 -3
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +22 -0
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +7 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +20 -0
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +6 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +6 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +8 -2
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +21 -0
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +6 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +22 -0
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +6 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +6 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +7 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +21 -0
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +7 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +13 -4
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +197 -0
- package/README.md +11 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +17 -3
- package/docs/baseline.md +12 -6
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +15 -3
- package/docs/components.md +13 -6
- package/docs/events-schema.md +59 -9
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +135 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +106 -11
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +125 -8
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +72 -25
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +211 -23
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +6 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +200 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +18 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +119 -28
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +385 -5
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/hooks/subagent-telemetry.mjs +15 -19
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +2 -2
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +171 -15
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +77 -32
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +220 -11
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +408 -33
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +76 -13
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +524 -5
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +332 -50
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +122 -91
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/rule-loader.mjs +41 -12
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +429 -7
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +91 -12
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
- package/scripts/lib/user-invocable-skills.mjs +205 -0
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +329 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +35 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
- package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
- package/scripts/lib/validate/check-unwired-features.mjs +220 -33
- package/scripts/lib/validate/check-validator-registration.mjs +36 -12
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +39 -12
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +109 -39
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +227 -0
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +11 -4
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +80 -11
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +52 -23
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +9 -2
- package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +2 -2
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +21 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +18 -6
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +20 -2
- package/skills/wave-executor/SKILL.md +14 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
package/scripts/release.mjs
CHANGED
|
@@ -356,7 +356,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
356
356
|
'templates/python-uv/tests/test_sanity.py',
|
|
357
357
|
]);
|
|
358
358
|
|
|
359
|
-
// `
|
|
359
|
+
// `skills/release/SKILL.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
|
|
360
360
|
// dated at the line. Bumping it would destroy the evidence it exists to carry —
|
|
361
361
|
// the registry state on that date is the whole point of the paragraph.
|
|
362
362
|
//
|
|
@@ -384,7 +384,10 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
|
|
|
384
384
|
// September 10 campaign snapshot: inputs and receipt reproduce the dated 4.3-planned
|
|
385
385
|
// render made while 4.2.0 was current. Exact files only; neighboring marketing
|
|
386
386
|
// remains checked. Do not bump receipt props without a newly reviewed render.
|
|
387
|
-
|
|
387
|
+
// `scripts/lib/locks/index.mjs` is the one-cycle deprecation shim (5.2.0 CHANGELOG, Changed): its
|
|
388
|
+
// runtime warning names the version it was deprecated IN, so it legitimately carries the previous
|
|
389
|
+
// release literal until its 6.0.0 removal — version history by construction, not drift.
|
|
390
|
+
export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|scripts\/lib\/locks\/index\.mjs|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
|
|
388
391
|
|
|
389
392
|
/** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
|
|
390
393
|
export function checkLeakage(lines) {
|
|
@@ -881,14 +884,36 @@ export function evaluateNpmAuth(whoami) {
|
|
|
881
884
|
* never found out". Only an actual `status: 'green'` reading passes; a release
|
|
882
885
|
* must never proceed on an unknown CI state.
|
|
883
886
|
*
|
|
884
|
-
*
|
|
887
|
+
* The row also carries the CAUSE the probe already knows, because the three
|
|
888
|
+
* ways this row goes red are indistinguishable without it (#1384 P5): a short
|
|
889
|
+
* `sha`, a commit the mirror has never seen, and a HEAD with no pipeline all
|
|
890
|
+
* printed `CI status unknown (query-failed)` / `status: unknown`. The probe's
|
|
891
|
+
* `detail` (degraded branch, `degradedResult`) and `details.reason` /
|
|
892
|
+
* `failingJobName` (read branch, `sanitizeApiText`) are clamped and
|
|
893
|
+
* control-byte-escaped AT THEIR GENERATION in `ci-status-banner.mjs` —
|
|
894
|
+
* appended here, never re-formatted. That escaping is load-bearing, not
|
|
895
|
+
* hygiene: the read branch interpolates a RAW API `status` value into
|
|
896
|
+
* `reason`, and until #1384 f-4 a status carrying `\r` + an ANSI sequence
|
|
897
|
+
* could repaint THIS row green while its verdict stayed `ok:false`. Do not
|
|
898
|
+
* append a further probe field here without checking it goes through one of
|
|
899
|
+
* those two. With neither present the text is byte-identical to
|
|
900
|
+
* before, so a plain green or a named failing job reads exactly as it did.
|
|
901
|
+
*
|
|
902
|
+
* @param {null | {status?: string, failingJobName?: string, degraded?: string,
|
|
903
|
+
* detail?: string, details?: {reason?: string}}} ci
|
|
885
904
|
* @returns {{ok: boolean, detail: string}}
|
|
886
905
|
*/
|
|
887
906
|
export function evaluateCiRow(ci) {
|
|
888
907
|
if (ci === null || ci === undefined) return { ok: false, detail: 'CI status unavailable' };
|
|
889
|
-
if (ci.degraded)
|
|
890
|
-
|
|
891
|
-
|
|
908
|
+
if (ci.degraded) {
|
|
909
|
+
const why = ci.detail ? `: ${ci.detail}` : '';
|
|
910
|
+
return { ok: false, detail: `CI status unknown (${ci.degraded}${why})` };
|
|
911
|
+
}
|
|
912
|
+
// A failing job NAMES the failure, so it wins over the generic reason;
|
|
913
|
+
// `reason` is what fills the gap when there is no job to name (`unknown`).
|
|
914
|
+
const reason = ci.failingJobName || ci.details?.reason || '';
|
|
915
|
+
const why = reason ? ` (${reason})` : '';
|
|
916
|
+
return { ok: ci.status === 'green', detail: `status: ${ci.status}${why}` };
|
|
892
917
|
}
|
|
893
918
|
|
|
894
919
|
/**
|
|
@@ -908,6 +933,11 @@ export function evaluateCiRow(ci) {
|
|
|
908
933
|
* "we could not read the mirror" is not "the mirror is green" (the fail-closed
|
|
909
934
|
* house rule at the top of this file).
|
|
910
935
|
*
|
|
936
|
+
* This function only judges the `ci` reading it is handed — it is
|
|
937
|
+
* `evaluateCiPreflightRows` (the caller, see its own docblock) that fetches
|
|
938
|
+
* `ci` for the commit actually being released, rather than whichever HEAD the
|
|
939
|
+
* GitHub mirror itself reports.
|
|
940
|
+
*
|
|
911
941
|
* @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
|
|
912
942
|
* @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
|
|
913
943
|
* @returns {{ok: boolean, detail: string}}
|
|
@@ -969,6 +999,62 @@ function readPackageVersion(repoRoot) {
|
|
|
969
999
|
return JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version;
|
|
970
1000
|
}
|
|
971
1001
|
|
|
1002
|
+
/**
|
|
1003
|
+
* Both CI preflight rows (`ci-green-on-head` + `ci-green-on-head-github`) for
|
|
1004
|
+
* ONE commit — the commit actually about to be released — never whichever
|
|
1005
|
+
* HEAD the queried platform happens to report.
|
|
1006
|
+
*
|
|
1007
|
+
* Before this function existed, neither `checkCiStatus` call below passed
|
|
1008
|
+
* `sha`, so the GitHub branch fell back to `commits/HEAD` — the MIRROR's own
|
|
1009
|
+
* default-branch head, which is the release commit only once `head-pushed-github`
|
|
1010
|
+
* has ALREADY proven `github/main == local HEAD`. A release cut before that
|
|
1011
|
+
* push landed asked GitHub about a commit that was never pushed and could read
|
|
1012
|
+
* green for work GitHub has not seen at all (measured 2026-09-16 for
|
|
1013
|
+
* `3ebf0e9d`). The GitLab branch was already correct by default (`deps.sha ??
|
|
1014
|
+
* getHeadSha(repoRoot)`), so only the two call sites below needed the fix —
|
|
1015
|
+
* see `checkCiStatus`'s own docblock in `ci-status-banner.mjs` for the `sha`
|
|
1016
|
+
* contract (#1332).
|
|
1017
|
+
*
|
|
1018
|
+
* Factored out of `preflight()` as its own async, DI-testable unit:
|
|
1019
|
+
* `checkCiStatus` reaches the network by default, and `preflight()`'s
|
|
1020
|
+
* surrounding checks (git, npm) are not test-doubled, so this is the seam
|
|
1021
|
+
* through which the `sha` wiring can be unit-tested without spinning up a
|
|
1022
|
+
* full fixture release.
|
|
1023
|
+
*
|
|
1024
|
+
* @param {string} repoRoot
|
|
1025
|
+
* @param {string} head - full hex commit id of the commit being released (the
|
|
1026
|
+
* local HEAD `preflight()` already computed for the remote-parity rows).
|
|
1027
|
+
* @param {{
|
|
1028
|
+
* skipCi?: boolean,
|
|
1029
|
+
* checkCiStatus?: Function,
|
|
1030
|
+
* resolveRepoSpec?: (opts: { repoRoot: string, vcs: 'github' }) => string|undefined,
|
|
1031
|
+
* }} [deps]
|
|
1032
|
+
* @returns {Promise<{ gitlab: {ok: boolean, detail: string}, github: {ok: boolean, detail: string} }>}
|
|
1033
|
+
*/
|
|
1034
|
+
export async function evaluateCiPreflightRows(repoRoot, head, {
|
|
1035
|
+
skipCi = false,
|
|
1036
|
+
checkCiStatus: checkCiStatusDep,
|
|
1037
|
+
resolveRepoSpec: resolveRepoSpecDep = resolveRepoSpec,
|
|
1038
|
+
} = {}) {
|
|
1039
|
+
if (skipCi) {
|
|
1040
|
+
const skipped = { ok: true, detail: 'SKIPPED via --skip-ci' };
|
|
1041
|
+
return { gitlab: skipped, github: skipped };
|
|
1042
|
+
}
|
|
1043
|
+
const checkCiStatusImpl = checkCiStatusDep ?? (await import('./lib/ci-status-banner.mjs')).checkCiStatus;
|
|
1044
|
+
|
|
1045
|
+
const ci = await checkCiStatusImpl({ repoRoot, timeoutMs: 15000, sha: head });
|
|
1046
|
+
const gitlab = evaluateCiRow(ci);
|
|
1047
|
+
|
|
1048
|
+
const githubSpec = resolveRepoSpecDep({ repoRoot, vcs: 'github' });
|
|
1049
|
+
let githubCi = null;
|
|
1050
|
+
if (githubSpec) {
|
|
1051
|
+
githubCi = await checkCiStatusImpl({ repoRoot, vcs: 'github', timeoutMs: 15000, sha: head });
|
|
1052
|
+
}
|
|
1053
|
+
const github = evaluateGithubCiRow(githubSpec, githubCi);
|
|
1054
|
+
|
|
1055
|
+
return { gitlab, github };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
972
1058
|
async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
973
1059
|
const checks = [];
|
|
974
1060
|
const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
|
|
@@ -1098,39 +1184,23 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
|
|
|
1098
1184
|
add('npm-token-live', false, err.message);
|
|
1099
1185
|
}
|
|
1100
1186
|
|
|
1101
|
-
// 6. CI green on HEAD
|
|
1102
|
-
//
|
|
1103
|
-
//
|
|
1104
|
-
//
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
//
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
// detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
|
|
1119
|
-
// mirror's default-branch head — which is HEAD only because `head-pushed-github`
|
|
1120
|
-
// above proves github/main == local HEAD; that row is this one's precondition,
|
|
1121
|
-
// not a duplicate of it.
|
|
1122
|
-
if (skipCi) {
|
|
1123
|
-
add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
|
|
1124
|
-
} else {
|
|
1125
|
-
const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
|
|
1126
|
-
let githubCi = null;
|
|
1127
|
-
if (githubSpec) {
|
|
1128
|
-
const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
|
|
1129
|
-
githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
|
|
1130
|
-
}
|
|
1131
|
-
const row = evaluateGithubCiRow(githubSpec, githubCi);
|
|
1132
|
-
add('ci-green-on-head-github', row.ok, row.detail);
|
|
1133
|
-
}
|
|
1187
|
+
// 6/6b. CI green on HEAD, both platforms — judged for the commit actually
|
|
1188
|
+
// being released, never whichever HEAD the queried platform reports (see
|
|
1189
|
+
// evaluateCiPreflightRows's docblock for the `sha`-wiring history, #1332).
|
|
1190
|
+
//
|
|
1191
|
+
// GitLab: the repo's iron session-start rule applies to releases doubly —
|
|
1192
|
+
// local green is not evidence — see .claude/rules. GitHub: this second row
|
|
1193
|
+
// exists because `detectVcsFamily` picks `origin` (GitLab) for the row
|
|
1194
|
+
// above, whose pipeline is Linux-only; the macOS matrix leg lives
|
|
1195
|
+
// exclusively in `.github/workflows/test.yml`. `vcs: 'github'` forces the
|
|
1196
|
+
// probe onto the mirror without touching the detection order.
|
|
1197
|
+
// SELF-DISABLING: a checkout with no `github` remote has no mirror to be
|
|
1198
|
+
// red, so `evaluateGithubCiRow` reports `skipped` rather than red — see its
|
|
1199
|
+
// own docblock. --skip-ci is refused under --publish upstream in
|
|
1200
|
+
// validateFlags(); it can only reach this branch from --check.
|
|
1201
|
+
const ciRows = await evaluateCiPreflightRows(repoRoot, head, { skipCi });
|
|
1202
|
+
add('ci-green-on-head', ciRows.gitlab.ok, ciRows.gitlab.detail);
|
|
1203
|
+
add('ci-green-on-head-github', ciRows.github.ok, ciRows.github.detail);
|
|
1134
1204
|
|
|
1135
1205
|
// 7. Leakage gate over the actual pack file list.
|
|
1136
1206
|
// `npm_config_loglevel` is INHERITED, and `npm pack --dry-run` writes its whole
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
|
|
41
41
|
import { promises as fs, existsSync } from 'node:fs';
|
|
42
42
|
import { spawnSync } from 'node:child_process';
|
|
43
|
-
import { pathToFileURL } from 'node:url';
|
|
44
43
|
import path from 'node:path';
|
|
45
44
|
import {
|
|
46
45
|
loadVaultRelocationRules,
|
|
@@ -55,6 +54,7 @@ import {
|
|
|
55
54
|
} from './lib/vault-relocation-rules.mjs';
|
|
56
55
|
import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
|
|
57
56
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
57
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
58
58
|
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
// Constants
|
|
@@ -1158,8 +1158,7 @@ async function main() {
|
|
|
1158
1158
|
// Entry guard (run only when invoked directly)
|
|
1159
1159
|
// ---------------------------------------------------------------------------
|
|
1160
1160
|
|
|
1161
|
-
const invokedDirectly =
|
|
1162
|
-
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1161
|
+
const invokedDirectly =isMainModule(import.meta.url);
|
|
1163
1162
|
|
|
1164
1163
|
if (invokedDirectly) {
|
|
1165
1164
|
main().catch((err) => {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
import fs from 'node:fs';
|
|
38
38
|
import { parseArgs } from 'node:util';
|
|
39
39
|
import path from 'node:path';
|
|
40
|
-
import { fileURLToPath } from 'node:url';
|
|
41
40
|
|
|
42
41
|
import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
|
|
43
42
|
import { getProjectDir } from './lib/platform.mjs';
|
|
43
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
44
|
|
|
45
45
|
const USAGE =
|
|
46
46
|
'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
|
|
@@ -200,7 +200,7 @@ async function main() {
|
|
|
200
200
|
process.exit(summary.ok === false ? 3 : 0);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const isDirectRun =
|
|
203
|
+
const isDirectRun =isMainModule(import.meta.url);
|
|
204
204
|
if (isDirectRun) {
|
|
205
205
|
main().catch((err) => {
|
|
206
206
|
process.stderr.write(`repair-invalid-sessions: unexpected error: ${err?.stack ?? err}\n`);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Stdin-only boundary for commands/session.md; never executes task context. */
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { resolveSessionInvocation } from './lib/session-invocation.mjs';
|
|
6
|
+
|
|
7
|
+
const HELP = `Usage: node scripts/resolve-session-invocation.mjs [--json] < arguments.txt
|
|
8
|
+
|
|
9
|
+
Read the complete /session argument text from UTF-8 stdin.
|
|
10
|
+
Resolve the leading housekeeping, feature, deep or ultradeep token; preserve
|
|
11
|
+
the rest as task context. Empty input defaults to deep. An invalid mode emits
|
|
12
|
+
a warning and resolves to deep, as the session command has always specified.
|
|
13
|
+
|
|
14
|
+
--json Emit one JSON object instead of a human-readable summary
|
|
15
|
+
--help Show this help
|
|
16
|
+
--version Show the plugin version
|
|
17
|
+
|
|
18
|
+
Example: node scripts/resolve-session-invocation.mjs --json < arguments.txt
|
|
19
|
+
Write arguments.txt with a file tool; do not interpolate user text into a shell.
|
|
20
|
+
Exit codes: 0 resolved (including fallback), 1 bad CLI arguments, 2 stdin I/O error
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
let options;
|
|
24
|
+
try {
|
|
25
|
+
options = parseArgs({
|
|
26
|
+
options: { json: { type: 'boolean' }, help: { type: 'boolean' }, version: { type: 'boolean' } },
|
|
27
|
+
allowPositionals: false,
|
|
28
|
+
}).values;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
process.stderr.write(`session-invocation: ${error.message}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (options.help) {
|
|
35
|
+
process.stdout.write(HELP);
|
|
36
|
+
} else if (options.version) {
|
|
37
|
+
process.stdout.write(
|
|
38
|
+
`${JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version}\n`,
|
|
39
|
+
);
|
|
40
|
+
} else {
|
|
41
|
+
let input;
|
|
42
|
+
try {
|
|
43
|
+
input = readFileSync(0, 'utf8');
|
|
44
|
+
} catch {
|
|
45
|
+
process.stderr.write('session-invocation: could not read UTF-8 stdin\n');
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
const resolved = resolveSessionInvocation(input);
|
|
49
|
+
if (resolved.invalidMode !== undefined) {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`Invalid session type '${resolved.invalidMode}'. Valid types: housekeeping, feature, deep (alias: ultradeep). Falling back to deep.\n`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
process.stdout.write(
|
|
55
|
+
options.json
|
|
56
|
+
? `${JSON.stringify(resolved)}\n`
|
|
57
|
+
: `Session type: ${resolved.sessionType}${resolved.profile ? ` (profile: ${resolved.profile})` : ''}\nTask context: ${resolved.context}\n`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -23,9 +23,15 @@
|
|
|
23
23
|
* 0 — pass (or informational; non-blocking variants always exit 0)
|
|
24
24
|
* 1 — script error (bad arguments, missing dependencies)
|
|
25
25
|
* 2 — gate failed (full-gate only: typecheck/test/lint errors)
|
|
26
|
+
* 124 — the gate sub-script exceeded the wall-clock ceiling and its whole
|
|
27
|
+
* process GROUP was killed (SIGTERM→SIGKILL). Same value coreutils
|
|
28
|
+
* `timeout(1)` uses. Applies to EVERY variant, including the otherwise
|
|
29
|
+
* non-blocking ones: a killed gate measured nothing, so reporting it as
|
|
30
|
+
* 0 would be a false green (Epic #1425 A3).
|
|
26
31
|
*
|
|
27
32
|
* The gate sub-scripts in scripts/lib/gates/ are NOT reimplemented here; they are
|
|
28
|
-
* invoked via
|
|
33
|
+
* invoked via spawnInGroup() — a detached, process-group-leading shell — with the
|
|
34
|
+
* required env vars.
|
|
29
35
|
*
|
|
30
36
|
* References:
|
|
31
37
|
* scripts/run-quality-gate.sh — original shell orchestrator
|
|
@@ -37,12 +43,18 @@
|
|
|
37
43
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
38
44
|
import { join, dirname, resolve } from 'node:path';
|
|
39
45
|
import { fileURLToPath } from 'node:url';
|
|
40
|
-
import {
|
|
46
|
+
import { spawn } from 'node:child_process';
|
|
41
47
|
|
|
42
48
|
import { die, warn } from './lib/common.mjs';
|
|
43
49
|
import { loadQualityGatesPolicy, resolveCommand } from './lib/quality-gates-policy.mjs';
|
|
44
50
|
import { emitEvent, sessionAttribution } from './lib/events.mjs';
|
|
45
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
admitSuiteCounts,
|
|
53
|
+
publishGateOutcome,
|
|
54
|
+
resolveGateTimeoutMs,
|
|
55
|
+
} from './lib/gates/gate-helpers.mjs';
|
|
56
|
+
import { buildCommandSignature, spawnInGroup } from './lib/process-group.mjs';
|
|
57
|
+
import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
|
|
46
58
|
import { findScopeFile } from './lib/scope-gate.mjs';
|
|
47
59
|
|
|
48
60
|
// ---------------------------------------------------------------------------
|
|
@@ -62,6 +74,24 @@ const VALID_VARIANTS = ['baseline', 'incremental', 'full-gate', 'per-file'];
|
|
|
62
74
|
*/
|
|
63
75
|
const GATE_STDOUT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
64
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Head-room between the ceiling the gate's OWN commands run under
|
|
79
|
+
* (`resolveGateTimeoutMs()`, applied per command inside `runCheck`) and the one
|
|
80
|
+
* this wrapper applies to the gate sub-script as a whole.
|
|
81
|
+
*
|
|
82
|
+
* The inner cap must fire FIRST: it kills exactly the wedged command and still
|
|
83
|
+
* lets the gate print its JSON envelope, name the failing check on stderr and
|
|
84
|
+
* exit 2. This outer cap is the backstop for the case the inner one cannot
|
|
85
|
+
* reach — a gate wedged outside `runCheck`, or a kill ladder that left a
|
|
86
|
+
* survivor holding the pipe open.
|
|
87
|
+
*
|
|
88
|
+
* Named ceiling (BV-004): 60 s. It only has to cover one full SIGTERM→grace→
|
|
89
|
+
* SIGKILL→verify ladder (`DEFAULT_KILL_GRACE_MS` 10 s + 0.5 s verify + 1 s
|
|
90
|
+
* deadline slack ≈ 11.5 s) plus the gate's own JSON write. Revisit if the grace
|
|
91
|
+
* period is ever raised past ~45 s.
|
|
92
|
+
*/
|
|
93
|
+
const GATE_OUTER_TIMEOUT_RESERVE_MS = 60_000;
|
|
94
|
+
|
|
65
95
|
const DEFAULT_TEST_CMD = 'npm test';
|
|
66
96
|
const DEFAULT_TYPECHECK_CMD = 'npm run typecheck';
|
|
67
97
|
const DEFAULT_LINT_CMD = 'npm run lint';
|
|
@@ -88,7 +118,8 @@ if (argv.includes('-h') || argv.includes('--help')) {
|
|
|
88
118
|
'Exit codes:\n' +
|
|
89
119
|
' 0 — pass (non-blocking variants always exit 0)\n' +
|
|
90
120
|
' 1 — script error (bad arguments, missing dependencies)\n' +
|
|
91
|
-
' 2 — gate failed (full-gate only)\n'
|
|
121
|
+
' 2 — gate failed (full-gate only)\n' +
|
|
122
|
+
' 124 — gate timed out; its process group was killed (SIGTERM→SIGKILL)\n',
|
|
92
123
|
);
|
|
93
124
|
process.exit(0);
|
|
94
125
|
}
|
|
@@ -355,6 +386,11 @@ if (!existsSync(gatePath)) {
|
|
|
355
386
|
die(`Gate script not found: ${gatePath}`);
|
|
356
387
|
}
|
|
357
388
|
|
|
389
|
+
// Resolved BEFORE the spawn (it used to sit beside the telemetry block below):
|
|
390
|
+
// the gate-process ledger the spawn writes is pinned to the same root as the
|
|
391
|
+
// event, for the same reason — see the spawn's `repoRoot` comment.
|
|
392
|
+
const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
|
|
393
|
+
|
|
358
394
|
// `npm_config_loglevel` is INHERITED by every descendant, and the pre-push hook
|
|
359
395
|
// invokes this script as `npm run --silent quality-gate` — which sets it to
|
|
360
396
|
// `silent`. That level then reached the gate's own children: `npm pack
|
|
@@ -368,37 +404,141 @@ if (!existsSync(gatePath)) {
|
|
|
368
404
|
// independent of how the gate itself was invoked. `--silent` still does its real
|
|
369
405
|
// job — keeping THIS process's stdout to the single JSON envelope — because the
|
|
370
406
|
// children's output is captured by `runCheck`, never streamed.
|
|
407
|
+
/**
|
|
408
|
+
* Per-command wall-clock ceiling for PATH B (#1425 A3 / #1432).
|
|
409
|
+
*
|
|
410
|
+
* Precedence, highest first:
|
|
411
|
+
* 1. `SO_GATE_TIMEOUT_MS` — the OPERATOR override. It is inherited by every
|
|
412
|
+
* descendant, so a gate child that re-derives the ceiling gets the same
|
|
413
|
+
* answer; `resolveGateTimeoutMs()` already reads it.
|
|
414
|
+
* 2. Session Config `gate.timeout-path-b-ms` — the COMMITTED default.
|
|
415
|
+
* 3. `DEFAULT_GATE_TIMEOUT_MS` (900 000), via `resolveGateTimeoutMs()`.
|
|
416
|
+
*
|
|
417
|
+
* Published to the gate sub-script as `GATE_TIMEOUT_MS` — a DIFFERENT name from
|
|
418
|
+
* the operator override on purpose: writing the resolved value back into
|
|
419
|
+
* `SO_GATE_TIMEOUT_MS` would make a committed default indistinguishable from an
|
|
420
|
+
* operator decision for every process further down the tree.
|
|
421
|
+
*/
|
|
422
|
+
const configuredGateTimeoutMs = (() => {
|
|
423
|
+
const block = configJson !== null && typeof configJson === 'object' ? configJson.gate : null;
|
|
424
|
+
const raw = block && typeof block === 'object' ? block['timeout-path-b-ms'] : undefined;
|
|
425
|
+
return Number.isFinite(raw) && raw > 0 ? raw : null;
|
|
426
|
+
})();
|
|
427
|
+
const operatorTimeoutOverride = (process.env.SO_GATE_TIMEOUT_MS || '').trim() !== '';
|
|
428
|
+
const commandTimeoutMs = operatorTimeoutOverride || configuredGateTimeoutMs === null
|
|
429
|
+
? resolveGateTimeoutMs()
|
|
430
|
+
: configuredGateTimeoutMs;
|
|
431
|
+
|
|
432
|
+
// Gate-process REGISTER root — the same precedence the telemetry destination
|
|
433
|
+
// uses below (`--ledger-root` > project-dir env > cwd), so a sandboxed test that
|
|
434
|
+
// only sets CLAUDE_PROJECT_DIR never writes register lines into the checkout.
|
|
435
|
+
const gateLedgerRoot =
|
|
436
|
+
ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot;
|
|
437
|
+
|
|
371
438
|
const env = {
|
|
372
439
|
...process.env,
|
|
373
440
|
npm_config_loglevel: 'notice',
|
|
374
441
|
TYPECHECK_CMD,
|
|
375
442
|
TEST_CMD,
|
|
376
443
|
LINT_CMD,
|
|
444
|
+
GATE_TIMEOUT_MS: String(commandTimeoutMs),
|
|
445
|
+
// Same resolution the wrapper uses for its OWN spawn (`--ledger-root` > repo
|
|
446
|
+
// root): the gate sub-scripts pass it to runCheck() so every register line of
|
|
447
|
+
// one gate run lands in ONE ledger (#1425 A4, W5 fix-pass).
|
|
448
|
+
GATE_LEDGER_ROOT: gateLedgerRoot,
|
|
377
449
|
FILES: files,
|
|
378
450
|
SESSION_START_REF: sessionStartRef,
|
|
379
451
|
};
|
|
380
452
|
|
|
453
|
+
/**
|
|
454
|
+
* POSIX single-quote one argument for the shell `spawnInGroup` runs the command
|
|
455
|
+
* through. The gate path is derived from `import.meta.url`, so it carries
|
|
456
|
+
* whatever the checkout path carries — a space in it must not split the command.
|
|
457
|
+
*
|
|
458
|
+
* @param {string} value
|
|
459
|
+
* @returns {string}
|
|
460
|
+
*/
|
|
461
|
+
function shellQuote(value) {
|
|
462
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// The gate sub-script runs as the LEADER OF ITS OWN PROCESS GROUP under a
|
|
466
|
+
// wall-clock ceiling (Epic #1425 A3). Before this, `spawnSync('node', [gatePath])`
|
|
467
|
+
// had NO timeout at all and no group semantics: a wedged `tsgo` or vitest worker
|
|
468
|
+
// two levels down was reparented to PPID 1 and kept its memory (2026-09-20: four
|
|
469
|
+
// such orphans, up to 8.0 GB RSS each, host at 13 % free).
|
|
470
|
+
//
|
|
381
471
|
// stdout is PIPED (not inherited) so the suite counts the gate already computed
|
|
382
472
|
// can be lifted straight off its JSON envelope into telemetry (#954) instead of
|
|
383
473
|
// travelling as prose through the STATE.md header. The envelope is re-emitted
|
|
384
474
|
// verbatim below, so the stdout contract is unchanged — a gate sub-script writes
|
|
385
475
|
// exactly one JSON line at the very end (its own child commands are captured by
|
|
386
|
-
// `runCheck`), so nothing streamed before and nothing streams now.
|
|
387
|
-
//
|
|
388
|
-
|
|
476
|
+
// `runCheck`), so nothing streamed before and nothing streams now.
|
|
477
|
+
//
|
|
478
|
+
// stderr stays INHERITED, and that is why `spawnFn` is overridden here rather
|
|
479
|
+
// than left at its default: `spawnInGroup` merges stdout and stderr into one
|
|
480
|
+
// capture, which would interleave the gate's failure disclosure (hundreds of
|
|
481
|
+
// lines, #1149) into the single JSON document every consumer parses off stdout.
|
|
482
|
+
// Handing it a child with `stderr: 'inherit'` leaves `child.stderr` null, the
|
|
483
|
+
// module's own `child.stderr?.on(…)` a no-op, and the gate's warnings live on
|
|
484
|
+
// the operator's terminal exactly as before.
|
|
485
|
+
const gateCommand = `node ${shellQuote(gatePath)}`;
|
|
486
|
+
// Derived from the SAME resolved per-command ceiling published as
|
|
487
|
+
// `GATE_TIMEOUT_MS` above — not from `resolveGateTimeoutMs()` a second time.
|
|
488
|
+
// The outer cap must stay strictly ABOVE the inner one so the inner kill fires
|
|
489
|
+
// first and the gate can still print its envelope; re-deriving here would leave
|
|
490
|
+
// the outer cap at the 900 000 default while a committed
|
|
491
|
+
// `gate.timeout-path-b-ms` above it made the inner cap the later of the two.
|
|
492
|
+
const gateTimeoutMs = commandTimeoutMs + GATE_OUTER_TIMEOUT_RESERVE_MS;
|
|
493
|
+
const result = await spawnInGroup(gateCommand, {
|
|
494
|
+
cwd: repoRoot,
|
|
389
495
|
env,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
496
|
+
timeoutMs: gateTimeoutMs,
|
|
497
|
+
maxOutputBytes: GATE_STDOUT_MAX_BUFFER_BYTES,
|
|
498
|
+
// The ledger is what the orphan reaper (#1425 B) reads to tell its OWN gate
|
|
499
|
+
// processes from every other `node` on the host, so it is pinned to the same
|
|
500
|
+
// root the telemetry is — under the pre-push hook the tree the gate runs in
|
|
501
|
+
// is deleted seconds later, and the record with it.
|
|
502
|
+
repoRoot: gateLedgerRoot,
|
|
503
|
+
commandSignature: buildCommandSignature(gateCommand),
|
|
504
|
+
// The OWNER the ledger records, from the PROCESS-LOCAL witness only — the same
|
|
505
|
+
// rule `scripts/lib/quality-gate.mjs` follows for the gate commands it spawns
|
|
506
|
+
// (`.claude/rules/identity-and-locks.md`: rank witnesses, never union them).
|
|
507
|
+
// `.orchestrator/session.lock` is deliberately NOT consulted: it is a
|
|
508
|
+
// repo-GLOBAL artefact any session in this working copy may hold, so reading
|
|
509
|
+
// it would stamp a PEER's id onto our own gate process and point the orphan
|
|
510
|
+
// reaper (#1425 B) at the wrong owner. No witness → `null`, which the ledger
|
|
511
|
+
// records as "owner unknown" — the state every record carried before this.
|
|
512
|
+
sessionId: [...readProcessLocalSessionIds({ env: process.env, hookInput: null })][0] ?? null,
|
|
513
|
+
// stdin is IGNORED, not inherited. A gate sub-script reads no stdin, but a
|
|
514
|
+
// DETACHED child is in its own process group and is therefore not the
|
|
515
|
+
// terminal's foreground group: the first read from an inherited TTY earns it
|
|
516
|
+
// SIGTTIN, which stops the whole group until the outer ceiling kills it. An
|
|
517
|
+
// ignored stdin turns that hang into an immediate EOF.
|
|
518
|
+
spawnFn: (command, options) => spawn(command, { ...options, stdio: ['ignore', 'pipe', 'inherit'] }),
|
|
393
519
|
});
|
|
394
520
|
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
if (result.error && typeof result.status !== 'number') {
|
|
399
|
-
die(`Failed to run gate script: ${result.error.message}`);
|
|
521
|
+
// `pid: -1` is `spawnInGroup`'s spawn-failure channel (it never rejects).
|
|
522
|
+
if (result.pid === -1) {
|
|
523
|
+
die(`Failed to run gate script: ${result.fullOutput.trim()}`);
|
|
400
524
|
}
|
|
401
525
|
|
|
526
|
+
// On timeout the child was KILLED before it could write its envelope, so its
|
|
527
|
+
// capture is at best a partial JSON document. Publishing that would hand every
|
|
528
|
+
// stdout consumer a parse error where a named failure belongs; publishing the
|
|
529
|
+
// partial text AND an envelope would break the one-document contract. So the
|
|
530
|
+
// capture goes to stderr, where the operator can still read it, and stdout
|
|
531
|
+
// carries a complete `gate-timeout` envelope instead.
|
|
532
|
+
//
|
|
533
|
+
// The DECISION lives in `publishGateOutcome` (gate-helpers.mjs) and only the
|
|
534
|
+
// WRITES live here: this branch needs a real 16-minute gate to reach, so while
|
|
535
|
+
// the decision was inline it was pinned by nothing.
|
|
536
|
+
const outcome = publishGateOutcome({ result, variant, timeoutMs: gateTimeoutMs });
|
|
537
|
+
const gateStdout = result.timedOut ? '' : result.fullOutput;
|
|
538
|
+
if (outcome.stderr) process.stderr.write(outcome.stderr);
|
|
539
|
+
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
540
|
+
for (const line of outcome.warnings) warn(line);
|
|
541
|
+
|
|
402
542
|
// Quality-gate telemetry — one canonical event per gate run via emitEvent
|
|
403
543
|
// (single emission path). `sessionAttribution` is the shared helper in
|
|
404
544
|
// events.mjs (#941); this CLI wrapper runs against the CWD `repoRoot`, so the
|
|
@@ -433,8 +573,7 @@ if (result.error && typeof result.status !== 'number') {
|
|
|
433
573
|
//
|
|
434
574
|
// Best-effort: a telemetry failure must NEVER alter the gate's authoritative
|
|
435
575
|
// exit code — which is why the counts parse also lives inside this try.
|
|
436
|
-
const exitCode =
|
|
437
|
-
const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
|
|
576
|
+
const exitCode = outcome.exitCode;
|
|
438
577
|
try {
|
|
439
578
|
const counts = suiteCountsFromGateStdout(gateStdout);
|
|
440
579
|
// The names behind `counts.failed`. Absent, never `[]` — see
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
43
43
|
import { join, resolve } from 'node:path';
|
|
44
44
|
import { parseArgs } from 'node:util';
|
|
45
|
-
import { fileURLToPath } from 'node:url';
|
|
46
45
|
import { resolveInstructionFile } from './lib/common.mjs';
|
|
47
46
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
48
47
|
import { resolveAndRecordSessionShape } from './lib/session-shape.mjs';
|
|
48
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
49
49
|
|
|
50
50
|
const USAGE = [
|
|
51
51
|
'Usage: node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep>',
|
|
@@ -258,7 +258,7 @@ async function main() {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// Entrypoint guard — importing this file must not run it (check-unwired-features S3).
|
|
261
|
-
if (
|
|
261
|
+
if (isMainModule(import.meta.url)) {
|
|
262
262
|
main().catch((err) => {
|
|
263
263
|
process.stderr.write(`session-shape: unexpected error: ${err?.stack ?? err}\n`);
|
|
264
264
|
process.exit(EXIT_CONFIG_IO);
|
package/scripts/site-numbers.mjs
CHANGED
|
@@ -101,10 +101,11 @@
|
|
|
101
101
|
import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
102
102
|
import { join, resolve, relative } from 'node:path';
|
|
103
103
|
import { execFileSync } from 'node:child_process';
|
|
104
|
-
import { fileURLToPath } from 'node:url';
|
|
105
104
|
|
|
106
105
|
import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
|
|
107
106
|
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
107
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
108
|
+
import { slashCommandNames } from './lib/user-invocable-skills.mjs';
|
|
108
109
|
|
|
109
110
|
/** Machine-readable schema tag for the --json envelope. */
|
|
110
111
|
export const SCHEMA = 'site-numbers/1';
|
|
@@ -169,11 +170,37 @@ export function countSkills(root) {
|
|
|
169
170
|
return n;
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* The product's "slash commands" number: `commands/*.md` ∪ skills carrying an
|
|
175
|
+
* EXPLICIT `user-invocable: true`, deduplicated by name.
|
|
176
|
+
*
|
|
177
|
+
* NOT `ls commands/*.md | wc -l` any more, and the change is not cosmetic. After
|
|
178
|
+
* the 2026-09-16 fold (#1370) `commands/` holds two files — `session` and
|
|
179
|
+
* `templates-ack`, the only two names a skill cannot carry — while 26 operator-
|
|
180
|
+
* facing slash commands exist. A command file only ever resolved because a
|
|
181
|
+
* same-named skill existed, and the twin additionally listed the entry twice in
|
|
182
|
+
* the `/` picker; the skill frontmatter flag is what makes a slash command a
|
|
183
|
+
* slash command now. Counting the directory would have reported 2.
|
|
184
|
+
*
|
|
185
|
+
* Dedup by name is load-bearing: a name present on BOTH sides is the picker-
|
|
186
|
+
* duplicate defect (`tests/commands/headless-bare-command-availability.test.mjs`
|
|
187
|
+
* fails on it), so it must count once here rather than inflate the tile.
|
|
188
|
+
*
|
|
189
|
+
* null (→ the tile renders as unmeasured) only when NEITHER `commands/` nor
|
|
190
|
+
* `skills/` exists — i.e. this is not a plugin tree at all. A tree with one of
|
|
191
|
+
* the two counts what it has; a missing `commands/` is now the NORMAL state, not
|
|
192
|
+
* a missing measurement.
|
|
193
|
+
*
|
|
194
|
+
* NO divergence from `skills/claude-md-drift-check/checker.mjs` `command-count`:
|
|
195
|
+
* that checker unions the same user-invocable skills (`checker.mjs:435-446`,
|
|
196
|
+
* measured 2026-09-17), and both routes reach the same verdict through the same
|
|
197
|
+
* SSOT — `isUserInvocableValue` in `scripts/lib/user-invocable-skills.mjs`. The
|
|
198
|
+
* checker stays GENERIC over consumer repos only in the sense that a repo
|
|
199
|
+
* without `skills/` sees the union collapse to the directory count on its own.
|
|
200
|
+
*/
|
|
173
201
|
export function countCommands(root) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return readdirSync(dir).filter((f) => f.endsWith('.md') && !f.startsWith('.')).length;
|
|
202
|
+
if (!isDir(join(root, 'commands')) && !isDir(join(root, 'skills'))) return null;
|
|
203
|
+
return slashCommandNames(root).length;
|
|
177
204
|
}
|
|
178
205
|
|
|
179
206
|
/**
|
|
@@ -400,7 +427,8 @@ export const METRIC_DEFS = Object.freeze([
|
|
|
400
427
|
{
|
|
401
428
|
id: 'commands',
|
|
402
429
|
provenance: false,
|
|
403
|
-
source:
|
|
430
|
+
source:
|
|
431
|
+
'ls commands/*.md | wc -l + grep -l "^user-invocable: true" skills/*/SKILL.md | wc -l (union by name — see countCommands)',
|
|
404
432
|
compute: (root) => fmtCount(countCommands(root)),
|
|
405
433
|
},
|
|
406
434
|
{
|
|
@@ -1398,11 +1426,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
|
|
|
1398
1426
|
return exitCode;
|
|
1399
1427
|
}
|
|
1400
1428
|
|
|
1401
|
-
const isMain =
|
|
1402
|
-
typeof process !== 'undefined' &&
|
|
1403
|
-
process.argv[1] !== null &&
|
|
1404
|
-
process.argv[1] !== undefined &&
|
|
1405
|
-
resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
|
|
1429
|
+
const isMain =isMainModule(import.meta.url);
|
|
1406
1430
|
|
|
1407
1431
|
if (isMain) {
|
|
1408
1432
|
const argv = process.argv.slice(2);
|