session-orchestrator 5.4.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/060-evolve.mdc +5 -5
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +95 -3
- package/README.md +11 -11
- package/docs/ci-setup.md +120 -23
- package/docs/components.md +7 -7
- package/docs/events-schema.md +17 -14
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/scope-collision-guard.md +15 -13
- package/docs/session-config-reference.md +7 -7
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +17 -8
- 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.json +1 -1
- package/hooks/on-session-start.mjs +4 -2
- package/hooks/on-stop.mjs +70 -170
- 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 -154
- package/hooks/pre-bash-memory-propose-audit.mjs +19 -7
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-task-scope-disjoint.mjs +584 -207
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/scripts/apply-session-learnings.mjs +35 -14
- package/scripts/archive-closed-prds.mjs +38 -11
- package/scripts/autopilot.mjs +8 -1
- package/scripts/backfill-abandoned-sessions.mjs +80 -5
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +46 -2
- package/scripts/emit-session.mjs +5 -3
- package/scripts/export-hw-learnings.mjs +18 -7
- package/scripts/fleet-checkin.mjs +26 -3
- package/scripts/lib/autopilot/loop.mjs +25 -10
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config.mjs +5 -1
- 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 +51 -3
- package/scripts/lib/fleet-protocol.mjs +20 -2
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +8 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +387 -20
- package/scripts/lib/learnings/io.mjs +125 -16
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/named-vault-resolver.mjs +48 -5
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +101 -69
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +79 -27
- package/scripts/lib/orphan-reaper/scan.mjs +4 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +5 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +83 -22
- package/scripts/lib/scope-baseline.mjs +116 -25
- 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-registry.mjs +41 -16
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +39 -34
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +5 -2
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +77 -2
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +320 -32
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- 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 +18 -11
- package/scripts/lib/vault-status/board-writer.mjs +5 -1
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/release.mjs +1 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +14 -2
- package/scripts/run-quality-gate.mjs +8 -5
- package/scripts/self-update.mjs +229 -0
- package/scripts/sweep-expired-learnings.mjs +560 -28
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-mirror.mjs +170 -33
- package/skills/_shared/fleet-protocol.md +74 -71
- package/skills/_shared/state-ownership.md +38 -14
- package/skills/autopilot/SKILL.md +1 -1
- package/skills/bootstrap/SKILL.md +5 -3
- package/skills/bootstrap/_shared-template.md +96 -1
- package/skills/bootstrap/fast-template.md +3 -0
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/standard-template.md +2 -0
- 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 +11 -2
- package/skills/evolve/references/evolve-analyze-mode.md +37 -17
- package/skills/session-end/learning-patterns.md +3 -3
- package/skills/session-end/metrics-collection.md +1 -1
- 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 +11 -1
- package/skills/session-end/session-metrics-write.md +45 -10
- package/skills/session-start/SKILL.md +3 -3
- package/skills/session-start/references/operations-contract.md +5 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-4-ssot-environment-check.md +8 -1
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +1 -1
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +1 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +8 -0
- package/skills/wave-executor/references/wave-loop-review.md +2 -2
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/.agents/skills/navigator/SKILL.md +0 -22
- package/.agents/skills/navigator/agents/openai.yaml +0 -5
- package/.codex-plugin/skills/navigator/SKILL.md +0 -22
- package/.codex-plugin/skills/navigator/agents/openai.yaml +0 -5
- package/.cursor/commands/navigator.md +0 -15
- package/.cursor/skills/navigator/SKILL.md +0 -13
- package/pi/prompts/navigator.md +0 -12
- package/skills/navigator/SKILL.md +0 -193
- package/skills/navigator/references/account-check.sh +0 -87
- package/skills/navigator/references/fleet-decisions.mjs +0 -231
- package/skills/navigator/references/slot-check.sh +0 -114
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* `open(2)` flags of a `noFollow` read. `O_NOFOLLOW` refuses a symlink as the
|
|
21
|
+
* last path component (ELOOP) in the open itself; `O_NONBLOCK` makes the
|
|
22
|
+
* read-side open of a FIFO return at once instead of blocking until a writer
|
|
23
|
+
* appears (a plain `'r'` open of a planted FIFO blocked a detached scan child
|
|
24
|
+
* for good — reproduced 2026-10-02, #1487). On a regular file `O_NONBLOCK`
|
|
25
|
+
* changes nothing. Both are POSIX-only; Node leaves them undefined on Windows,
|
|
26
|
+
* where the read degrades to a plain open.
|
|
27
|
+
*/
|
|
28
|
+
const NO_FOLLOW_READ_FLAGS = fs.constants.O_RDONLY
|
|
29
|
+
| (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0);
|
|
30
|
+
|
|
19
31
|
/**
|
|
20
32
|
* Read the last `maxBytes` of `file` as UTF-8.
|
|
21
33
|
*
|
|
@@ -28,14 +40,26 @@ import fs from 'node:fs';
|
|
|
28
40
|
* THROWS on any fs error (ENOENT, EACCES, …) — mapping the failure is the
|
|
29
41
|
* caller's decision.
|
|
30
42
|
*
|
|
43
|
+
* `opts.noFollow` is for a file a hostile local writer may have replaced: it
|
|
44
|
+
* opens with {@link NO_FOLLOW_READ_FLAGS} and then refuses anything the
|
|
45
|
+
* descriptor's `fstat` does not call a regular file (FIFO, directory, device),
|
|
46
|
+
* so a symlink throws ELOOP and every other non-regular file throws
|
|
47
|
+
* `ERR_NOT_REGULAR_FILE` — never a blocked open, never a read of a link
|
|
48
|
+
* target. The default (`false`) is the plain `'r'` open every other caller uses.
|
|
49
|
+
*
|
|
31
50
|
* @param {string} file
|
|
32
51
|
* @param {number} maxBytes window size in bytes (> 0)
|
|
52
|
+
* @param {{noFollow?: boolean}} [opts]
|
|
33
53
|
* @returns {{ text: string, cut: boolean, size: number }} `size` is the file size at open time
|
|
34
54
|
*/
|
|
35
|
-
export function readTailWindow(file, maxBytes) {
|
|
36
|
-
const fd = fs.openSync(file, 'r');
|
|
55
|
+
export function readTailWindow(file, maxBytes, { noFollow = false } = {}) {
|
|
56
|
+
const fd = fs.openSync(file, noFollow ? NO_FOLLOW_READ_FLAGS : 'r');
|
|
37
57
|
try {
|
|
38
|
-
const
|
|
58
|
+
const stats = fs.fstatSync(fd);
|
|
59
|
+
if (noFollow && !stats.isFile()) {
|
|
60
|
+
throw Object.assign(new Error('not a regular file'), { code: 'ERR_NOT_REGULAR_FILE' });
|
|
61
|
+
}
|
|
62
|
+
const size = stats.size;
|
|
39
63
|
const want = Math.min(size, maxBytes);
|
|
40
64
|
const start = size - want;
|
|
41
65
|
const buf = Buffer.allocUnsafe(want);
|
|
@@ -122,9 +122,12 @@ export const PRICING_TABLE = Object.freeze({
|
|
|
122
122
|
//
|
|
123
123
|
// Ceiling: a model absent from this table prices as null, never at a neighbour's rate, so the
|
|
124
124
|
// rollup reports `total_cost_usd: null` and `cost_records_priced < cost_records_total` — the
|
|
125
|
-
// intended way missing evidence shows up. That ratio also drops below 1 for
|
|
125
|
+
// intended way missing evidence shows up. That ratio also drops below 1 for agents no row can
|
|
126
126
|
// fix: a token-less record (transcript over the hook's size cap, without usage turns, or
|
|
127
|
-
// unreadable)
|
|
127
|
+
// unreadable), a record with a token-bearing turn that names no model (#1475), a started agent
|
|
128
|
+
// whose stop found no transcript of its own, and an agent seen only in schema_version 1 records
|
|
129
|
+
// (#1487 Pkt 1). The one complete list is the `session-token-rollup.mjs` module doc — the code
|
|
130
|
+
// that decides; this comment and `emit-session.mjs` only point at it (#1498). Revisit when
|
|
128
131
|
// an unpriced record names a model id absent from this table: add that model as its own
|
|
129
132
|
// evidenced row, never as a family prefix or an alias to another generation.
|
|
130
133
|
'claude-fable-5-1': usd({ input: 10.0, cache_read: 0.25, cache_creation: 12.5, output: 50.0 }),
|
|
@@ -312,6 +312,23 @@ export function readSessionProfileForMetricsDir(metricsDir) {
|
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Events window this reader needs retained (#1401 part 3), in days — a CHOSEN
|
|
317
|
+
* ceiling, not a derived value. `deriveSessionFromEvents` loads the whole
|
|
318
|
+
* rotated ledger (no time filter) and keeps the LATEST
|
|
319
|
+
* `orchestrator.session.started`, so what it needs retained is that one
|
|
320
|
+
* record; 1 day is the assumed maximum age of that record when a flush reads
|
|
321
|
+
* it. Nothing in this module bounds that age: `buildBatch`'s
|
|
322
|
+
* `[now - DAILY_FLUSH_MS, now]` cutoff filters `skill-invocations.jsonl`, not
|
|
323
|
+
* the events ledger. CEILING (BV-004): a latest `session.started` older than a
|
|
324
|
+
* day at flush time (a session running longer than that) is found only if
|
|
325
|
+
* retention happens to reach it; revisit if that becomes common. Read by
|
|
326
|
+
* `scripts/lib/events-retention-banner.mjs`.
|
|
327
|
+
*
|
|
328
|
+
* @type {number}
|
|
329
|
+
*/
|
|
330
|
+
export const REQUIRED_EVENTS_WINDOW_DAYS = 1;
|
|
331
|
+
|
|
315
332
|
/**
|
|
316
333
|
* Reconstruct the session facts a ping needs (`session_type`, `started_at`,
|
|
317
334
|
* `completed_at`) from `<metricsDir>/events.jsonl` when `sessions.jsonl` has no
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
* anywhere: basename `*.test.<ext>` / `*.spec.<ext>`, or any file
|
|
29
29
|
* under a `__tests__/` directory
|
|
30
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)
|
|
31
33
|
*
|
|
32
34
|
* ## The four questions, answered in code rather than left open
|
|
33
35
|
*
|
|
@@ -122,12 +124,32 @@ export const DECLARATION_SUFFIXES = Object.freeze(['.d.ts', '.d.mts', '.d.cts'])
|
|
|
122
124
|
/** Top-level path prefix that makes a tracked code file part of the TEST corpus. */
|
|
123
125
|
export const TEST_PREFIX = 'tests/';
|
|
124
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
|
+
|
|
125
138
|
/**
|
|
126
139
|
* Co-located test files, wherever they sit: a `*.test.<ext>` / `*.spec.<ext>`
|
|
127
140
|
* basename (the vitest/jest default include shape) or a `__tests__/` segment.
|
|
128
141
|
*/
|
|
129
142
|
const COLOCATED_TEST_RE = /\.(test|spec)\.[^./]+$|(^|\/)__tests__\//;
|
|
130
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
|
+
|
|
131
153
|
/**
|
|
132
154
|
* A tracked path that LOOKS like a test, counted or not: a `*.test.*` / `*.spec.*`
|
|
133
155
|
* JS/TS file, anything under a `__tests__/` segment, or any file under `tests/`
|
|
@@ -168,7 +190,21 @@ export function classifyPath(relPath) {
|
|
|
168
190
|
const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
169
191
|
if (!CODE_EXTENSIONS.some((ext) => p.endsWith(ext))) return null;
|
|
170
192
|
if (DECLARATION_SUFFIXES.some((ext) => p.endsWith(ext))) return null;
|
|
171
|
-
return
|
|
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';
|
|
172
208
|
}
|
|
173
209
|
|
|
174
210
|
/**
|
|
@@ -209,7 +245,9 @@ export function countPhysicalLines(content) {
|
|
|
209
245
|
* @param {number} [opts.ceiling]
|
|
210
246
|
* @returns {{testFiles:number,testLoc:number,srcFiles:number,srcLoc:number,ratio:number|null,
|
|
211
247
|
* ceiling:number,withinCorridor:boolean|null,consolidationWaveRequired:boolean|null,
|
|
212
|
-
* skipped:number,testLikeFilesSeen:number,reason:'no-tests-found'|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}}}}
|
|
213
251
|
*/
|
|
214
252
|
export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
215
253
|
let testFiles = 0;
|
|
@@ -218,9 +256,31 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
218
256
|
let srcLoc = 0;
|
|
219
257
|
let skipped = 0;
|
|
220
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 };
|
|
221
262
|
|
|
222
263
|
for (const rel of files) {
|
|
223
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
|
+
}
|
|
224
284
|
const bucket = classifyPath(rel);
|
|
225
285
|
if (bucket === null) continue;
|
|
226
286
|
const content = readFile(rel);
|
|
@@ -257,6 +317,7 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
257
317
|
skipped,
|
|
258
318
|
testLikeFilesSeen,
|
|
259
319
|
reason: 'no-tests-found',
|
|
320
|
+
excludedScope: { shell },
|
|
260
321
|
};
|
|
261
322
|
}
|
|
262
323
|
|
|
@@ -275,6 +336,7 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
|
|
|
275
336
|
skipped,
|
|
276
337
|
testLikeFilesSeen,
|
|
277
338
|
reason: null,
|
|
339
|
+
excludedScope: { shell },
|
|
278
340
|
};
|
|
279
341
|
}
|
|
280
342
|
|
|
@@ -297,6 +359,11 @@ export function definitionOf(source) {
|
|
|
297
359
|
`declaration files (${DECLARATION_SUFFIXES.map((x) => `*${x}`).join(', ')}: types, not ` +
|
|
298
360
|
'executable code) and non-code extensions (.md, .json, .jsonl, .yml) on BOTH sides; ' +
|
|
299
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.',
|
|
300
367
|
};
|
|
301
368
|
}
|
|
302
369
|
|
|
@@ -506,6 +573,9 @@ function main() {
|
|
|
506
573
|
console.log(' denominator every other tracked code file (src by negation)');
|
|
507
574
|
console.log(` code exts ${CODE_EXTENSIONS.join(' ')} (.md / .json never counted)`);
|
|
508
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
|
+
);
|
|
509
579
|
console.log(' lines physical; blanks + comments counted; EOF-newline-insensitive');
|
|
510
580
|
console.log('');
|
|
511
581
|
console.log(' --json machine-readable envelope on stdout');
|
|
@@ -575,9 +645,14 @@ function main() {
|
|
|
575
645
|
? 'within corridor'
|
|
576
646
|
: `ABOVE ceiling ${result.ceiling} — TV-003 consolidation wave required`;
|
|
577
647
|
const at = envelope.ref ? ` @ ${envelope.ref}${dirty ? '+dirty' : ''}` : '';
|
|
648
|
+
const sh = result.excludedScope.shell;
|
|
578
649
|
writeStdoutLineSync(
|
|
579
650
|
`tests:src = ${r} (${result.testLoc} test LOC / ${result.srcLoc} src LOC` +
|
|
580
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
|
+
: '') +
|
|
581
656
|
(dirty ? '\n NOTE: working tree is dirty — this number is NOT reproducible at that SHA' : ''),
|
|
582
657
|
);
|
|
583
658
|
}
|
|
@@ -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.
|