session-orchestrator 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +202 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
package/scripts/autopilot.mjs
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* --max-sessions=N Max iterations (1..50, default 5).
|
|
15
15
|
* --max-hours=H Max wall-clock hours (0.5..24.0, default 4.0).
|
|
16
16
|
* --confidence-threshold=0.X Mode confidence gate (0.0..1.0, default 0.85).
|
|
17
|
+
* --max-tokens=N Cumulative token budget (0..10000000, default 0 = off). Counts
|
|
18
|
+
* SUBAGENT output tokens only (sessions.jsonl total_token_output,
|
|
19
|
+
* else total_tokens — both subagent-only; coordinator tokens are
|
|
20
|
+
* not counted). One deep session measured ~450k.
|
|
17
21
|
* --dry-run Emit a single record without spawning sessions.
|
|
18
22
|
* --verbose Pipe child process stdio (instead of inherit).
|
|
19
23
|
*
|
|
@@ -32,9 +36,10 @@
|
|
|
32
36
|
* "/autopilot Loop Command (Phase C)" (#277/#271; archived in the private Meta-Vault)
|
|
33
37
|
*/
|
|
34
38
|
|
|
35
|
-
import {
|
|
39
|
+
import { randomUUID } from 'node:crypto';
|
|
36
40
|
import { spawnSync, spawn } from 'node:child_process';
|
|
37
41
|
import { resolve } from 'node:path';
|
|
42
|
+
import { fileURLToPath } from 'node:url';
|
|
38
43
|
|
|
39
44
|
import {
|
|
40
45
|
runLoop,
|
|
@@ -47,7 +52,7 @@ import { selectMode } from './lib/mode-selector.mjs';
|
|
|
47
52
|
import { probe, evaluate, DEFAULT_RESOURCE_THRESHOLDS } from './lib/resource-probe.mjs';
|
|
48
53
|
import { detectPeers } from './lib/session-registry.mjs';
|
|
49
54
|
import { normalizeSession } from './lib/session-schema.mjs';
|
|
50
|
-
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
55
|
+
import { isBackfillStub, readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
51
56
|
|
|
52
57
|
// ---------------------------------------------------------------------------
|
|
53
58
|
// CLI-level flag extraction
|
|
@@ -55,6 +60,14 @@ import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
|
55
60
|
|
|
56
61
|
const argv = process.argv.slice(2);
|
|
57
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Plugin root, resolved from THIS file's own location (`<root>/scripts/autopilot.mjs`
|
|
65
|
+
* → `<root>`) and never from an env var. The driver is spawned unattended from an
|
|
66
|
+
* arbitrary cwd, so `--plugin-dir` must name the plugin that owns this very file;
|
|
67
|
+
* an env-var read would point wherever the operator's shell last happened to point.
|
|
68
|
+
*/
|
|
69
|
+
const PLUGIN_ROOT = resolve(fileURLToPath(new URL('.', import.meta.url)), '..');
|
|
70
|
+
|
|
58
71
|
const hasHeadless = argv.includes('--headless');
|
|
59
72
|
const hasVerbose = argv.includes('--verbose');
|
|
60
73
|
|
|
@@ -69,7 +82,7 @@ const flagsForParse = argv.filter(
|
|
|
69
82
|
(a) => a !== '--headless' && a !== '--verbose'
|
|
70
83
|
);
|
|
71
84
|
|
|
72
|
-
const { maxSessions, maxHours, confidenceThreshold, dryRun } = parseFlags(flagsForParse);
|
|
85
|
+
const { maxSessions, maxHours, confidenceThreshold, maxTokens, dryRun } = parseFlags(flagsForParse);
|
|
73
86
|
|
|
74
87
|
// ---------------------------------------------------------------------------
|
|
75
88
|
// Session Config (parsed once, shared by thresholds + decay readers)
|
|
@@ -84,10 +97,7 @@ const { maxSessions, maxHours, confidenceThreshold, dryRun } = parseFlags(flagsF
|
|
|
84
97
|
*/
|
|
85
98
|
function loadConfig() {
|
|
86
99
|
try {
|
|
87
|
-
const scriptPath =
|
|
88
|
-
new URL('.', import.meta.url).pathname,
|
|
89
|
-
'parse-config.mjs'
|
|
90
|
-
);
|
|
100
|
+
const scriptPath = fileURLToPath(new URL('parse-config.mjs', import.meta.url));
|
|
91
101
|
const result = spawnSync(
|
|
92
102
|
process.execPath,
|
|
93
103
|
[scriptPath],
|
|
@@ -175,50 +185,49 @@ const branch = detectBranch();
|
|
|
175
185
|
const SESSIONS_JSONL_PATH = resolve('.orchestrator/metrics/sessions.jsonl');
|
|
176
186
|
|
|
177
187
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
188
|
+
* Read the child's canonical record by its raw harness UUID, independent of
|
|
189
|
+
* semantic session IDs and peer append order. Zero or multiple matches throw;
|
|
190
|
+
* only the unique record supplies normalized fields and finite token usage.
|
|
180
191
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* @returns {Set<string>}
|
|
192
|
+
* A unique record that is a BACKFILL STUB throws too (#1457 F2): a stub is a
|
|
193
|
+
* placeholder some backfill wrote because the child never closed — no waves,
|
|
194
|
+
* agents or token totals. Its usage is UNKNOWN, not 0, and its empty
|
|
195
|
+
* agent_summary/effectiveness would blind the post-session kill-switches as
|
|
196
|
+
* well, so it fails closed like the zero-match case above: the loop records
|
|
197
|
+
* kill_switch=failed-wave with a detail naming the stub, instead of counting a
|
|
198
|
+
* healthy 0-token iteration that silently undercounts --max-tokens.
|
|
199
|
+
* @returns {{session_id: string, agent_summary?: object, effectiveness?: object,
|
|
200
|
+
* usage?: {output_tokens?: number, total_tokens?: number}}}
|
|
191
201
|
*/
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
function readOwnSession(childSessionId) {
|
|
203
|
+
const own = readCanonicalSessions({ filePath: SESSIONS_JSONL_PATH }).filter((r) => r.raw_session_id === childSessionId);
|
|
204
|
+
if (own.length === 0) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`no session record carries raw_session_id=${childSessionId} (the child wrote none, or none stamped ` +
|
|
207
|
+
'with its raw id) — refusing to fall back to the ledger tail (#1457)'
|
|
208
|
+
);
|
|
199
209
|
}
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Read the last non-empty line from sessions.jsonl, parse it, run it through
|
|
205
|
-
* normalizeSession, and project to the sessionRunner return shape.
|
|
206
|
-
*
|
|
207
|
-
* @returns {{session_id: string, agent_summary?: object, effectiveness?: object}}
|
|
208
|
-
*/
|
|
209
|
-
function readTailSession() {
|
|
210
|
-
const raw = readFileSync(SESSIONS_JSONL_PATH, 'utf8');
|
|
211
|
-
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
|
|
212
|
-
if (lines.length === 0) {
|
|
213
|
-
throw new Error('sessions.jsonl is empty after session completed');
|
|
210
|
+
if (own.length > 1) {
|
|
211
|
+
throw new Error(`ambiguous: ${own.length} canonical records carry raw_session_id=${childSessionId} (#1457)`);
|
|
214
212
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
if (isBackfillStub(own[0])) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`the only record with raw_session_id=${childSessionId} is a backfill stub ` +
|
|
216
|
+
`(_backfill_source=${own[0]._backfill_source}, no waves/agents/token totals) — token usage unknown, ` +
|
|
217
|
+
'refusing to count it as a 0-token iteration (#1457 F2)'
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const normalized = normalizeSession(own[0]);
|
|
221
|
+
const result = {
|
|
218
222
|
session_id: normalized.session_id,
|
|
219
223
|
agent_summary: normalized.agent_summary,
|
|
220
224
|
effectiveness: normalized.effectiveness,
|
|
221
225
|
};
|
|
226
|
+
const usage = {};
|
|
227
|
+
if (Number.isFinite(normalized.total_token_output)) usage.output_tokens = normalized.total_token_output;
|
|
228
|
+
if (Number.isFinite(normalized.total_tokens)) usage.total_tokens = normalized.total_tokens;
|
|
229
|
+
if (Object.keys(usage).length > 0) result.usage = usage;
|
|
230
|
+
return result;
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
// ---------------------------------------------------------------------------
|
|
@@ -256,22 +265,30 @@ async function modeSelector() {
|
|
|
256
265
|
|
|
257
266
|
/**
|
|
258
267
|
* sessionRunner — spawns `claude` with the recommended mode and awaits exit.
|
|
259
|
-
*
|
|
268
|
+
* Sets a fresh raw harness UUID so a peer's later ledger append is never read
|
|
269
|
+
* as the child's record (emit-session stamps that UUID as `raw_session_id`).
|
|
270
|
+
* After exit 0, returns its unique canonical record; zero or multiple matches
|
|
271
|
+
* throw into the loop's failed-wave channel.
|
|
260
272
|
*
|
|
261
273
|
* @param {{mode: string, autopilotRunId: string}} args
|
|
262
274
|
* @returns {Promise<{session_id: string, agent_summary?: object, effectiveness?: object}>}
|
|
263
275
|
*/
|
|
264
276
|
async function sessionRunner({ mode, autopilotRunId }) {
|
|
265
|
-
const
|
|
277
|
+
const childSessionId = randomUUID();
|
|
266
278
|
|
|
267
279
|
await new Promise((res, rej) => {
|
|
268
280
|
const childStdio = hasVerbose
|
|
269
281
|
? ['ignore', 'pipe', 'pipe']
|
|
270
282
|
: ['ignore', 'inherit', 'inherit'];
|
|
271
283
|
|
|
284
|
+
// `session` is a RESERVED terminal-only built-in name under `claude -p`: the
|
|
285
|
+
// bare `/session <mode>` form answers "/session isn't available in this
|
|
286
|
+
// environment." and the child exits having done nothing (measured 2026-09-16,
|
|
287
|
+
// claude 2.1.273 — `commands/session.md` § Headless). Only the namespaced form
|
|
288
|
+
// resolves, and only when the plugin that defines it is on `--plugin-dir`.
|
|
272
289
|
const child = spawn(
|
|
273
290
|
'claude',
|
|
274
|
-
['-p', `/session ${mode}
|
|
291
|
+
['-p', `/session-orchestrator:session ${mode}`, '--plugin-dir', PLUGIN_ROOT, '--session-id', childSessionId],
|
|
275
292
|
{
|
|
276
293
|
env: { ...process.env, AUTOPILOT_RUN_ID: autopilotRunId },
|
|
277
294
|
stdio: childStdio,
|
|
@@ -294,13 +311,7 @@ async function sessionRunner({ mode, autopilotRunId }) {
|
|
|
294
311
|
});
|
|
295
312
|
});
|
|
296
313
|
|
|
297
|
-
|
|
298
|
-
const appeared = [...postIds].some((id) => !preIds.has(id));
|
|
299
|
-
if (!appeared) {
|
|
300
|
-
throw new Error('no session record appended');
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return readTailSession();
|
|
314
|
+
return readOwnSession(childSessionId);
|
|
304
315
|
}
|
|
305
316
|
|
|
306
317
|
/**
|
|
@@ -372,12 +383,18 @@ async function main() {
|
|
|
372
383
|
maxSessions,
|
|
373
384
|
maxHours,
|
|
374
385
|
confidenceThreshold,
|
|
386
|
+
maxTokens,
|
|
375
387
|
dryRun,
|
|
376
388
|
modeSelector,
|
|
377
389
|
sessionRunner,
|
|
378
390
|
resourceEvaluator,
|
|
379
391
|
peerCounter,
|
|
380
392
|
abortSignal: controller.signal,
|
|
393
|
+
// STALL_TIMEOUT progress marker. `autopilot.jsonl` is written ONCE per
|
|
394
|
+
// invocation (after the loop), so its mtime belongs to the PREVIOUS run and
|
|
395
|
+
// fired the kill-switch after iteration 1; the session.lock heartbeat is
|
|
396
|
+
// refreshed while a session is alive. See stall-sampler.mjs § PRECEDENCE.
|
|
397
|
+
sessionLockPath: resolve('.orchestrator/session.lock'),
|
|
381
398
|
runId: autopilotRunId,
|
|
382
399
|
branch: branch ?? undefined,
|
|
383
400
|
});
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* backfill-abandoned-sessions.mjs — one-time historical migration CLI (#724 C1).
|
|
4
4
|
*
|
|
5
|
-
* Scans `.orchestrator/metrics/events.jsonl`
|
|
5
|
+
* Scans `.orchestrator/metrics/events.jsonl` — and every rotated archive
|
|
6
|
+
* beside it (#1414) — for every distinct
|
|
6
7
|
* `orchestrator.session.started` UUID that has no counterpart in
|
|
7
8
|
* `.orchestrator/metrics/sessions.jsonl`, bridges each to its semantic id via
|
|
8
9
|
* `orchestrator.session.lock.acquired` where available, and synthesizes a
|
|
@@ -41,7 +42,7 @@ import path from 'node:path';
|
|
|
41
42
|
import { parseArgs } from 'node:util';
|
|
42
43
|
|
|
43
44
|
import { backfillAbandonedSession, isUuid } from './lib/session-close-backfill.mjs';
|
|
44
|
-
import { emitEvent } from './lib/events.mjs';
|
|
45
|
+
import { SCAN_CHUNK_BYTES, emitEvent, listEventSourcesNewestFirst } from './lib/events.mjs';
|
|
45
46
|
import { getProjectDir } from './lib/platform.mjs';
|
|
46
47
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
47
48
|
import { isLockLive, readLock } from './lib/session-lock.mjs';
|
|
@@ -125,7 +126,7 @@ function readJsonl(filePath) {
|
|
|
125
126
|
* @returns {Array<{ sessionId: string, semanticSessionId: string|null }>}
|
|
126
127
|
*/
|
|
127
128
|
export function planSessions({ repoRoot }) {
|
|
128
|
-
const
|
|
129
|
+
const eventsPath = path.join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl');
|
|
129
130
|
|
|
130
131
|
// Two independent UUID -> semantic bridges (#1167). `lock.acquired` is the
|
|
131
132
|
// original one, but a session that LOST the lock-acquire race never emits it;
|
|
@@ -137,29 +138,130 @@ export function planSessions({ repoRoot }) {
|
|
|
137
138
|
// lock.acquired keeps precedence — it is the older, mode-carrying attestation.
|
|
138
139
|
const semanticFromLock = new Map();
|
|
139
140
|
const semanticFromEnded = new Map();
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
/** Distinct `session.started` ids in FIRST-SEEN (chronological) order. */
|
|
143
|
+
const startedIds = [];
|
|
144
|
+
|
|
145
|
+
// #1414 — the whole history, OLDEST SOURCE FIRST, streamed line by line. The
|
|
146
|
+
// previous `readJsonl` of the active file alone missed every session whose
|
|
147
|
+
// `session.started` had been rotated into `_archive/`; reading the same
|
|
148
|
+
// sources through `readEventsWithRotations` instead would have loaded up to
|
|
149
|
+
// ~60 MB into memory on a path that runs at SESSION START
|
|
150
|
+
// (`hooks/on-session-start.mjs` → `backfillOnSessionStart`). Reversing the
|
|
151
|
+
// newest-first source list keeps first-seen order chronological, exactly as
|
|
152
|
+
// the single-file read produced it.
|
|
153
|
+
for (const source of [...listEventSourcesNewestFirst({ filePath: eventsPath })].reverse()) {
|
|
154
|
+
forEachJsonlRecord(source.path, (ev) => {
|
|
155
|
+
if (!ev || typeof ev !== 'object') return;
|
|
156
|
+
if (typeof ev.session_id === 'string' && typeof ev.semantic_session_id === 'string') {
|
|
157
|
+
if (ev.event === LOCK_ACQUIRED) semanticFromLock.set(ev.session_id, ev.semantic_session_id);
|
|
158
|
+
else if (ev.event === SESSION_ENDED) {
|
|
159
|
+
semanticFromEnded.set(ev.session_id, ev.semantic_session_id);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (ev.event !== SESSION_STARTED || typeof ev.session_id !== 'string') return;
|
|
163
|
+
if (seen.has(ev.session_id)) return;
|
|
164
|
+
seen.add(ev.session_id);
|
|
165
|
+
startedIds.push(ev.session_id);
|
|
166
|
+
});
|
|
144
167
|
}
|
|
168
|
+
|
|
169
|
+
// Resolved AFTER the pass, never during it: a `lock.acquired` / `session.ended`
|
|
170
|
+
// bridge may sit later in the stream (or in a later source) than the
|
|
171
|
+
// `session.started` it names, and deciding mid-stream would mint a synthetic
|
|
172
|
+
// id for a session whose semantic id was two lines further on.
|
|
145
173
|
const semanticByUuid = new Map([...semanticFromEnded, ...semanticFromLock]);
|
|
146
174
|
|
|
147
175
|
const runningIds = readRunningSessionIds(repoRoot);
|
|
148
176
|
|
|
149
|
-
const seen = new Set();
|
|
150
177
|
const plan = [];
|
|
151
|
-
for (const
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
seen.add(ev.session_id);
|
|
155
|
-
const semanticSessionId = semanticByUuid.get(ev.session_id) ?? null;
|
|
156
|
-
if (runningIds.has(ev.session_id)
|
|
178
|
+
for (const sessionId of startedIds) {
|
|
179
|
+
const semanticSessionId = semanticByUuid.get(sessionId) ?? null;
|
|
180
|
+
if (runningIds.has(sessionId)
|
|
157
181
|
|| (semanticSessionId !== null && runningIds.has(semanticSessionId))) continue;
|
|
158
|
-
plan.push({ sessionId
|
|
182
|
+
plan.push({ sessionId, semanticSessionId });
|
|
159
183
|
}
|
|
160
184
|
return plan;
|
|
161
185
|
}
|
|
162
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Stream one JSONL file, handing every PARSED record to `onRecord` in file
|
|
189
|
+
* order — without ever holding the file in memory (#1414).
|
|
190
|
+
*
|
|
191
|
+
* MISSING (ENOENT) → silent no-op; UNREADABLE → a stderr WARN, same split as
|
|
192
|
+
* {@link readJsonl} (#1188: a missing ledger is the fresh-repo case, an
|
|
193
|
+
* unreadable one used to read as "no records" and made every count below
|
|
194
|
+
* silently wrong). Malformed lines are skipped, as in the full-read path.
|
|
195
|
+
*
|
|
196
|
+
* A line split across a chunk boundary is CARRIED as a BUFFER, so neither a
|
|
197
|
+
* record nor a multibyte character is ever cut in half — the 0x0A byte cannot
|
|
198
|
+
* occur inside a UTF-8 continuation sequence.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} filePath
|
|
201
|
+
* @param {(record: object) => void} onRecord
|
|
202
|
+
*/
|
|
203
|
+
function forEachJsonlRecord(filePath, onRecord) {
|
|
204
|
+
const emit = (buf) => {
|
|
205
|
+
const text = buf.toString('utf8').trim();
|
|
206
|
+
if (!text) return;
|
|
207
|
+
try {
|
|
208
|
+
onRecord(JSON.parse(text));
|
|
209
|
+
} catch {
|
|
210
|
+
/* skip malformed */
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
let fd;
|
|
215
|
+
try {
|
|
216
|
+
fd = fs.openSync(filePath, 'r');
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (!err || err.code !== 'ENOENT') {
|
|
219
|
+
process.stderr.write(
|
|
220
|
+
`⚠ backfill-abandoned-sessions: cannot read ${filePath} ` +
|
|
221
|
+
`(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
|
|
222
|
+
'treating as EMPTY, counts below are floors\n',
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const size = fs.fstatSync(fd).size;
|
|
229
|
+
const buf = Buffer.alloc(SCAN_CHUNK_BYTES);
|
|
230
|
+
let pos = 0;
|
|
231
|
+
let carry = Buffer.alloc(0);
|
|
232
|
+
while (pos < size) {
|
|
233
|
+
const n = fs.readSync(fd, buf, 0, SCAN_CHUNK_BYTES, pos);
|
|
234
|
+
if (n <= 0) break;
|
|
235
|
+
pos += n;
|
|
236
|
+
const block =
|
|
237
|
+
carry.length > 0
|
|
238
|
+
? Buffer.concat([carry, buf.subarray(0, n)])
|
|
239
|
+
: Buffer.from(buf.subarray(0, n));
|
|
240
|
+
let start = 0;
|
|
241
|
+
let idx = block.indexOf(0x0a, start);
|
|
242
|
+
while (idx !== -1) {
|
|
243
|
+
emit(block.subarray(start, idx));
|
|
244
|
+
start = idx + 1;
|
|
245
|
+
idx = block.indexOf(0x0a, start);
|
|
246
|
+
}
|
|
247
|
+
carry = block.subarray(start);
|
|
248
|
+
}
|
|
249
|
+
if (carry.length > 0) emit(carry);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
process.stderr.write(
|
|
252
|
+
`⚠ backfill-abandoned-sessions: cannot read ${filePath} ` +
|
|
253
|
+
`(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
|
|
254
|
+
'treating as EMPTY, counts below are floors\n',
|
|
255
|
+
);
|
|
256
|
+
} finally {
|
|
257
|
+
try {
|
|
258
|
+
fs.closeSync(fd);
|
|
259
|
+
} catch {
|
|
260
|
+
/* best-effort */
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
163
265
|
/**
|
|
164
266
|
* The id(s) naming the session THIS process is, when that session holds the
|
|
165
267
|
* live lock — else the empty set (#1376). Never throws: an unreadable lock,
|
|
@@ -551,7 +653,7 @@ async function main() {
|
|
|
551
653
|
|
|
552
654
|
// --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
|
|
553
655
|
const apply = values.apply === true && values['dry-run'] !== true;
|
|
554
|
-
const repoRoot = values['repo-root'] || getProjectDir();
|
|
656
|
+
const repoRoot = path.resolve(values['repo-root'] || getProjectDir());
|
|
555
657
|
|
|
556
658
|
let assumeDeadBeforeMs = null;
|
|
557
659
|
if (typeof values['assume-dead-before'] === 'string' && values['assume-dead-before'].length > 0) {
|
|
@@ -10,7 +10,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
10
10
|
for (let index = 0; index < argv.length; index++) {
|
|
11
11
|
const arg = argv[index];
|
|
12
12
|
if (arg === '--help' || arg === '-h') {
|
|
13
|
-
process.stdout.write('Usage: node scripts/baseline-archetypes.mjs [--repo PATH] [--archetype ID]\nOffline, read-only JSON lookup. Exit 0: public/private; exit 2: invalid configuration or contract.\n');
|
|
13
|
+
process.stdout.write('Usage: node scripts/baseline-archetypes.mjs [--repo PATH] [--archetype ID]\nOffline, read-only JSON lookup. Exit 0: public/private; exit 2: invalid configuration or contract.\nExit 2 reason producer-missing: the baseline directory exists but has no scripts/archetype-manifest.mjs exporter.\n');
|
|
14
14
|
return 0;
|
|
15
15
|
}
|
|
16
16
|
if (!['--repo', '--archetype'].includes(arg) || !argv[index + 1] || argv[index + 1].startsWith('--')) {
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
// (warn locally; upgraded to a hard failure under CI)
|
|
26
26
|
// c1 pnpm-lock.yaml must NOT be tracked in git
|
|
27
27
|
// c2 package-lock.json MUST be tracked in git
|
|
28
|
+
// 3. Husky activation (#1458, advisory only — never affects `ok`/exit code):
|
|
29
|
+
// h1 `.husky/` exists but git core.hooksPath is not `.husky/_` — the
|
|
30
|
+
// `prepare: husky` script never ran because ignore-scripts=true,
|
|
31
|
+
// so commit-msg/pre-commit/pre-push are inactive. Warns locally
|
|
32
|
+
// and recommends `npx husky`; silent under CI.
|
|
28
33
|
//
|
|
29
34
|
// SCOPE GUARD:
|
|
30
35
|
// This script only guards the REPOSITORY ROOT package.json. If invoked
|
|
@@ -64,8 +69,8 @@
|
|
|
64
69
|
// on any dependency install).
|
|
65
70
|
|
|
66
71
|
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
67
|
-
import { dirname, join, resolve } from 'node:path';
|
|
68
|
-
import { spawnSync } from 'node:child_process';
|
|
72
|
+
import { dirname, isAbsolute, join, posix, resolve } from 'node:path';
|
|
73
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
69
74
|
import { fileURLToPath } from 'node:url';
|
|
70
75
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
71
76
|
|
|
@@ -266,11 +271,53 @@ export function parseCiEnv(value) {
|
|
|
266
271
|
return !['', '0', 'false', 'no'].includes(normalized);
|
|
267
272
|
}
|
|
268
273
|
|
|
274
|
+
/** One-line stderr WARN for an inactive Husky install (#1458). */
|
|
275
|
+
export const HUSKY_INACTIVE_WARNING =
|
|
276
|
+
'⚠ check-package-manager: git core.hooksPath is not set to .husky/_ — Husky hooks (commit-msg, pre-commit, pre-push) are INACTIVE in this clone because .npmrc ignore-scripts=true skips the prepare script (#1458). Run: npx husky';
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Advisory check (#1458): `.npmrc` `ignore-scripts=true` (SEC-020) means the
|
|
280
|
+
* `prepare: husky` script never runs on `npm ci`, so a fresh clone has no
|
|
281
|
+
* `core.hooksPath` and every Husky hook is silently inactive. This only
|
|
282
|
+
* REPORTS the state — it never runs `npx husky`, never writes git config and
|
|
283
|
+
* never affects the guard's `ok`/exit code. Silent under CI.
|
|
284
|
+
* @param {string} repoRoot
|
|
285
|
+
* @param {{ env?: NodeJS.ProcessEnv, ci?: boolean }} [opts] — `env` is also
|
|
286
|
+
* passed to the git subprocess (lets tests isolate from global git config).
|
|
287
|
+
* @returns {{ hooksPath: string, active: boolean, skipped: 'ci'|'no-husky-dir'|null }}
|
|
288
|
+
*/
|
|
289
|
+
export function checkHuskyHooksPath(repoRoot, { env = process.env, ci } = {}) {
|
|
290
|
+
if (ci ?? parseCiEnv(env.CI)) return { hooksPath: '', active: false, skipped: 'ci' };
|
|
291
|
+
if (!existsSync(join(repoRoot, '.husky'))) {
|
|
292
|
+
return { hooksPath: '', active: false, skipped: 'no-husky-dir' };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let hooksPath;
|
|
296
|
+
try {
|
|
297
|
+
hooksPath = execFileSync('git', ['-C', repoRoot, 'config', '--get', 'core.hooksPath'], {
|
|
298
|
+
encoding: 'utf8',
|
|
299
|
+
env,
|
|
300
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
301
|
+
}).trim();
|
|
302
|
+
} catch {
|
|
303
|
+
// Non-zero exit (key unset) or git unavailable — treat as unset.
|
|
304
|
+
hooksPath = '';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const norm = hooksPath.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
308
|
+
const active =
|
|
309
|
+
norm !== '' &&
|
|
310
|
+
(isAbsolute(hooksPath) ? norm.endsWith('/.husky/_') : posix.normalize(norm) === '.husky/_');
|
|
311
|
+
|
|
312
|
+
return { hooksPath, active, skipped: null };
|
|
313
|
+
}
|
|
314
|
+
|
|
269
315
|
/**
|
|
270
316
|
* Runs the full guard and returns a plain-object result. Pure(ish) — the
|
|
271
317
|
* only side effects are read-only fs/git calls. Never throws or exits.
|
|
272
|
-
*
|
|
273
|
-
* @
|
|
318
|
+
* `hooks` carries the advisory Husky check (#1458); it never affects `ok`.
|
|
319
|
+
* @param {{ cwd?: string, ci?: boolean, allowMissingNodeModules?: boolean, repoRoot?: string, env?: NodeJS.ProcessEnv }} [opts]
|
|
320
|
+
* @returns {{ ok: boolean, ci: boolean, skipped: boolean, repoRoot: string, findings: Array<{code: string, severity: string, effectiveSeverity: string, message: string}>, hooks?: { hooksPath: string, active: boolean, skipped: 'ci'|'no-husky-dir'|null } }}
|
|
274
321
|
*/
|
|
275
322
|
export function runPackageManagerGuard(opts = {}) {
|
|
276
323
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -302,7 +349,9 @@ export function runPackageManagerGuard(opts = {}) {
|
|
|
302
349
|
|
|
303
350
|
const ok = !findings.some((f) => f.effectiveSeverity === 'error');
|
|
304
351
|
|
|
305
|
-
|
|
352
|
+
const hooks = checkHuskyHooksPath(repoRoot, { env: opts.env ?? process.env, ci });
|
|
353
|
+
|
|
354
|
+
return { ok, ci, skipped: false, repoRoot, findings, hooks };
|
|
306
355
|
}
|
|
307
356
|
|
|
308
357
|
// ---------------------------------------------------------------------------
|
|
@@ -332,6 +381,9 @@ CHECKS
|
|
|
332
381
|
(warn locally; upgraded to a hard failure under CI, fail-closed)
|
|
333
382
|
c1 pnpm-lock.yaml must NOT be tracked in git
|
|
334
383
|
c2 package-lock.json MUST be tracked in git
|
|
384
|
+
Husky activation (advisory, never changes the exit code; silent under CI):
|
|
385
|
+
h1 .husky/ exists but git core.hooksPath is not .husky/_ (#1458) —
|
|
386
|
+
run \`npx husky\` once (ignore-scripts=true skips \`prepare\`)
|
|
335
387
|
|
|
336
388
|
OPTIONS
|
|
337
389
|
--allow-missing-node-modules Document that node_modules may not exist yet
|
|
@@ -412,6 +464,11 @@ if (isMain) {
|
|
|
412
464
|
|
|
413
465
|
const result = runPackageManagerGuard({ allowMissingNodeModules: opts.allowMissingNodeModules });
|
|
414
466
|
|
|
467
|
+
// Advisory only (#1458) — never changes the exit code.
|
|
468
|
+
if (result.hooks && !result.hooks.active && result.hooks.skipped === null) {
|
|
469
|
+
console.error(HUSKY_INACTIVE_WARNING);
|
|
470
|
+
}
|
|
471
|
+
|
|
415
472
|
if (opts.json) {
|
|
416
473
|
console.log(JSON.stringify(result));
|
|
417
474
|
process.exit(result.ok ? 0 : 1);
|