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
|
@@ -22,8 +22,11 @@
|
|
|
22
22
|
* Here `src` is defined by NEGATION, so a new top-level directory joins the
|
|
23
23
|
* denominator the moment it is committed, with no rule edit and no re-derivation.
|
|
24
24
|
*
|
|
25
|
-
* universe `git ls-files`, filtered to CODE_EXTENSIONS
|
|
26
|
-
*
|
|
25
|
+
* universe `git ls-files`, filtered to CODE_EXTENSIONS, minus declaration
|
|
26
|
+
* files (`*.d.ts`, `*.d.mts`, `*.d.cts` — types, not executable code)
|
|
27
|
+
* numerator tracked code under a top-level `tests/`, plus co-located tests
|
|
28
|
+
* anywhere: basename `*.test.<ext>` / `*.spec.<ext>`, or any file
|
|
29
|
+
* under a `__tests__/` directory
|
|
27
30
|
* denominator every OTHER tracked code file
|
|
28
31
|
*
|
|
29
32
|
* ## The four questions, answered in code rather than left open
|
|
@@ -37,8 +40,8 @@
|
|
|
37
40
|
* Only git-tracked? YES — reproducible at a SHA, and it is
|
|
38
41
|
* what keeps node_modules/, coverage/ and
|
|
39
42
|
* untracked scratch out without an ignore list.
|
|
40
|
-
* Do `tests/fixtures/` count as test LOC? YES when they are code (
|
|
41
|
-
*
|
|
43
|
+
* Do `tests/fixtures/` count as test LOC? YES when they are code (any of
|
|
44
|
+
* CODE_EXTENSIONS). A code fixture is maintained
|
|
42
45
|
* code that exists only to serve the suite —
|
|
43
46
|
* that is test-corpus cost. Non-code
|
|
44
47
|
* fixtures (`.json`, `.jsonl`, `.md`) are
|
|
@@ -62,7 +65,9 @@
|
|
|
62
65
|
* <repo-root> defaults to process.cwd()
|
|
63
66
|
* --json emit a single JSON object on stdout, nothing else
|
|
64
67
|
* --check exit 1 when the ratio exceeds the ceiling (consolidation
|
|
65
|
-
* wave required); exit
|
|
68
|
+
* wave required); exit 3 when the ratio is not measurable
|
|
69
|
+
* (`reason: 'no-tests-found'`, #1451); exit 0 when inside
|
|
70
|
+
* the corridor
|
|
66
71
|
* --ceiling <n> override the TV-003 ceiling (default 1.60)
|
|
67
72
|
* --stdin take newline-separated paths from stdin instead of
|
|
68
73
|
* enumerating via `git ls-files` (test seam / staged-only mode)
|
|
@@ -71,6 +76,12 @@
|
|
|
71
76
|
* 0 — measurement completed (and, under --check, ratio is within the corridor)
|
|
72
77
|
* 1 — --check only: ratio exceeds the ceiling
|
|
73
78
|
* 2 — tool error (missing/unreadable root, bad argv)
|
|
79
|
+
* 3 — --check only: ratio not measurable — 0 test files counted while test-like
|
|
80
|
+
* files ARE tracked (`reason: 'no-tests-found'`, e.g. a Python repo whose
|
|
81
|
+
* `tests/test_*.py` sit outside CODE_EXTENSIONS). Fail-closed since #1451:
|
|
82
|
+
* before, such a repo read `ratio 0, withinCorridor true` — "not found"
|
|
83
|
+
* reported as "in corridor". TypeScript tests count since the 2026-10-01
|
|
84
|
+
* owner decision on #1451; other stacks still get "not measurable".
|
|
74
85
|
*/
|
|
75
86
|
|
|
76
87
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
@@ -90,23 +101,65 @@ import { isMainModule } from './is-main-module.mjs';
|
|
|
90
101
|
* Applied symmetrically to numerator and denominator by construction — a file
|
|
91
102
|
* type can never inflate one side while being invisible to the other.
|
|
92
103
|
*/
|
|
93
|
-
export const CODE_EXTENSIONS = Object.freeze([
|
|
104
|
+
export const CODE_EXTENSIONS = Object.freeze([
|
|
105
|
+
'.mjs',
|
|
106
|
+
'.js',
|
|
107
|
+
'.cjs',
|
|
108
|
+
'.jsx',
|
|
109
|
+
'.ts',
|
|
110
|
+
'.tsx',
|
|
111
|
+
'.mts',
|
|
112
|
+
'.cts',
|
|
113
|
+
]);
|
|
94
114
|
|
|
95
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* TypeScript declaration files end in a CODE_EXTENSION but carry only types —
|
|
117
|
+
* no executable line a test could exercise. Excluded from BOTH sides, so a
|
|
118
|
+
* generated `.d.ts` can never move the ratio.
|
|
119
|
+
*/
|
|
120
|
+
export const DECLARATION_SUFFIXES = Object.freeze(['.d.ts', '.d.mts', '.d.cts']);
|
|
121
|
+
|
|
122
|
+
/** Top-level path prefix that makes a tracked code file part of the TEST corpus. */
|
|
96
123
|
export const TEST_PREFIX = 'tests/';
|
|
97
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Co-located test files, wherever they sit: a `*.test.<ext>` / `*.spec.<ext>`
|
|
127
|
+
* basename (the vitest/jest default include shape) or a `__tests__/` segment.
|
|
128
|
+
*/
|
|
129
|
+
const COLOCATED_TEST_RE = /\.(test|spec)\.[^./]+$|(^|\/)__tests__\//;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A tracked path that LOOKS like a test, counted or not: a `*.test.*` / `*.spec.*`
|
|
133
|
+
* JS/TS file, anything under a `__tests__/` segment, or any file under `tests/`
|
|
134
|
+
* regardless of extension. Used only to tell "this repo has no tests" apart from
|
|
135
|
+
* "this repo has tests the recipe cannot see" (#1451) — e.g. `tests/test_x.py`.
|
|
136
|
+
*/
|
|
137
|
+
const TEST_LIKE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {string} relPath repo-relative path
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
export function isTestLikePath(relPath) {
|
|
144
|
+
const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
145
|
+
return TEST_LIKE_RE.test(p) || p.startsWith(TEST_PREFIX) || /(^|\/)__tests__\//.test(p);
|
|
146
|
+
}
|
|
147
|
+
|
|
98
148
|
/** The TV-003 ceiling. Exceeding it switches the consolidation rule on. */
|
|
99
149
|
export const DEFAULT_CEILING = 1.6;
|
|
100
150
|
|
|
101
151
|
/** Machine-readable schema tag for the --json envelope. */
|
|
102
|
-
export const SCHEMA = 'tests-src-ratio/
|
|
152
|
+
export const SCHEMA = 'tests-src-ratio/2';
|
|
103
153
|
|
|
104
154
|
/**
|
|
105
155
|
* Which bucket a repo-relative path belongs to.
|
|
106
156
|
*
|
|
107
|
-
* `src` is defined by NEGATION — every tracked code file that is not
|
|
108
|
-
* `tests
|
|
109
|
-
* globs that can overlap or leave a gap.
|
|
157
|
+
* `src` is defined by NEGATION — every tracked code file that is not a test
|
|
158
|
+
* (under `tests/`, or co-located per COLOCATED_TEST_RE). That is what makes the
|
|
159
|
+
* two buckets a partition rather than two globs that can overlap or leave a gap.
|
|
160
|
+
*
|
|
161
|
+
* Known blind spot: a nested `pkg/tests/helper.ts` that is not itself named
|
|
162
|
+
* `*.test.*` counts as src — only the TOP-LEVEL `tests/` is a test directory.
|
|
110
163
|
*
|
|
111
164
|
* @param {string} relPath repo-relative path, `/`-separated
|
|
112
165
|
* @returns {'test'|'src'|null} null = outside the metric entirely
|
|
@@ -114,7 +167,8 @@ export const SCHEMA = 'tests-src-ratio/1';
|
|
|
114
167
|
export function classifyPath(relPath) {
|
|
115
168
|
const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
116
169
|
if (!CODE_EXTENSIONS.some((ext) => p.endsWith(ext))) return null;
|
|
117
|
-
|
|
170
|
+
if (DECLARATION_SUFFIXES.some((ext) => p.endsWith(ext))) return null;
|
|
171
|
+
return p.startsWith(TEST_PREFIX) || COLOCATED_TEST_RE.test(p) ? 'test' : 'src';
|
|
118
172
|
}
|
|
119
173
|
|
|
120
174
|
/**
|
|
@@ -154,7 +208,8 @@ export function countPhysicalLines(content) {
|
|
|
154
208
|
* @param {(relPath: string) => string|null} opts.readFile returns content, or null when unreadable
|
|
155
209
|
* @param {number} [opts.ceiling]
|
|
156
210
|
* @returns {{testFiles:number,testLoc:number,srcFiles:number,srcLoc:number,ratio:number|null,
|
|
157
|
-
* ceiling:number,withinCorridor:boolean,consolidationWaveRequired:boolean,
|
|
211
|
+
* ceiling:number,withinCorridor:boolean|null,consolidationWaveRequired:boolean|null,
|
|
212
|
+
* skipped:number,testLikeFilesSeen:number,reason:'no-tests-found'|null}}
|
|
158
213
|
*/
|
|
159
214
|
export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
160
215
|
let testFiles = 0;
|
|
@@ -162,8 +217,10 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
162
217
|
let srcFiles = 0;
|
|
163
218
|
let srcLoc = 0;
|
|
164
219
|
let skipped = 0;
|
|
220
|
+
let testLikeFilesSeen = 0;
|
|
165
221
|
|
|
166
222
|
for (const rel of files) {
|
|
223
|
+
if (isTestLikePath(rel)) testLikeFilesSeen++;
|
|
167
224
|
const bucket = classifyPath(rel);
|
|
168
225
|
if (bucket === null) continue;
|
|
169
226
|
const content = readFile(rel);
|
|
@@ -183,6 +240,26 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
183
240
|
|
|
184
241
|
// A repo with no src code has an undefined ratio, not an infinite one. Saying
|
|
185
242
|
// `null` keeps the consumer from reading Infinity as a corridor breach.
|
|
243
|
+
// Fail closed (#1451): a zero numerator next to tracked test-like files means
|
|
244
|
+
// the recipe cannot SEE this repo's tests (e.g. `tests/test_x.py`), not that
|
|
245
|
+
// there are none. Reporting ratio 0 / withinCorridor true there turned "not found"
|
|
246
|
+
// into "green". The verdict is unknown, so every verdict field is null.
|
|
247
|
+
if (testFiles === 0 && testLikeFilesSeen > 0) {
|
|
248
|
+
return {
|
|
249
|
+
testFiles,
|
|
250
|
+
testLoc,
|
|
251
|
+
srcFiles,
|
|
252
|
+
srcLoc,
|
|
253
|
+
ratio: null,
|
|
254
|
+
ceiling,
|
|
255
|
+
withinCorridor: null,
|
|
256
|
+
consolidationWaveRequired: null,
|
|
257
|
+
skipped,
|
|
258
|
+
testLikeFilesSeen,
|
|
259
|
+
reason: 'no-tests-found',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
186
263
|
const ratio = srcLoc === 0 ? null : Number((testLoc / srcLoc).toFixed(4));
|
|
187
264
|
const withinCorridor = ratio === null ? true : ratio <= ceiling;
|
|
188
265
|
|
|
@@ -196,6 +273,8 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
196
273
|
withinCorridor,
|
|
197
274
|
consolidationWaveRequired: !withinCorridor,
|
|
198
275
|
skipped,
|
|
276
|
+
testLikeFilesSeen,
|
|
277
|
+
reason: null,
|
|
199
278
|
};
|
|
200
279
|
}
|
|
201
280
|
|
|
@@ -208,11 +287,16 @@ export function definitionOf(source) {
|
|
|
208
287
|
return {
|
|
209
288
|
source: source === 'stdin' ? 'stdin path list' : 'git ls-files (tracked files only)',
|
|
210
289
|
codeExtensions: [...CODE_EXTENSIONS],
|
|
211
|
-
|
|
290
|
+
declarationsExcluded: [...DECLARATION_SUFFIXES],
|
|
291
|
+
numerator:
|
|
292
|
+
`tracked code files under a top-level ${TEST_PREFIX}, plus co-located tests anywhere: ` +
|
|
293
|
+
'basename *.test.<ext> / *.spec.<ext>, or any file under a __tests__/ directory',
|
|
212
294
|
denominator: `every OTHER tracked code file (src defined by negation, not by a directory list)`,
|
|
213
295
|
lineRule: 'physical lines; blank + comment lines counted; EOF-newline-insensitive',
|
|
214
296
|
excluded:
|
|
215
|
-
|
|
297
|
+
`declaration files (${DECLARATION_SUFFIXES.map((x) => `*${x}`).join(', ')}: types, not ` +
|
|
298
|
+
'executable code) and non-code extensions (.md, .json, .jsonl, .yml) on BOTH sides; ' +
|
|
299
|
+
'untracked files (node_modules/, coverage/, scratch)',
|
|
216
300
|
};
|
|
217
301
|
}
|
|
218
302
|
|
|
@@ -322,7 +406,11 @@ const USAGE =
|
|
|
322
406
|
* because a measurement problem must not block a session start.
|
|
323
407
|
*
|
|
324
408
|
* @param {{ repoRoot?: string, ceiling?: number }} [opts]
|
|
325
|
-
*
|
|
409
|
+
* The one non-null path besides a breach is `reason: 'no-tests-found'` (#1451):
|
|
410
|
+
* a repo whose tests the recipe cannot see gets a WARN, never silence — silence
|
|
411
|
+
* is what made the unmeasurable ratio read as green fleet-wide.
|
|
412
|
+
*
|
|
413
|
+
* @returns {{ severity: 'warn', message: string, ratio: number|null, ceiling: number, reason?: string } | null}
|
|
326
414
|
*/
|
|
327
415
|
export function checkTestsSrcRatio({ repoRoot, ceiling = DEFAULT_CEILING } = {}) {
|
|
328
416
|
try {
|
|
@@ -342,6 +430,18 @@ export function checkTestsSrcRatio({ repoRoot, ceiling = DEFAULT_CEILING } = {})
|
|
|
342
430
|
};
|
|
343
431
|
|
|
344
432
|
const result = measure({ files, readFile, ceiling });
|
|
433
|
+
if (result.reason === 'no-tests-found') {
|
|
434
|
+
return {
|
|
435
|
+
severity: 'warn',
|
|
436
|
+
ratio: null,
|
|
437
|
+
ceiling: result.ceiling,
|
|
438
|
+
reason: result.reason,
|
|
439
|
+
message:
|
|
440
|
+
`⚠ tests:src ratio nicht messbar — 0 Testdateien gezählt, ` +
|
|
441
|
+
`${result.testLikeFilesSeen} test-artige Dateien getrackt (#1451). ` +
|
|
442
|
+
`Detail: node scripts/lib/tests-src-ratio.mjs --json`,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
345
445
|
if (result.ratio === null || result.withinCorridor) return null;
|
|
346
446
|
|
|
347
447
|
const dirty = isDirty(root);
|
|
@@ -402,17 +502,20 @@ function main() {
|
|
|
402
502
|
console.log(USAGE);
|
|
403
503
|
console.log('');
|
|
404
504
|
console.log('The canonical tests:src LOC measurement for TV-003 (test-value.md).');
|
|
405
|
-
console.log(' numerator tracked code under tests/');
|
|
505
|
+
console.log(' numerator tracked code under tests/, plus *.test.* / *.spec.* / __tests__/ anywhere');
|
|
406
506
|
console.log(' denominator every other tracked code file (src by negation)');
|
|
407
507
|
console.log(` code exts ${CODE_EXTENSIONS.join(' ')} (.md / .json never counted)`);
|
|
508
|
+
console.log(` excluded declaration files ${DECLARATION_SUFFIXES.join(' ')} on both sides`);
|
|
408
509
|
console.log(' lines physical; blanks + comments counted; EOF-newline-insensitive');
|
|
409
510
|
console.log('');
|
|
410
511
|
console.log(' --json machine-readable envelope on stdout');
|
|
411
|
-
console.log(' --check exit 1 when the ratio exceeds the ceiling');
|
|
512
|
+
console.log(' --check exit 1 when the ratio exceeds the ceiling, 3 when not measurable');
|
|
412
513
|
console.log(` --ceiling <n> override the TV-003 ceiling (default ${DEFAULT_CEILING})`);
|
|
413
514
|
console.log(' --stdin read newline-separated paths instead of git ls-files');
|
|
414
515
|
console.log('');
|
|
415
|
-
console.log(
|
|
516
|
+
console.log(
|
|
517
|
+
'Exit: 0 ok / within corridor · 1 (--check) ceiling exceeded · 2 tool error · 3 (--check) not measurable',
|
|
518
|
+
);
|
|
416
519
|
process.exit(0);
|
|
417
520
|
}
|
|
418
521
|
|
|
@@ -460,10 +563,17 @@ function main() {
|
|
|
460
563
|
// fail-open class is not worth re-litigating per payload size.
|
|
461
564
|
writeStdoutLineSync(JSON.stringify(envelope, null, 2));
|
|
462
565
|
} else {
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
? '
|
|
466
|
-
:
|
|
566
|
+
const notMeasurable = result.reason === 'no-tests-found';
|
|
567
|
+
const r = notMeasurable
|
|
568
|
+
? 'n/a (no tests found)'
|
|
569
|
+
: result.ratio === null
|
|
570
|
+
? 'n/a (no src code)'
|
|
571
|
+
: result.ratio.toFixed(4);
|
|
572
|
+
const verdict = notMeasurable
|
|
573
|
+
? `NOT MEASURABLE — ${result.testLikeFilesSeen} test-like files tracked but 0 counted (#1451)`
|
|
574
|
+
: result.withinCorridor
|
|
575
|
+
? 'within corridor'
|
|
576
|
+
: `ABOVE ceiling ${result.ceiling} — TV-003 consolidation wave required`;
|
|
467
577
|
const at = envelope.ref ? ` @ ${envelope.ref}${dirty ? '+dirty' : ''}` : '';
|
|
468
578
|
writeStdoutLineSync(
|
|
469
579
|
`tests:src = ${r} (${result.testLoc} test LOC / ${result.srcLoc} src LOC` +
|
|
@@ -472,6 +582,13 @@ function main() {
|
|
|
472
582
|
);
|
|
473
583
|
}
|
|
474
584
|
|
|
585
|
+
if (args.check && result.reason === 'no-tests-found') {
|
|
586
|
+
console.error(
|
|
587
|
+
`tests:src ratio not measurable — 0 test files counted but ${result.testLikeFilesSeen} ` +
|
|
588
|
+
`test-like files tracked (no-tests-found); see #1451`,
|
|
589
|
+
);
|
|
590
|
+
process.exit(3);
|
|
591
|
+
}
|
|
475
592
|
process.exit(args.check && !result.withinCorridor ? 1 : 0);
|
|
476
593
|
}
|
|
477
594
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { resolveStateDir } from '../platform.mjs';
|
|
28
|
+
import { shellQuote } from '../sh-quote.mjs';
|
|
28
29
|
import { detectVcsCommand } from './vcs-detector.mjs';
|
|
29
30
|
import { isSessionCollision } from './tmux-shell.mjs';
|
|
30
31
|
|
|
@@ -48,17 +49,6 @@ try {
|
|
|
48
49
|
// Internal helpers
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
-
* Single-quote a shell command string for use as a tmux send-keys argument.
|
|
53
|
-
* Escapes embedded single-quotes using the POSIX `'...' '\'' '...'` pattern.
|
|
54
|
-
*
|
|
55
|
-
* @param {string} s
|
|
56
|
-
* @returns {string} e.g. 'tail -F /path/to/STATE.md'
|
|
57
|
-
*/
|
|
58
|
-
function shellQuote(s) {
|
|
59
|
-
return "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
60
|
-
}
|
|
61
|
-
|
|
62
52
|
/** Poll interval (seconds) of the agent-status pane loop. */
|
|
63
53
|
const STATUS_PANE_POLL_SECONDS = 2;
|
|
64
54
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* scripts/lib/tmux-layout/telemetry-stats.mjs
|
|
3
3
|
*
|
|
4
4
|
* Aggregator stub for /tmux-layout telemetry promotion gate (#563).
|
|
5
|
-
* Reads
|
|
5
|
+
* Reads the events ledger — active file AND every rotated archive (#1407) —
|
|
6
|
+
* and computes:
|
|
6
7
|
* - invocation count (total + per layout)
|
|
7
8
|
* - completion rate (completed / invoked) — promotion gate threshold 80%
|
|
8
9
|
* - top-K degradation reasons
|
|
@@ -13,26 +14,68 @@
|
|
|
13
14
|
* - CLI: `node scripts/lib/tmux-layout/telemetry-stats.mjs` (emits JSON to stdout)
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
import
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { readEventsWithRotations } from '../events.mjs';
|
|
17
19
|
import { isMainModule } from '../is-main-module.mjs';
|
|
18
20
|
|
|
19
21
|
const EVENTS_PATH = '.orchestrator/metrics/events.jsonl';
|
|
20
22
|
|
|
23
|
+
/** Every event this module reports on carries this prefix. */
|
|
24
|
+
const TMUX_EVENT_PREFIX = 'tmux-layout.';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read the tmux-layout events ACROSS rotation boundaries, together with the
|
|
28
|
+
* honesty verdict of that read.
|
|
29
|
+
*
|
|
30
|
+
* WHY THE ENVELOPE (#1407 acceptance criterion 3): the stats below are an
|
|
31
|
+
* ALL-TIME rate. Reading only the active file silently redefines "all time" as
|
|
32
|
+
* "since the last rotation", and a genuinely missing archive would then look
|
|
33
|
+
* identical to a quiet week. `complete === false` says which of the two it is;
|
|
34
|
+
* the CLI prints it and never computes over a partial set in silence.
|
|
35
|
+
*
|
|
36
|
+
* THREE states (#1423): `complete === null` is a THIRD reading — no source
|
|
37
|
+
* existed at all, so `invocations: 0` is UNMEASURED, not a measured zero, and
|
|
38
|
+
* `meetsPromotionGate: false` below rests on nothing. The CLI names that case
|
|
39
|
+
* separately; folding it into `false` would report a gap nobody found, folding
|
|
40
|
+
* it into `true` would report a measurement nobody took.
|
|
41
|
+
*
|
|
42
|
+
* CEILING (BV-004): `readEventsWithRotations` loads the active file and every
|
|
43
|
+
* archive fully into memory — up to ~60 MB transient at the default
|
|
44
|
+
* `max-size-mb: 10` / `max-backups: 5`. Acceptable here because this is a COLD
|
|
45
|
+
* ops path: a hand-run CLI / promotion-gate check, never a hook and never on
|
|
46
|
+
* the session path. Revisit if this module gains a hot-path caller or if
|
|
47
|
+
* `max-size-mb` is raised past ~100.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} [eventsPath=.orchestrator/metrics/events.jsonl]
|
|
50
|
+
* @returns {{events: Array<object>, complete: boolean|null, gaps: Array<object>,
|
|
51
|
+
* notices: Array<object>}}
|
|
52
|
+
*/
|
|
53
|
+
export function readTmuxEventsEnvelope(eventsPath = EVENTS_PATH) {
|
|
54
|
+
const { events, gaps, complete, notices } = readEventsWithRotations(undefined, {
|
|
55
|
+
filePath: eventsPath,
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
events: events.filter(
|
|
59
|
+
(rec) => rec && typeof rec.event === 'string' && rec.event.startsWith(TMUX_EVENT_PREFIX),
|
|
60
|
+
),
|
|
61
|
+
complete,
|
|
62
|
+
gaps,
|
|
63
|
+
notices: notices ?? [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
21
67
|
/**
|
|
22
|
-
* Read events
|
|
68
|
+
* Read the events ledger and return tmux-layout-related events.
|
|
69
|
+
*
|
|
70
|
+
* Thin array-returning view of {@link readTmuxEventsEnvelope} — the shape every
|
|
71
|
+
* existing caller expects. Use the envelope when the completeness of the read
|
|
72
|
+
* matters to the answer.
|
|
23
73
|
*
|
|
24
74
|
* @param {string} [eventsPath=.orchestrator/metrics/events.jsonl]
|
|
25
75
|
* @returns {Array<object>} parsed event records (filtered to tmux-layout.* events)
|
|
26
76
|
*/
|
|
27
77
|
export function readTmuxEvents(eventsPath = EVENTS_PATH) {
|
|
28
|
-
|
|
29
|
-
const raw = readFileSync(eventsPath, 'utf-8');
|
|
30
|
-
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
|
|
31
|
-
return lines
|
|
32
|
-
.map((l) => {
|
|
33
|
-
try { return JSON.parse(l); } catch { return null; }
|
|
34
|
-
})
|
|
35
|
-
.filter((rec) => rec && typeof rec.event === 'string' && rec.event.startsWith('tmux-layout.'));
|
|
78
|
+
return readTmuxEventsEnvelope(eventsPath).events;
|
|
36
79
|
}
|
|
37
80
|
|
|
38
81
|
/**
|
|
@@ -95,7 +138,23 @@ export function computeStats(events) {
|
|
|
95
138
|
|
|
96
139
|
// CLI entry-point — emit stats as JSON to stdout when run directly
|
|
97
140
|
if (isMainModule(import.meta.url)) {
|
|
98
|
-
const events =
|
|
141
|
+
const { events, complete, gaps } = readTmuxEventsEnvelope();
|
|
99
142
|
const stats = computeStats(events);
|
|
100
|
-
|
|
143
|
+
if (complete === null) {
|
|
144
|
+
// NOT MEASURED — a different sentence from "incomplete" (#1423): no active
|
|
145
|
+
// file and no archive means the zeros below were never observed.
|
|
146
|
+
console.error(
|
|
147
|
+
'WARN: no events source was readable (no active file, no archive) — the stats below are UNMEASURED, not zero',
|
|
148
|
+
);
|
|
149
|
+
} else if (!complete) {
|
|
150
|
+
// A gap, never an empty window: diagnostics on stderr (cli-design.md),
|
|
151
|
+
// the machine-readable verdict in the JSON below.
|
|
152
|
+
const detail = gaps
|
|
153
|
+
.map((g) => `${g.kind}:${path.basename(String(g.archived_as ?? 'unknown'))}`)
|
|
154
|
+
.join(', ');
|
|
155
|
+
console.error(
|
|
156
|
+
`WARN: events ledger incomplete — the all-time rate below is computed over a PARTIAL set (${gaps.length} gap(s): ${detail})`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
console.log(JSON.stringify({ ...stats, ledgerComplete: complete, ledgerGaps: gaps }, null, 2));
|
|
101
160
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
|
|
9
|
+
import { shellQuote } from '../sh-quote.mjs';
|
|
10
|
+
|
|
9
11
|
const MIN_MAJOR = 3;
|
|
10
12
|
const MIN_MINOR = 0;
|
|
11
13
|
|
|
@@ -69,14 +71,3 @@ export function isSessionCollision(sessionName) {
|
|
|
69
71
|
return { exists: false, error: `tmux has-session failed: ${err.message}` };
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Shell-quote a string for safe interpolation into a shell command.
|
|
75
|
-
* Uses single-quote wrapping with embedded single-quote escaping.
|
|
76
|
-
*
|
|
77
|
-
* @param {string} s
|
|
78
|
-
* @returns {string}
|
|
79
|
-
*/
|
|
80
|
-
function shellQuote(s) {
|
|
81
|
-
return "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
82
|
-
}
|
|
@@ -102,11 +102,31 @@ function normaliseScalar(text) {
|
|
|
102
102
|
* than inventing a fifth dialect. Because the demotion is the surprising half,
|
|
103
103
|
* each one emits exactly one stderr WARN naming the file.
|
|
104
104
|
*
|
|
105
|
+
* The same normalisation governs the SIBLING boolean marker
|
|
106
|
+
* `disable-model-invocation` (the Cursor and Codex adapters read it with this
|
|
107
|
+
* predicate). The WARN therefore names the key it was asked about rather than a
|
|
108
|
+
* hard-coded `user-invocable`: measured 2026-09-18, a `disable-model-invocation:
|
|
109
|
+
* yes` demotion reported `user-invocable: "yes"`, sending the operator to a
|
|
110
|
+
* line that does not exist in that file.
|
|
111
|
+
*
|
|
112
|
+
* The NOUN follows the key too (#1388 P10). Naming the key while hard-coding
|
|
113
|
+
* "is NOT a slash-command marker" told a `disable-model-invocation: yes` author
|
|
114
|
+
* that his value failed to be something it never was — a correct key with a
|
|
115
|
+
* wrong subject reads as a broken diagnostic. The default phrase is unchanged
|
|
116
|
+
* for `user-invocable`, so the four existing call sites see identical text.
|
|
117
|
+
*
|
|
105
118
|
* @param {unknown} value the raw frontmatter value
|
|
106
119
|
* @param {string} [file] path named in the WARN when a truthy-looking value is demoted
|
|
120
|
+
* @param {string} [key] frontmatter key named in the WARN (default `user-invocable`)
|
|
107
121
|
* @returns {boolean}
|
|
108
122
|
*/
|
|
109
|
-
|
|
123
|
+
const MARKER_NOUN = Object.freeze({
|
|
124
|
+
'user-invocable': 'a slash-command marker',
|
|
125
|
+
'disable-model-invocation': 'a model-invocation opt-out marker',
|
|
126
|
+
});
|
|
127
|
+
const DEFAULT_MARKER_NOUN = 'a boolean marker';
|
|
128
|
+
|
|
129
|
+
export function isUserInvocableValue(value, file, key = 'user-invocable') {
|
|
110
130
|
if (value === true) return true;
|
|
111
131
|
if (typeof value !== 'string') return false;
|
|
112
132
|
const token = normaliseScalar(value);
|
|
@@ -114,8 +134,8 @@ export function isUserInvocableValue(value, file) {
|
|
|
114
134
|
if (TRUTHY_LOOKALIKE.test(token)) {
|
|
115
135
|
const where = file ? `${file}: ` : '';
|
|
116
136
|
process.stderr.write(
|
|
117
|
-
`WARN ${where}
|
|
118
|
-
+
|
|
137
|
+
`WARN ${where}${key}: ${JSON.stringify(value)} is NOT ${MARKER_NOUN[key] ?? DEFAULT_MARKER_NOUN} — `
|
|
138
|
+
+ `only \`true\` is (YAML 1.2 reads yes/on/1 as strings). Write \`${key}: true\`.\n`,
|
|
119
139
|
);
|
|
120
140
|
}
|
|
121
141
|
return false;
|
|
@@ -37,12 +37,16 @@
|
|
|
37
37
|
* mistaken import away from filing issues nobody asked for.
|
|
38
38
|
*
|
|
39
39
|
* Exports:
|
|
40
|
-
* UX_GRILL_LABELS, buildIssueTitle,
|
|
40
|
+
* UX_GRILL_LABELS, buildIssueTitle, buildUxGrillIssueBody, reconcileFindings
|
|
41
|
+
* buildIssueBody — @deprecated alias of buildUxGrillIssueBody (5.2.0 name),
|
|
42
|
+
* removed in 6.0.0
|
|
41
43
|
*/
|
|
42
44
|
|
|
43
45
|
import {
|
|
44
46
|
createFinding as defaultCreateFinding,
|
|
47
|
+
fingerprintSentinelLine,
|
|
45
48
|
listExistingFindings as defaultListExisting,
|
|
49
|
+
neutralizeFingerprintSentinel,
|
|
46
50
|
triageDecision,
|
|
47
51
|
updateFinding as defaultUpdateFinding,
|
|
48
52
|
} from '../test-runner/issue-reconcile.mjs';
|
|
@@ -69,31 +73,22 @@ const TITLE_MAX_LENGTH = 100;
|
|
|
69
73
|
const AUTO_SEVERITIES = Object.freeze(['critical', 'high']);
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
|
-
* Neutralise
|
|
76
|
+
* Neutralise fingerprint-sentinel literals (#388 SEC-IR-MED-1) and strip the
|
|
77
|
+
* characters `createFinding` rejects at the argv boundary, so a multi-line page
|
|
78
|
+
* title in a finding message cannot turn into a VALIDATION failure for the whole
|
|
79
|
+
* reconcile pass.
|
|
73
80
|
*
|
|
74
|
-
* `issue-reconcile.mjs`
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
81
|
+
* The neutralisation is the SHARED one from `issue-reconcile.mjs` (#1339 P6): a
|
|
82
|
+
* body built HERE and handed to `createFinding` is not sanitised by anything
|
|
83
|
+
* else, so a finding `message` echoing page text could otherwise forge the
|
|
84
|
+
* authoritative `**Fingerprint:**` line and make the next run's
|
|
78
85
|
* `triageDecision` dedup against the wrong issue.
|
|
79
86
|
*
|
|
80
87
|
* @param {unknown} text
|
|
81
88
|
* @returns {string}
|
|
82
89
|
*/
|
|
83
|
-
function sanitizeSentinel(text) {
|
|
84
|
-
return String(text ?? '').replace(/\*\*Fingerprint:\*\*/gi, '__Fingerprint__');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Strip the characters `createFinding` rejects at the argv boundary, so a
|
|
89
|
-
* multi-line page title in a finding message cannot turn into a VALIDATION
|
|
90
|
-
* failure for the whole reconcile pass.
|
|
91
|
-
*
|
|
92
|
-
* @param {unknown} text
|
|
93
|
-
* @returns {string}
|
|
94
|
-
*/
|
|
95
90
|
function oneLine(text) {
|
|
96
|
-
return
|
|
91
|
+
return neutralizeFingerprintSentinel(text).replace(/[\n\r\0]+/g, ' ').trim();
|
|
97
92
|
}
|
|
98
93
|
|
|
99
94
|
/**
|
|
@@ -144,7 +139,7 @@ export function buildIssueTitle(finding) {
|
|
|
144
139
|
* @param {string} [context.rubricHash]
|
|
145
140
|
* @returns {string}
|
|
146
141
|
*/
|
|
147
|
-
export function
|
|
142
|
+
export function buildUxGrillIssueBody(finding, { runId, rubricHash } = {}) {
|
|
148
143
|
const evidence = finding?.evidence ?? {};
|
|
149
144
|
const evidenceLines = Object.entries(evidence)
|
|
150
145
|
.filter(([, value]) => typeof value === 'string' || Array.isArray(value))
|
|
@@ -153,7 +148,7 @@ export function buildIssueBody(finding, { runId, rubricHash } = {}) {
|
|
|
153
148
|
return [
|
|
154
149
|
oneLine(finding?.message) || `${oneLine(finding?.checkId)} at ${oneLine(finding?.locator)}`,
|
|
155
150
|
'',
|
|
156
|
-
|
|
151
|
+
fingerprintSentinelLine(oneLine(finding?.fingerprint)),
|
|
157
152
|
`**Severity:** ${oneLine(finding?.severity)}`,
|
|
158
153
|
`**Check:** ${oneLine(finding?.checkId)}`,
|
|
159
154
|
`**Locator:** \`${oneLine(finding?.locator)}\``,
|
|
@@ -168,6 +163,37 @@ export function buildIssueBody(finding, { runId, rubricHash } = {}) {
|
|
|
168
163
|
.join('\n');
|
|
169
164
|
}
|
|
170
165
|
|
|
166
|
+
/** One stderr line per process for the deprecated alias below, not one per call. */
|
|
167
|
+
let buildIssueBodyDeprecationWarned = false;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Deprecated alias of {@link buildUxGrillIssueBody}, shipped under this name in
|
|
171
|
+
* 5.2.0.
|
|
172
|
+
*
|
|
173
|
+
* @deprecated since 5.3.0 — use buildUxGrillIssueBody; removed in 6.0.0.
|
|
174
|
+
* Kept one minor cycle (`.claude/rules/development.md` § Package Lifecycle &
|
|
175
|
+
* Versioning) because `package.json` carries no `exports` map: every packed
|
|
176
|
+
* `scripts/lib/` export is a public deep-import, and a consumer calling this
|
|
177
|
+
* name would otherwise get a runtime TypeError, not a compile error. It
|
|
178
|
+
* delegates to the LOCAL ux-grill body builder — never to the unrelated
|
|
179
|
+
* `buildIssueBody(finding, fp)` of `test-runner/issue-reconcile.mjs`, whose
|
|
180
|
+
* finding shape and second argument differ.
|
|
181
|
+
*
|
|
182
|
+
* @param {object} finding - a `makeFinding` record
|
|
183
|
+
* @param {object} [context] - same as {@link buildUxGrillIssueBody}
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
export function buildIssueBody(finding, context) {
|
|
187
|
+
if (!buildIssueBodyDeprecationWarned) {
|
|
188
|
+
buildIssueBodyDeprecationWarned = true;
|
|
189
|
+
console.warn(
|
|
190
|
+
'[deprecated] buildIssueBody from scripts/lib/ux-grill/reconcile.mjs is deprecated since the 5.3 line ' +
|
|
191
|
+
'and will be removed in 6.0.0 — use buildUxGrillIssueBody instead.',
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return buildUxGrillIssueBody(finding, context);
|
|
195
|
+
}
|
|
196
|
+
|
|
171
197
|
/**
|
|
172
198
|
* Collect every string in a finding's `evidence` that looks like an absolute
|
|
173
199
|
* host path (POSIX `/…` or Windows `C:\…`).
|
|
@@ -296,7 +322,7 @@ export async function reconcileFindings({
|
|
|
296
322
|
}
|
|
297
323
|
|
|
298
324
|
const title = buildIssueTitle(finding);
|
|
299
|
-
const body =
|
|
325
|
+
const body = buildUxGrillIssueBody(finding, { runId, rubricHash });
|
|
300
326
|
const decision = triageDecision({ fingerprint: finding?.fingerprint, title }, candidates);
|
|
301
327
|
|
|
302
328
|
if (decision.action === 'ignore') {
|