session-orchestrator 5.3.0 → 5.5.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/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- 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/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -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 +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- 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 +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- 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-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -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/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/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/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- 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 +10 -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/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -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
|
@@ -22,9 +22,14 @@
|
|
|
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
|
|
31
|
+
* reported tracked shell (EXCLUDED_SHELL_EXTENSIONS) — in NEITHER bucket,
|
|
32
|
+
* its volume shipped as `excludedScope.shell` (#1488, variant B)
|
|
28
33
|
*
|
|
29
34
|
* ## The four questions, answered in code rather than left open
|
|
30
35
|
*
|
|
@@ -37,8 +42,8 @@
|
|
|
37
42
|
* Only git-tracked? YES — reproducible at a SHA, and it is
|
|
38
43
|
* what keeps node_modules/, coverage/ and
|
|
39
44
|
* untracked scratch out without an ignore list.
|
|
40
|
-
* Do `tests/fixtures/` count as test LOC? YES when they are code (
|
|
41
|
-
*
|
|
45
|
+
* Do `tests/fixtures/` count as test LOC? YES when they are code (any of
|
|
46
|
+
* CODE_EXTENSIONS). A code fixture is maintained
|
|
42
47
|
* code that exists only to serve the suite —
|
|
43
48
|
* that is test-corpus cost. Non-code
|
|
44
49
|
* fixtures (`.json`, `.jsonl`, `.md`) are
|
|
@@ -62,7 +67,9 @@
|
|
|
62
67
|
* <repo-root> defaults to process.cwd()
|
|
63
68
|
* --json emit a single JSON object on stdout, nothing else
|
|
64
69
|
* --check exit 1 when the ratio exceeds the ceiling (consolidation
|
|
65
|
-
* wave required); exit
|
|
70
|
+
* wave required); exit 3 when the ratio is not measurable
|
|
71
|
+
* (`reason: 'no-tests-found'`, #1451); exit 0 when inside
|
|
72
|
+
* the corridor
|
|
66
73
|
* --ceiling <n> override the TV-003 ceiling (default 1.60)
|
|
67
74
|
* --stdin take newline-separated paths from stdin instead of
|
|
68
75
|
* enumerating via `git ls-files` (test seam / staged-only mode)
|
|
@@ -71,6 +78,12 @@
|
|
|
71
78
|
* 0 — measurement completed (and, under --check, ratio is within the corridor)
|
|
72
79
|
* 1 — --check only: ratio exceeds the ceiling
|
|
73
80
|
* 2 — tool error (missing/unreadable root, bad argv)
|
|
81
|
+
* 3 — --check only: ratio not measurable — 0 test files counted while test-like
|
|
82
|
+
* files ARE tracked (`reason: 'no-tests-found'`, e.g. a Python repo whose
|
|
83
|
+
* `tests/test_*.py` sit outside CODE_EXTENSIONS). Fail-closed since #1451:
|
|
84
|
+
* before, such a repo read `ratio 0, withinCorridor true` — "not found"
|
|
85
|
+
* reported as "in corridor". TypeScript tests count since the 2026-10-01
|
|
86
|
+
* owner decision on #1451; other stacks still get "not measurable".
|
|
74
87
|
*/
|
|
75
88
|
|
|
76
89
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
@@ -90,23 +103,85 @@ import { isMainModule } from './is-main-module.mjs';
|
|
|
90
103
|
* Applied symmetrically to numerator and denominator by construction — a file
|
|
91
104
|
* type can never inflate one side while being invisible to the other.
|
|
92
105
|
*/
|
|
93
|
-
export const CODE_EXTENSIONS = Object.freeze([
|
|
106
|
+
export const CODE_EXTENSIONS = Object.freeze([
|
|
107
|
+
'.mjs',
|
|
108
|
+
'.js',
|
|
109
|
+
'.cjs',
|
|
110
|
+
'.jsx',
|
|
111
|
+
'.ts',
|
|
112
|
+
'.tsx',
|
|
113
|
+
'.mts',
|
|
114
|
+
'.cts',
|
|
115
|
+
]);
|
|
94
116
|
|
|
95
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* TypeScript declaration files end in a CODE_EXTENSION but carry only types —
|
|
119
|
+
* no executable line a test could exercise. Excluded from BOTH sides, so a
|
|
120
|
+
* generated `.d.ts` can never move the ratio.
|
|
121
|
+
*/
|
|
122
|
+
export const DECLARATION_SUFFIXES = Object.freeze(['.d.ts', '.d.mts', '.d.cts']);
|
|
123
|
+
|
|
124
|
+
/** Top-level path prefix that makes a tracked code file part of the TEST corpus. */
|
|
96
125
|
export const TEST_PREFIX = 'tests/';
|
|
97
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Shell extensions the ratio deliberately does NOT count (#1488, owner decision
|
|
129
|
+
* 2026-10-02, variant B): they stay out of numerator and denominator, but their
|
|
130
|
+
* volume is MEASURED and reported under `excludedScope.shell`, so "this repo has
|
|
131
|
+
* no shell" and "this repo has 2.5k unmeasured shell lines" stop looking alike.
|
|
132
|
+
* Detection is by extension only — an extensionless shebang script (`.husky/*`)
|
|
133
|
+
* is not seen; sniffing shebangs would mean reading every extensionless tracked
|
|
134
|
+
* file inside a synchronous session-start probe.
|
|
135
|
+
*/
|
|
136
|
+
export const EXCLUDED_SHELL_EXTENSIONS = Object.freeze(['.sh', '.bash', '.bats']);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Co-located test files, wherever they sit: a `*.test.<ext>` / `*.spec.<ext>`
|
|
140
|
+
* basename (the vitest/jest default include shape) or a `__tests__/` segment.
|
|
141
|
+
*/
|
|
142
|
+
const COLOCATED_TEST_RE = /\.(test|spec)\.[^./]+$|(^|\/)__tests__\//;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The one test-corpus rule, shared by the counted code buckets and the reported
|
|
146
|
+
* shell scope so the two can never split a path differently.
|
|
147
|
+
* @param {string} p normalised repo-relative path
|
|
148
|
+
*/
|
|
149
|
+
function isTestCorpusPath(p) {
|
|
150
|
+
return p.startsWith(TEST_PREFIX) || COLOCATED_TEST_RE.test(p);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A tracked path that LOOKS like a test, counted or not: a `*.test.*` / `*.spec.*`
|
|
155
|
+
* JS/TS file, anything under a `__tests__/` segment, or any file under `tests/`
|
|
156
|
+
* regardless of extension. Used only to tell "this repo has no tests" apart from
|
|
157
|
+
* "this repo has tests the recipe cannot see" (#1451) — e.g. `tests/test_x.py`.
|
|
158
|
+
*/
|
|
159
|
+
const TEST_LIKE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {string} relPath repo-relative path
|
|
163
|
+
* @returns {boolean}
|
|
164
|
+
*/
|
|
165
|
+
export function isTestLikePath(relPath) {
|
|
166
|
+
const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
167
|
+
return TEST_LIKE_RE.test(p) || p.startsWith(TEST_PREFIX) || /(^|\/)__tests__\//.test(p);
|
|
168
|
+
}
|
|
169
|
+
|
|
98
170
|
/** The TV-003 ceiling. Exceeding it switches the consolidation rule on. */
|
|
99
171
|
export const DEFAULT_CEILING = 1.6;
|
|
100
172
|
|
|
101
173
|
/** Machine-readable schema tag for the --json envelope. */
|
|
102
|
-
export const SCHEMA = 'tests-src-ratio/
|
|
174
|
+
export const SCHEMA = 'tests-src-ratio/2';
|
|
103
175
|
|
|
104
176
|
/**
|
|
105
177
|
* Which bucket a repo-relative path belongs to.
|
|
106
178
|
*
|
|
107
|
-
* `src` is defined by NEGATION — every tracked code file that is not
|
|
108
|
-
* `tests
|
|
109
|
-
* globs that can overlap or leave a gap.
|
|
179
|
+
* `src` is defined by NEGATION — every tracked code file that is not a test
|
|
180
|
+
* (under `tests/`, or co-located per COLOCATED_TEST_RE). That is what makes the
|
|
181
|
+
* two buckets a partition rather than two globs that can overlap or leave a gap.
|
|
182
|
+
*
|
|
183
|
+
* Known blind spot: a nested `pkg/tests/helper.ts` that is not itself named
|
|
184
|
+
* `*.test.*` counts as src — only the TOP-LEVEL `tests/` is a test directory.
|
|
110
185
|
*
|
|
111
186
|
* @param {string} relPath repo-relative path, `/`-separated
|
|
112
187
|
* @returns {'test'|'src'|null} null = outside the metric entirely
|
|
@@ -114,7 +189,22 @@ export const SCHEMA = 'tests-src-ratio/1';
|
|
|
114
189
|
export function classifyPath(relPath) {
|
|
115
190
|
const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
116
191
|
if (!CODE_EXTENSIONS.some((ext) => p.endsWith(ext))) return null;
|
|
117
|
-
|
|
192
|
+
if (DECLARATION_SUFFIXES.some((ext) => p.endsWith(ext))) return null;
|
|
193
|
+
return isTestCorpusPath(p) ? 'test' : 'src';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Which half of the EXCLUDED shell scope a path falls into — reported, never
|
|
198
|
+
* counted in the ratio. `.bats` is a test framework's own extension, so it is
|
|
199
|
+
* test-shell anywhere; any other shell file follows the code rule above.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} relPath repo-relative path
|
|
202
|
+
* @returns {'test'|'src'|null} null = not shell
|
|
203
|
+
*/
|
|
204
|
+
function classifyShellPath(relPath) {
|
|
205
|
+
const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
206
|
+
if (!EXCLUDED_SHELL_EXTENSIONS.some((ext) => p.endsWith(ext))) return null;
|
|
207
|
+
return p.endsWith('.bats') || isTestCorpusPath(p) ? 'test' : 'src';
|
|
118
208
|
}
|
|
119
209
|
|
|
120
210
|
/**
|
|
@@ -154,7 +244,10 @@ export function countPhysicalLines(content) {
|
|
|
154
244
|
* @param {(relPath: string) => string|null} opts.readFile returns content, or null when unreadable
|
|
155
245
|
* @param {number} [opts.ceiling]
|
|
156
246
|
* @returns {{testFiles:number,testLoc:number,srcFiles:number,srcLoc:number,ratio:number|null,
|
|
157
|
-
* ceiling:number,withinCorridor:boolean,consolidationWaveRequired:boolean,
|
|
247
|
+
* ceiling:number,withinCorridor:boolean|null,consolidationWaveRequired:boolean|null,
|
|
248
|
+
* skipped:number,testLikeFilesSeen:number,reason:'no-tests-found'|null,
|
|
249
|
+
* excludedScope:{shell:{files:number,loc:number,testFiles:number,testLoc:number,
|
|
250
|
+
* srcFiles:number,srcLoc:number,skipped:number}}}}
|
|
158
251
|
*/
|
|
159
252
|
export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
160
253
|
let testFiles = 0;
|
|
@@ -162,8 +255,32 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
162
255
|
let srcFiles = 0;
|
|
163
256
|
let srcLoc = 0;
|
|
164
257
|
let skipped = 0;
|
|
258
|
+
let testLikeFilesSeen = 0;
|
|
259
|
+
// Reported only — never added to testLoc/srcLoc (#1488 variant B). Always
|
|
260
|
+
// present, zeros included: a measured zero, not an absent measurement.
|
|
261
|
+
const shell = { files: 0, loc: 0, testFiles: 0, testLoc: 0, srcFiles: 0, srcLoc: 0, skipped: 0 };
|
|
165
262
|
|
|
166
263
|
for (const rel of files) {
|
|
264
|
+
if (isTestLikePath(rel)) testLikeFilesSeen++;
|
|
265
|
+
const shellBucket = classifyShellPath(rel);
|
|
266
|
+
if (shellBucket !== null) {
|
|
267
|
+
const content = readFile(rel);
|
|
268
|
+
if (content === null || content === undefined) {
|
|
269
|
+
shell.skipped++;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const lines = countPhysicalLines(content);
|
|
273
|
+
shell.files++;
|
|
274
|
+
shell.loc += lines;
|
|
275
|
+
if (shellBucket === 'test') {
|
|
276
|
+
shell.testFiles++;
|
|
277
|
+
shell.testLoc += lines;
|
|
278
|
+
} else {
|
|
279
|
+
shell.srcFiles++;
|
|
280
|
+
shell.srcLoc += lines;
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
167
284
|
const bucket = classifyPath(rel);
|
|
168
285
|
if (bucket === null) continue;
|
|
169
286
|
const content = readFile(rel);
|
|
@@ -183,6 +300,27 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
183
300
|
|
|
184
301
|
// A repo with no src code has an undefined ratio, not an infinite one. Saying
|
|
185
302
|
// `null` keeps the consumer from reading Infinity as a corridor breach.
|
|
303
|
+
// Fail closed (#1451): a zero numerator next to tracked test-like files means
|
|
304
|
+
// the recipe cannot SEE this repo's tests (e.g. `tests/test_x.py`), not that
|
|
305
|
+
// there are none. Reporting ratio 0 / withinCorridor true there turned "not found"
|
|
306
|
+
// into "green". The verdict is unknown, so every verdict field is null.
|
|
307
|
+
if (testFiles === 0 && testLikeFilesSeen > 0) {
|
|
308
|
+
return {
|
|
309
|
+
testFiles,
|
|
310
|
+
testLoc,
|
|
311
|
+
srcFiles,
|
|
312
|
+
srcLoc,
|
|
313
|
+
ratio: null,
|
|
314
|
+
ceiling,
|
|
315
|
+
withinCorridor: null,
|
|
316
|
+
consolidationWaveRequired: null,
|
|
317
|
+
skipped,
|
|
318
|
+
testLikeFilesSeen,
|
|
319
|
+
reason: 'no-tests-found',
|
|
320
|
+
excludedScope: { shell },
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
186
324
|
const ratio = srcLoc === 0 ? null : Number((testLoc / srcLoc).toFixed(4));
|
|
187
325
|
const withinCorridor = ratio === null ? true : ratio <= ceiling;
|
|
188
326
|
|
|
@@ -196,6 +334,9 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
196
334
|
withinCorridor,
|
|
197
335
|
consolidationWaveRequired: !withinCorridor,
|
|
198
336
|
skipped,
|
|
337
|
+
testLikeFilesSeen,
|
|
338
|
+
reason: null,
|
|
339
|
+
excludedScope: { shell },
|
|
199
340
|
};
|
|
200
341
|
}
|
|
201
342
|
|
|
@@ -208,11 +349,21 @@ export function definitionOf(source) {
|
|
|
208
349
|
return {
|
|
209
350
|
source: source === 'stdin' ? 'stdin path list' : 'git ls-files (tracked files only)',
|
|
210
351
|
codeExtensions: [...CODE_EXTENSIONS],
|
|
211
|
-
|
|
352
|
+
declarationsExcluded: [...DECLARATION_SUFFIXES],
|
|
353
|
+
numerator:
|
|
354
|
+
`tracked code files under a top-level ${TEST_PREFIX}, plus co-located tests anywhere: ` +
|
|
355
|
+
'basename *.test.<ext> / *.spec.<ext>, or any file under a __tests__/ directory',
|
|
212
356
|
denominator: `every OTHER tracked code file (src defined by negation, not by a directory list)`,
|
|
213
357
|
lineRule: 'physical lines; blank + comment lines counted; EOF-newline-insensitive',
|
|
214
358
|
excluded:
|
|
215
|
-
|
|
359
|
+
`declaration files (${DECLARATION_SUFFIXES.map((x) => `*${x}`).join(', ')}: types, not ` +
|
|
360
|
+
'executable code) and non-code extensions (.md, .json, .jsonl, .yml) on BOTH sides; ' +
|
|
361
|
+
'untracked files (node_modules/, coverage/, scratch)',
|
|
362
|
+
excludedScope:
|
|
363
|
+
`shell (${EXCLUDED_SHELL_EXTENSIONS.join(' ')}, by extension only — extensionless shebang ` +
|
|
364
|
+
'scripts such as .husky/ hooks are not seen): counted in NEITHER bucket, its volume ' +
|
|
365
|
+
'reported under excludedScope.shell (#1488). test-shell = .bats anywhere, or the ' +
|
|
366
|
+
'numerator path rule above; every other shell file is src-shell. Same line rule.',
|
|
216
367
|
};
|
|
217
368
|
}
|
|
218
369
|
|
|
@@ -322,7 +473,11 @@ const USAGE =
|
|
|
322
473
|
* because a measurement problem must not block a session start.
|
|
323
474
|
*
|
|
324
475
|
* @param {{ repoRoot?: string, ceiling?: number }} [opts]
|
|
325
|
-
*
|
|
476
|
+
* The one non-null path besides a breach is `reason: 'no-tests-found'` (#1451):
|
|
477
|
+
* a repo whose tests the recipe cannot see gets a WARN, never silence — silence
|
|
478
|
+
* is what made the unmeasurable ratio read as green fleet-wide.
|
|
479
|
+
*
|
|
480
|
+
* @returns {{ severity: 'warn', message: string, ratio: number|null, ceiling: number, reason?: string } | null}
|
|
326
481
|
*/
|
|
327
482
|
export function checkTestsSrcRatio({ repoRoot, ceiling = DEFAULT_CEILING } = {}) {
|
|
328
483
|
try {
|
|
@@ -342,6 +497,18 @@ export function checkTestsSrcRatio({ repoRoot, ceiling = DEFAULT_CEILING } = {})
|
|
|
342
497
|
};
|
|
343
498
|
|
|
344
499
|
const result = measure({ files, readFile, ceiling });
|
|
500
|
+
if (result.reason === 'no-tests-found') {
|
|
501
|
+
return {
|
|
502
|
+
severity: 'warn',
|
|
503
|
+
ratio: null,
|
|
504
|
+
ceiling: result.ceiling,
|
|
505
|
+
reason: result.reason,
|
|
506
|
+
message:
|
|
507
|
+
`⚠ tests:src ratio nicht messbar — 0 Testdateien gezählt, ` +
|
|
508
|
+
`${result.testLikeFilesSeen} test-artige Dateien getrackt (#1451). ` +
|
|
509
|
+
`Detail: node scripts/lib/tests-src-ratio.mjs --json`,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
345
512
|
if (result.ratio === null || result.withinCorridor) return null;
|
|
346
513
|
|
|
347
514
|
const dirty = isDirty(root);
|
|
@@ -402,17 +569,23 @@ function main() {
|
|
|
402
569
|
console.log(USAGE);
|
|
403
570
|
console.log('');
|
|
404
571
|
console.log('The canonical tests:src LOC measurement for TV-003 (test-value.md).');
|
|
405
|
-
console.log(' numerator tracked code under tests/');
|
|
572
|
+
console.log(' numerator tracked code under tests/, plus *.test.* / *.spec.* / __tests__/ anywhere');
|
|
406
573
|
console.log(' denominator every other tracked code file (src by negation)');
|
|
407
574
|
console.log(` code exts ${CODE_EXTENSIONS.join(' ')} (.md / .json never counted)`);
|
|
575
|
+
console.log(` excluded declaration files ${DECLARATION_SUFFIXES.join(' ')} on both sides`);
|
|
576
|
+
console.log(
|
|
577
|
+
` shell ${EXCLUDED_SHELL_EXTENSIONS.join(' ')} counted nowhere, volume reported (excludedScope.shell)`,
|
|
578
|
+
);
|
|
408
579
|
console.log(' lines physical; blanks + comments counted; EOF-newline-insensitive');
|
|
409
580
|
console.log('');
|
|
410
581
|
console.log(' --json machine-readable envelope on stdout');
|
|
411
|
-
console.log(' --check exit 1 when the ratio exceeds the ceiling');
|
|
582
|
+
console.log(' --check exit 1 when the ratio exceeds the ceiling, 3 when not measurable');
|
|
412
583
|
console.log(` --ceiling <n> override the TV-003 ceiling (default ${DEFAULT_CEILING})`);
|
|
413
584
|
console.log(' --stdin read newline-separated paths instead of git ls-files');
|
|
414
585
|
console.log('');
|
|
415
|
-
console.log(
|
|
586
|
+
console.log(
|
|
587
|
+
'Exit: 0 ok / within corridor · 1 (--check) ceiling exceeded · 2 tool error · 3 (--check) not measurable',
|
|
588
|
+
);
|
|
416
589
|
process.exit(0);
|
|
417
590
|
}
|
|
418
591
|
|
|
@@ -460,18 +633,37 @@ function main() {
|
|
|
460
633
|
// fail-open class is not worth re-litigating per payload size.
|
|
461
634
|
writeStdoutLineSync(JSON.stringify(envelope, null, 2));
|
|
462
635
|
} else {
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
? '
|
|
466
|
-
:
|
|
636
|
+
const notMeasurable = result.reason === 'no-tests-found';
|
|
637
|
+
const r = notMeasurable
|
|
638
|
+
? 'n/a (no tests found)'
|
|
639
|
+
: result.ratio === null
|
|
640
|
+
? 'n/a (no src code)'
|
|
641
|
+
: result.ratio.toFixed(4);
|
|
642
|
+
const verdict = notMeasurable
|
|
643
|
+
? `NOT MEASURABLE — ${result.testLikeFilesSeen} test-like files tracked but 0 counted (#1451)`
|
|
644
|
+
: result.withinCorridor
|
|
645
|
+
? 'within corridor'
|
|
646
|
+
: `ABOVE ceiling ${result.ceiling} — TV-003 consolidation wave required`;
|
|
467
647
|
const at = envelope.ref ? ` @ ${envelope.ref}${dirty ? '+dirty' : ''}` : '';
|
|
648
|
+
const sh = result.excludedScope.shell;
|
|
468
649
|
writeStdoutLineSync(
|
|
469
650
|
`tests:src = ${r} (${result.testLoc} test LOC / ${result.srcLoc} src LOC` +
|
|
470
651
|
`; ${result.testFiles} test + ${result.srcFiles} src files${at}) — ${verdict}` +
|
|
652
|
+
(sh.files > 0
|
|
653
|
+
? `\n excluded: shell ${sh.files} files / ${sh.loc} lines ` +
|
|
654
|
+
`(${sh.testFiles} test + ${sh.srcFiles} src files) — in neither bucket (#1488)`
|
|
655
|
+
: '') +
|
|
471
656
|
(dirty ? '\n NOTE: working tree is dirty — this number is NOT reproducible at that SHA' : ''),
|
|
472
657
|
);
|
|
473
658
|
}
|
|
474
659
|
|
|
660
|
+
if (args.check && result.reason === 'no-tests-found') {
|
|
661
|
+
console.error(
|
|
662
|
+
`tests:src ratio not measurable — 0 test files counted but ${result.testLikeFilesSeen} ` +
|
|
663
|
+
`test-like files tracked (no-tests-found); see #1451`,
|
|
664
|
+
);
|
|
665
|
+
process.exit(3);
|
|
666
|
+
}
|
|
475
667
|
process.exit(args.check && !result.withinCorridor ? 1 : 0);
|
|
476
668
|
}
|
|
477
669
|
|
|
@@ -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
|
|
|
@@ -23,6 +23,18 @@ const EVENTS_PATH = '.orchestrator/metrics/events.jsonl';
|
|
|
23
23
|
/** Every event this module reports on carries this prefix. */
|
|
24
24
|
const TMUX_EVENT_PREFIX = 'tmux-layout.';
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Events window this reader needs retained (#1401 part 3): UNBOUNDED, hence
|
|
28
|
+
* `null`. The stats are an ALL-TIME rate, so every record ever written is in
|
|
29
|
+
* scope and no finite `events-rotation` retention satisfies it — a number here
|
|
30
|
+
* could only be invented. Truncation is reported by the envelope's own
|
|
31
|
+
* `complete: false` (pruned archives leave rotation tombstones), not by a
|
|
32
|
+
* retention floor. Read by `scripts/lib/events-retention-banner.mjs`.
|
|
33
|
+
*
|
|
34
|
+
* @type {number|null}
|
|
35
|
+
*/
|
|
36
|
+
export const REQUIRED_EVENTS_WINDOW_DAYS = null;
|
|
37
|
+
|
|
26
38
|
/**
|
|
27
39
|
* Read the tmux-layout events ACROSS rotation boundaries, together with the
|
|
28
40
|
* honesty verdict of that read.
|
|
@@ -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
|
-
}
|
|
@@ -187,7 +187,7 @@ export function buildIssueBody(finding, context) {
|
|
|
187
187
|
if (!buildIssueBodyDeprecationWarned) {
|
|
188
188
|
buildIssueBodyDeprecationWarned = true;
|
|
189
189
|
console.warn(
|
|
190
|
-
'[deprecated] buildIssueBody from scripts/lib/ux-grill/reconcile.mjs is deprecated since 5.3
|
|
190
|
+
'[deprecated] buildIssueBody from scripts/lib/ux-grill/reconcile.mjs is deprecated since the 5.3 line ' +
|
|
191
191
|
'and will be removed in 6.0.0 — use buildUxGrillIssueBody instead.',
|
|
192
192
|
);
|
|
193
193
|
}
|
|
@@ -327,7 +327,7 @@ if (mdFiles.length === 0) {
|
|
|
327
327
|
if (modelVal === null || (!ALLOWED_MODEL_ALIASES.has(modelVal) && !MODEL_ID_RE.test(modelVal))) {
|
|
328
328
|
const got = modelVal ?? '';
|
|
329
329
|
const aliasList = [...ALLOWED_MODEL_ALIASES].join('|');
|
|
330
|
-
fail(`${agentName}: model must be ${aliasList} or a full model ID like 'claude-opus-
|
|
330
|
+
fail(`${agentName}: model must be ${aliasList} or a full model ID like 'claude-opus-5-5' or 'claude-sonnet-5' (got: '${got}')`);
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
@@ -113,6 +113,11 @@ const DOCUMENTED_ASYMMETRIES = {
|
|
|
113
113
|
'skill-invocation-telemetry.mjs',
|
|
114
114
|
'enforce-scope.mjs',
|
|
115
115
|
'config-protection.mjs',
|
|
116
|
+
// pre-edit-sessions-ledger-guard (#1443): same Edit/Write/MultiEdit tool-name
|
|
117
|
+
// gate as enforce-scope/config-protection above — Codex writes files through
|
|
118
|
+
// `apply_patch`, a name this hook never matches. Wired, it would run and allow
|
|
119
|
+
// everything: a silent no-op, not enforcement. Gap registered, not faked.
|
|
120
|
+
'pre-edit-sessions-ledger-guard.mjs',
|
|
116
121
|
'pre-bash-destructive-guard.mjs',
|
|
117
122
|
'pre-bash-staging-fence.mjs',
|
|
118
123
|
'pre-bash-memory-propose-audit.mjs',
|