session-orchestrator 5.3.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/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/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/.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/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/navigator/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- 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 +70 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +57 -4
- package/docs/components.md +12 -12
- package/docs/events-schema.md +11 -8
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +55 -6
- package/docs/session-config-template.md +11 -2
- package/hooks/_lib/hook-import-set.json +27 -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 +53 -0
- package/hooks/on-stop.mjs +3 -1
- package/hooks/post-tool-batch-wave-signal.mjs +3 -1
- package/hooks/pre-bash-memory-propose-audit.mjs +52 -3
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +559 -72
- package/hooks/subagent-telemetry.mjs +96 -19
- package/package.json +3 -3
- package/pi/prompts/navigator.md +12 -0
- 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 +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +49 -53
- package/scripts/backfill-abandoned-sessions.mjs +1 -1
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/emit-session.mjs +296 -2
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -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 +10 -6
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- 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-schema.mjs +13 -0
- package/scripts/lib/config.mjs +71 -10
- package/scripts/lib/events.mjs +21 -2
- package/scripts/lib/fleet-protocol.mjs +270 -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/instruction-budget-guard.mjs +69 -11
- 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/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 +43 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +62 -11
- 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 +3 -4
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- 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 +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- 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 +40 -9
- 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/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +10 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +323 -302
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +72 -46
- 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/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +25 -11
- 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/evolve/SKILL.md +2 -2
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- 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/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 +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- 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/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +22 -60
- package/skills/session-plan/SKILL.md +0 -3
- 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 +2 -2
- 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/wave-executor/SKILL.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pricing.mjs — USD price table for token-bucket cost estimation (#1244).
|
|
2
|
+
* pricing.mjs — USD price table for token-bucket cost estimation (#1244, #1470).
|
|
3
3
|
*
|
|
4
4
|
* The subagent ledger records four token buckets per stop record
|
|
5
5
|
* (`token_input_uncached`, `token_cache_read`, `token_cache_creation`,
|
|
@@ -8,23 +8,39 @@
|
|
|
8
8
|
* an order of magnitude on a cache-heavy run — which is the whole reason this
|
|
9
9
|
* table exists rather than a single `$/token` constant.
|
|
10
10
|
*
|
|
11
|
-
* ##
|
|
11
|
+
* ## Every rate is a documented figure
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* carries
|
|
16
|
-
* in this prose.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* multiplied out here.
|
|
13
|
+
* All four rates of every row are first-party Anthropic API list prices, read
|
|
14
|
+
* off the price page each row names in `source_url` on the date
|
|
15
|
+
* `PRICING_TABLE_DATE` carries — that constant is the SSOT, never a second date
|
|
16
|
+
* repeated in this prose. Nothing here is multiplied out. `cache_read` in
|
|
17
|
+
* particular is never derived from `input`: the ratio is not a constant (Opus
|
|
18
|
+
* 5.5 reads at 0.05× its input rate, Fable 5.1 at 0.025×, Fable 5 at 0.1×), so
|
|
19
|
+
* a multiplier would misprice exactly the cache-heavy runs this table exists
|
|
20
|
+
* for. Each row carries its own provenance fields, so a consumer quoting a
|
|
21
|
+
* dollar figure can cite where it came from without reading this file.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* `verified: false` would mark a PLACEHOLDER row — a best-effort stand-in,
|
|
24
|
+
* never a quoted price. No current row carries it; a consumer publishing a
|
|
25
|
+
* dollar figure must still surface the flag.
|
|
26
|
+
*
|
|
27
|
+
* ## Only exact ids resolve
|
|
28
|
+
*
|
|
29
|
+
* `priceFor()` accepts an exact table key or an explicit `MODEL_ALIASES` entry
|
|
30
|
+
* and nothing else. Until #1470 it fell back to the longest table key an id
|
|
31
|
+
* started with, which handed an unlisted generation the rates of its nearest
|
|
32
|
+
* neighbour, marked verified.
|
|
33
|
+
*
|
|
34
|
+
* ## Series break (#1470, 2026-09-29)
|
|
35
|
+
*
|
|
36
|
+
* Session reports written before this fix priced records through that
|
|
37
|
+
* fallback: `claude-opus-5-5` at `claude-opus-5` rates, `claude-sonnet-5-5` at
|
|
38
|
+
* `claude-sonnet-5` rates (identical numbers, by coincidence rather than
|
|
39
|
+
* evidence), and `claude-fable-5` / `claude-opus-4-8` not at all. Nothing
|
|
40
|
+
* rewrites that history — the metrics ledgers are append-only. Measured
|
|
41
|
+
* 2026-09-29: 0 of the 9 priced sessions.jsonl reports in this repo contained
|
|
42
|
+
* such records (the first one reached subagents.jsonl after the last priced
|
|
43
|
+
* report was written).
|
|
28
44
|
*
|
|
29
45
|
* ## Null means UNKNOWN MODEL, never zero
|
|
30
46
|
*
|
|
@@ -39,68 +55,93 @@
|
|
|
39
55
|
*/
|
|
40
56
|
|
|
41
57
|
/**
|
|
42
|
-
* The date the
|
|
43
|
-
* Any consumer quoting a dollar figure should quote this
|
|
44
|
-
* price without its measurement date ages silently.
|
|
58
|
+
* The date the rates in PRICING_TABLE were read off the price page. Every row's
|
|
59
|
+
* `checked_at` equals it. Any consumer quoting a dollar figure should quote this
|
|
60
|
+
* date beside it — a price without its measurement date ages silently.
|
|
45
61
|
* @type {string}
|
|
46
62
|
*/
|
|
47
|
-
export const PRICING_TABLE_DATE = '2026-09-
|
|
63
|
+
export const PRICING_TABLE_DATE = '2026-09-29';
|
|
48
64
|
|
|
49
65
|
/**
|
|
50
66
|
* @typedef {Object} PricingRow
|
|
51
67
|
* @property {number} input - USD per 1M uncached prompt tokens.
|
|
52
68
|
* @property {number} cache_read - USD per 1M tokens served from the prompt cache.
|
|
53
|
-
* @property {number} cache_creation - USD per 1M tokens written to the prompt cache
|
|
69
|
+
* @property {number} cache_creation - USD per 1M tokens written to the prompt cache
|
|
70
|
+
* (the `cache_write_ttl` rate).
|
|
54
71
|
* @property {number} output - USD per 1M completion tokens.
|
|
55
72
|
* @property {boolean} verified - false ⇒ the row is a labelled placeholder, not a quoted price.
|
|
56
73
|
* @property {'documented'|'derived-multiplier'} cache_source - provenance of the two cache rates.
|
|
74
|
+
* Every current row is 'documented'; 'derived-multiplier' is kept for a future row whose cache
|
|
75
|
+
* rates are multiplied out and must say so.
|
|
76
|
+
* @property {string} source_url - the page all four rates were read from.
|
|
77
|
+
* @property {string} checked_at - ISO date the rates were read; equals PRICING_TABLE_DATE.
|
|
78
|
+
* @property {'anthropic'} provider
|
|
79
|
+
* @property {'5m'} cache_write_ttl - which cache-write rate `cache_creation` is.
|
|
80
|
+
* @property {string} unit - 'USD per 1M tokens'.
|
|
81
|
+
* @property {string} surface - the billing surface and speed the rates apply to.
|
|
82
|
+
* @property {'standard'} tier - the service tier the rates apply to.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/** Provenance shared by every current row — all read off one page on one date. */
|
|
86
|
+
const PROVENANCE = Object.freeze({
|
|
87
|
+
verified: true,
|
|
88
|
+
cache_source: 'documented',
|
|
89
|
+
source_url: 'https://platform.claude.com/docs/en/about-claude/pricing',
|
|
90
|
+
checked_at: PRICING_TABLE_DATE,
|
|
91
|
+
provider: 'anthropic',
|
|
92
|
+
cache_write_ttl: '5m',
|
|
93
|
+
unit: 'USD per 1M tokens',
|
|
94
|
+
surface: 'Claude API (first-party), standard speed',
|
|
95
|
+
tier: 'standard',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* One frozen documented row: four USD rates plus the shared provenance. The
|
|
100
|
+
* rates are spread last, so provenance can never overwrite a price.
|
|
101
|
+
* @param {{input: number, cache_read: number, cache_creation: number, output: number}} rates
|
|
102
|
+
* @returns {PricingRow}
|
|
57
103
|
*/
|
|
104
|
+
function usd(rates) {
|
|
105
|
+
return Object.freeze({ ...PROVENANCE, ...rates });
|
|
106
|
+
}
|
|
58
107
|
|
|
59
108
|
/**
|
|
60
109
|
* Per-model USD-per-million-token rates.
|
|
61
110
|
* @type {Readonly<Record<string, PricingRow>>}
|
|
62
111
|
*/
|
|
63
112
|
export const PRICING_TABLE = Object.freeze({
|
|
64
|
-
//
|
|
65
|
-
// (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}),
|
|
82
|
-
'claude-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}),
|
|
90
|
-
'claude-haiku-4-5': Object.freeze({
|
|
91
|
-
input: 1.0,
|
|
92
|
-
cache_read: 0.1,
|
|
93
|
-
cache_creation: 1.25,
|
|
94
|
-
output: 5.0,
|
|
95
|
-
verified: true,
|
|
96
|
-
cache_source: 'derived-multiplier',
|
|
97
|
-
}),
|
|
113
|
+
// Ceiling: only the 5-minute cache-write rate is priced; a 1-hour write costs 2× input
|
|
114
|
+
// (Opus 5.5: $8/MTok) and would be under-priced here. Measured 2026-09-29 over 803,788
|
|
115
|
+
// assistant usage blocks in 10,745 subagent transcripts: `usage.cache_creation
|
|
116
|
+
// .ephemeral_1h_input_tokens > 0` on 0 blocks. Revisit when the first such block appears.
|
|
117
|
+
//
|
|
118
|
+
// Ceiling: list price only — fast mode, `inference_geo: "us"` (1.1×) and batch discounts are
|
|
119
|
+
// not modelled. Measured 2026-09-29: `usage.speed` = "fast" on 0 of the 374,331 blocks that
|
|
120
|
+
// carry the field; `inference_geo` = "not_available" on 803,479 and no other value seen.
|
|
121
|
+
// Revisit when either field shows another value.
|
|
122
|
+
//
|
|
123
|
+
// Ceiling: a model absent from this table prices as null, never at a neighbour's rate, so the
|
|
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 records no row can
|
|
126
|
+
// fix: a token-less record (transcript over the hook's size cap, without usage turns, or
|
|
127
|
+
// unreadable) and a record with a token-bearing turn that names no model (#1475). Revisit when
|
|
128
|
+
// an unpriced record names a model id absent from this table: add that model as its own
|
|
129
|
+
// evidenced row, never as a family prefix or an alias to another generation.
|
|
130
|
+
'claude-fable-5-1': usd({ input: 10.0, cache_read: 0.25, cache_creation: 12.5, output: 50.0 }),
|
|
131
|
+
'claude-fable-5': usd({ input: 10.0, cache_read: 1.0, cache_creation: 12.5, output: 50.0 }),
|
|
132
|
+
'claude-opus-5-5': usd({ input: 4.0, cache_read: 0.2, cache_creation: 5.0, output: 20.0 }),
|
|
133
|
+
'claude-opus-5': usd({ input: 5.0, cache_read: 0.5, cache_creation: 6.25, output: 25.0 }),
|
|
134
|
+
'claude-opus-4-8': usd({ input: 5.0, cache_read: 0.5, cache_creation: 6.25, output: 25.0 }),
|
|
135
|
+
'claude-sonnet-5-5': usd({ input: 2.0, cache_read: 0.2, cache_creation: 2.5, output: 10.0 }),
|
|
136
|
+
'claude-sonnet-5': usd({ input: 2.0, cache_read: 0.2, cache_creation: 2.5, output: 10.0 }),
|
|
137
|
+
'claude-haiku-4-5': usd({ input: 1.0, cache_read: 0.1, cache_creation: 1.25, output: 5.0 }),
|
|
98
138
|
});
|
|
99
139
|
|
|
100
140
|
/**
|
|
101
141
|
* Explicit aliases for model ids that appear in transcripts but are not the
|
|
102
|
-
* canonical table key — dated
|
|
103
|
-
* the harness stamps on long-context sessions.
|
|
142
|
+
* canonical table key — a dated snapshot of the SAME model and the `[1m]`
|
|
143
|
+
* context-window suffix the harness stamps on long-context sessions. An alias
|
|
144
|
+
* never points at another generation.
|
|
104
145
|
* @type {Readonly<Record<string, string>>}
|
|
105
146
|
*/
|
|
106
147
|
export const MODEL_ALIASES = Object.freeze({
|
|
@@ -113,10 +154,19 @@ export const MODEL_ALIASES = Object.freeze({
|
|
|
113
154
|
/**
|
|
114
155
|
* Look up the rate row for a model id.
|
|
115
156
|
*
|
|
116
|
-
* Resolution
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* `claude-opus-5-
|
|
157
|
+
* Resolution: exact `PRICING_TABLE` key → explicit `MODEL_ALIASES` entry → `null`,
|
|
158
|
+
* after trimming surrounding whitespace.
|
|
159
|
+
* Nothing else resolves — no prefix, no pattern, no family fallback — so
|
|
160
|
+
* `claude-opus-5-20260401`, `claude-opus-5-99` and `claude-haiku-4-5-20251002` are
|
|
161
|
+
* all UNKNOWN even though each starts with a table key.
|
|
162
|
+
*
|
|
163
|
+
* Why no prefix: from the 4.6 generation on, Anthropic ships dateless pinned
|
|
164
|
+
* model ids (https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions,
|
|
165
|
+
* retrieved 2026-09-29). The ids that extend a current table key are therefore
|
|
166
|
+
* other generations, not snapshots of it — `claude-opus-5-5` extends
|
|
167
|
+
* `claude-opus-5` — and a prefix match priced them at another generation's
|
|
168
|
+
* rates, marked verified (#1470). A future generation gets its own evidenced
|
|
169
|
+
* row, never a family prefix.
|
|
120
170
|
*
|
|
121
171
|
* @param {string|null|undefined} modelId
|
|
122
172
|
* @returns {PricingRow|null} the row, or null when the model is UNKNOWN
|
|
@@ -129,14 +179,10 @@ export function priceFor(modelId) {
|
|
|
129
179
|
|
|
130
180
|
if (Object.hasOwn(PRICING_TABLE, id)) return PRICING_TABLE[id];
|
|
131
181
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let best = null;
|
|
136
|
-
for (const key of Object.keys(PRICING_TABLE)) {
|
|
137
|
-
if (id.startsWith(key) && (best === null || key.length > best.length)) best = key;
|
|
182
|
+
if (Object.hasOwn(MODEL_ALIASES, id) && Object.hasOwn(PRICING_TABLE, MODEL_ALIASES[id])) {
|
|
183
|
+
return PRICING_TABLE[MODEL_ALIASES[id]];
|
|
138
184
|
}
|
|
139
|
-
return
|
|
185
|
+
return null;
|
|
140
186
|
}
|
|
141
187
|
|
|
142
188
|
/**
|
|
@@ -161,9 +207,10 @@ function num(n) {
|
|
|
161
207
|
/**
|
|
162
208
|
* Estimate the USD cost of one record's four token buckets.
|
|
163
209
|
*
|
|
164
|
-
* Each bucket is multiplied by ITS OWN rate —
|
|
165
|
-
*
|
|
166
|
-
* blended rate is not an approximation of
|
|
210
|
+
* Each bucket is multiplied by ITS OWN rate — depending on the row, a cache
|
|
211
|
+
* read costs a tenth to a fortieth of an uncached prompt token and a 5-minute
|
|
212
|
+
* cache write 1.25× as much, so a blended rate is not an approximation of
|
|
213
|
+
* this, it is a different number.
|
|
167
214
|
*
|
|
168
215
|
* @param {object} opts
|
|
169
216
|
* @param {string|null|undefined} opts.model - model id from the transcript
|
|
@@ -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
|
|
|
@@ -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',
|