chore(cleanup): R-b R1 — Voyage's own stats, events and OTel export removed

Removes Voyage's measurement, statistics and export apparatus (decision 189;
condition 175 met in step 1) and the R-a leftovers: lib/stats/, lib/exporters/,
the otel-export and post-bash-stats hooks, yardstick, end-state and comparison
gates, synthesis-measure, gold-scorer, docs/observability.md,
examples/observability/, docs/T1-cc26-delegated-orchestration.md and their
tests and fixtures. The six command stats phases and the /trekexecute
main-merge event blocks go; intent-approval --stamp no longer emits
brief-approved. README and docs point to Claude Code's own OpenTelemetry.

Files removed: 52. Files edited: 37.
npm test: 2930 tests (2928 pass / 0 fail / 2 skipped) before, 2600 tests
(2598 pass / 0 fail / 2 skipped) after. removed-surfaces group R1 green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-07 20:11:00 +01:00
commit 21480e8627
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
89 changed files with 94 additions and 10290 deletions

View file

@ -350,10 +350,3 @@ test('trekcontinue Phase 0 has no parseArgs($ARGUMENTS call, and its parser line
assert.doesNotMatch(p05, /After `parseArgs` has resolved/);
assert.match(p05, /After the parser has printed/);
});
test('trekcontinue — stats write through stats-append, never silently skipped', () => {
const md = readCommand();
assert.match(md, /node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs trekcontinue <<'JSON'/);
assert.doesNotMatch(md, /skip (stats )?silently|silently skip/i);
assert.doesNotMatch(md, /\$\{CLAUDE_PLUGIN_DATA\}\/trekcontinue-stats\.jsonl/);
});

View file

@ -10,7 +10,7 @@
// - SC5: every status value the prose instructs the executor to WRITE is in the
// validator's lists. Excluded on purpose (their own vocabularies):
// plan_verification.status, the session-state / NEXT-SESSION-PROMPT status,
// the stats and report `result`, and the tolerant session-orders reader list.
// the report `result`, and the tolerant session-orders reader list.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';

View file

@ -374,8 +374,8 @@ test('[#33] the Composition rule says the resolver runs in Phase 1, and no line
test('[#33] sibling: Composition rule has no "uniformity and stats" and no gates auto-escalation', () => {
const f = flat(sectionText(read(), '## Composition rule (v5.1)'));
assert.doesNotMatch(f, /uniformity and stats/, 'the Composition rule still says the model is returned for stats');
assert.ok(f.includes('it is returned for cross-command uniformity; no stats record carries it.'),
'the Composition rule does not say no stats record carries the model');
assert.ok(f.includes('it is returned for cross-command uniformity.'),
'the Composition rule does not say why the model is returned');
assert.doesNotMatch(f, /gates auto-escalation/);
});

View file

@ -341,10 +341,3 @@ test('trekresearch — promises no maxTurns budget', () => {
assert.doesNotMatch(text, /maxTurns/);
assert.match(text, /- convention-scanner: medium\+ codebases only \(50\+ files\)/);
});
test('trekresearch — stats write through stats-append, never silently skipped', () => {
const md = read();
assert.match(md, /node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs trekresearch <<'JSON'/);
assert.doesNotMatch(md, /skip (stats )?silently|silently skip/i);
assert.doesNotMatch(md, /\$\{CLAUDE_PLUGIN_DATA\}\/trekresearch-stats\.jsonl/);
});

View file

@ -72,10 +72,3 @@ test('trekreview — SC7: brief_version 2.1 + no phase_signals + no partial →
`sequencing gate must fire; errors=${JSON.stringify(r.errors)}`,
);
});
test('trekreview — stats write through stats-append, never silently skipped', () => {
const md = read();
assert.match(md, /node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs trekreview <<'JSON'/);
assert.doesNotMatch(md, /skip (stats )?silently|silently skip/i);
assert.doesNotMatch(md, /\$\{CLAUDE_PLUGIN_DATA\}\/trekreview-stats\.jsonl/);
});

View file

@ -1,17 +0,0 @@
[
{
"reviewer": "brief-conformance-reviewer",
"findings": [
{ "severity": "BLOCKER", "rule_key": "UNIMPLEMENTED_CRITERION", "file": "lib/handlers/login.mjs", "line": 17 },
{ "severity": "MAJOR", "rule_key": "PLAN_EXECUTE_DRIFT", "file": "lib/handlers/login.mjs", "line": 13 }
]
},
{
"reviewer": "code-correctness-reviewer",
"findings": [
{ "severity": "BLOCKER", "rule_key": "SECURITY_INJECTION", "file": "lib/auth/jwt.mjs", "line": 19 },
{ "severity": "MAJOR", "rule_key": "MISSING_TEST", "file": "lib/auth/refresh.mjs", "line": 0 },
{ "severity": "MINOR", "rule_key": "MISSING_ERROR_HANDLING", "file": "lib/auth/refresh.mjs", "line": 10 }
]
}
]

View file

@ -1,32 +0,0 @@
{
"why": "Frozen 2026-09-17, when the end-state direction was chosen. These ids and check signatures are the end-state gate's denominator: an entry may CLOSE, it may never disappear or have its check changed. Changing this file is a decision, not a refactor - tests/scripts/end-state-gate.test.mjs pins it literally. Amended 2026-09-18 by operator decision: D-03 and D-04 were rewritten from phrase probes to behaviour probes, so both signatures moved on purpose. That is the mechanism working, not a bypass - the list may change, but only as a decision that is said out loud. Amended a second time the same day, as a maintenance decision by the maintainer (no operator decision was asked for or given): the D-03/D-04 behaviour probes were greps for an export name, and a two-line stub exporting both names closed them on a tree where nothing was fixed. They now RUN a named test. Amended a third time the same day, same standing: running a NAMED test moved the cosmetic close one level up - two EMPTY tests with the right names closed both defects again - so each condition now also names the module whose STUB its test must fell. All three amendments moved signatures on purpose, removed nothing, and are kept honest by permanent mutants in the gate's own test (M6: a stub exporting the symbols; M7: an empty test with the right name).",
"defects": {
"D-01": "48f7f5cca070091e8f9b9ca0c7cdb7a1880733691935c25d07ea1d3e8d508fc9",
"D-02": "dfc94dba04a9116ae9be2097a0c5a9d8313b02c7de1ca6b49a472c1823a6d1e1",
"D-03": "98698ce74c243353713786b8604160f6672568b090884e9428d9e5e9fbcad89d",
"D-04": "df11a2e95db7b014ce077db0473e5dde4195ace92d8077cacdc1cee273947b47",
"D-05": "8fe6df52b43496c3302400507cb98002dcf8d2a12d92085e14bda4c7819bc7ba",
"D-06": "72a787c8154d1c18849e9856dcf1a67703d6b5541f88db81d6bab13cab14b418",
"D-07": "1bf3e0cd36c621720697475b0a3fd7db78611d66b58a9ce035e7695e57c69f1b"
},
"experiments": {
"E-01": "f7718897cec8ace6879fe79f5f32760ec957c3642e7186f8aa1688ce77b3f4e8",
"E-02": "c8ff12c504ba7a9ac36f11bb8f9b713263e9b4eb28c07d984d9e2115980e740d",
"E-03": "17153ad8808615a833a49ae0c3523264ce055aa690e5542a75bae1874495e7e8"
},
"agents": {
"dir": "agents",
"spawnSites": "commands/*.md",
"referenceMarker": "Reference document, not a spawnable capability"
},
"decisions": {
"file": "STATE.md",
"section": "## Åpne operatørbeslutninger",
"open": "^- \\[ \\] ",
"closed": "^- \\[x\\] "
},
"freeze": {
"tag": "end-state-freeze",
"featPattern": "^feat(\\(|!|:)"
}
}

View file

@ -1,66 +0,0 @@
# HELP voyage_trekbrief_interview_turns voyage stats — trekbrief_interview_turns
# TYPE voyage_trekbrief_interview_turns gauge
voyage_trekbrief_interview_turns{_schema_id="trekbrief",slug="add-auth",mode="default",profile="economy",profile_source="env"} 7
# HELP voyage_trekbrief_research_topics voyage stats — trekbrief_research_topics
# TYPE voyage_trekbrief_research_topics gauge
voyage_trekbrief_research_topics{_schema_id="trekbrief",slug="add-auth",mode="default",profile="economy",profile_source="env"} 3
# HELP voyage_trekbrief_review_iterations voyage stats — trekbrief_review_iterations
# TYPE voyage_trekbrief_review_iterations gauge
voyage_trekbrief_review_iterations{_schema_id="trekbrief",slug="add-auth",mode="default",profile="economy",profile_source="env"} 2
# HELP voyage_trekexecute_steps_failed voyage stats — trekexecute_steps_failed
# TYPE voyage_trekexecute_steps_failed counter
voyage_trekexecute_steps_failed{_schema_id="trekexecute",plan="trekplan-add-auth.md",plan_type="plan",mode="execute",result="completed",profile="premium",profile_source="inheritance"} 0
# HELP voyage_trekexecute_steps_passed voyage stats — trekexecute_steps_passed
# TYPE voyage_trekexecute_steps_passed counter
voyage_trekexecute_steps_passed{_schema_id="trekexecute",plan="trekplan-add-auth.md",plan_type="plan",mode="execute",result="completed",profile="premium",profile_source="inheritance"} 12
# HELP voyage_trekexecute_steps_skipped voyage stats — trekexecute_steps_skipped
# TYPE voyage_trekexecute_steps_skipped counter
voyage_trekexecute_steps_skipped{_schema_id="trekexecute",plan="trekplan-add-auth.md",plan_type="plan",mode="execute",result="completed",profile="premium",profile_source="inheritance"} 0
# HELP voyage_trekexecute_steps_total voyage stats — trekexecute_steps_total
# TYPE voyage_trekexecute_steps_total counter
voyage_trekexecute_steps_total{_schema_id="trekexecute",plan="trekplan-add-auth.md",plan_type="plan",mode="execute",result="completed",profile="premium",profile_source="inheritance"} 12
# HELP voyage_trekplan_agents_deployed voyage stats — trekplan_agents_deployed
# TYPE voyage_trekplan_agents_deployed gauge
voyage_trekplan_agents_deployed{_schema_id="trekplan",slug="add-auth",mode="default",profile="premium",profile_source="flag"} 7
# HELP voyage_trekplan_codebase_files voyage stats — trekplan_codebase_files
# TYPE voyage_trekplan_codebase_files gauge
voyage_trekplan_codebase_files{_schema_id="trekplan",slug="add-auth",mode="default",profile="premium",profile_source="flag"} 156
# HELP voyage_trekplan_deep_dives voyage stats — trekplan_deep_dives
# TYPE voyage_trekplan_deep_dives gauge
voyage_trekplan_deep_dives{_schema_id="trekplan",slug="add-auth",mode="default",profile="premium",profile_source="flag"} 2
# HELP voyage_trekplan_research_briefs_used voyage stats — trekplan_research_briefs_used
# TYPE voyage_trekplan_research_briefs_used gauge
voyage_trekplan_research_briefs_used{_schema_id="trekplan",slug="add-auth",mode="default",profile="premium",profile_source="flag"} 3
# HELP voyage_trekresearch_agents_external voyage stats — trekresearch_agents_external
# TYPE voyage_trekresearch_agents_external gauge
voyage_trekresearch_agents_external{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 3
# HELP voyage_trekresearch_agents_local voyage stats — trekresearch_agents_local
# TYPE voyage_trekresearch_agents_local gauge
voyage_trekresearch_agents_local{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 5
# HELP voyage_trekresearch_contradictions voyage stats — trekresearch_contradictions
# TYPE voyage_trekresearch_contradictions gauge
voyage_trekresearch_contradictions{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 1
# HELP voyage_trekresearch_conv_turns voyage stats — trekresearch_conv_turns
# TYPE voyage_trekresearch_conv_turns gauge
voyage_trekresearch_conv_turns{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 5
# HELP voyage_trekresearch_dimensions voyage stats — trekresearch_dimensions
# TYPE voyage_trekresearch_dimensions gauge
voyage_trekresearch_dimensions{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 4
# HELP voyage_trekresearch_dimensions_baseline voyage stats — trekresearch_dimensions_baseline
# TYPE voyage_trekresearch_dimensions_baseline gauge
voyage_trekresearch_dimensions_baseline{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 3
# HELP voyage_trekresearch_empty_turns voyage stats — trekresearch_empty_turns
# TYPE voyage_trekresearch_empty_turns gauge
voyage_trekresearch_empty_turns{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 1
# HELP voyage_trekresearch_open_questions voyage stats — trekresearch_open_questions
# TYPE voyage_trekresearch_open_questions gauge
voyage_trekresearch_open_questions{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 2
# HELP voyage_trekresearch_unique_sources voyage stats — trekresearch_unique_sources
# TYPE voyage_trekresearch_unique_sources gauge
voyage_trekresearch_unique_sources{_schema_id="trekresearch",slug="add-auth",mode="default",scope="both",effort="high",profile="premium",profile_source="default"} 17
# HELP voyage_trekreview_duration_ms voyage stats — trekreview_duration_ms
# TYPE voyage_trekreview_duration_ms histogram
voyage_trekreview_duration_ms{_schema_id="trekreview",slug="add-auth",verdict="ALLOW",mode="default",profile="balanced",profile_source="flag"} 4521
# HELP voyage_trekreview_reviewed_files_count voyage stats — trekreview_reviewed_files_count
# TYPE voyage_trekreview_reviewed_files_count counter
voyage_trekreview_reviewed_files_count{_schema_id="trekreview",slug="add-auth",verdict="ALLOW",mode="default",profile="balanced",profile_source="flag"} 18

View file

@ -1,84 +0,0 @@
# Voyage JSONL stats — schema audit (v4.1 input)
> **Purpose:** Field-allowlist input for v4.1 OTel exporter (Step 11). Lists every
> field every voyage stats JSONL writer emits today, plus the additive fields v4.1
> introduces. Load-bearing for Step 11 (field-allowlist) and Step 8 (stats plumbing).
>
> **PII-flag:** `command_excerpt` from `hooks/scripts/post-bash-stats.mjs` slices
> the first 120 chars of an arbitrary Bash command — may contain operator paths,
> branch names, or fragments of secrets that survived the secrets-hook. CWE-212
> (Improper Cross-boundary Removal of Sensitive Data). The OTel exporter MUST
> NOT export this field unless the operator explicitly opts in via
> `VOYAGE_EXPORT_INCLUDE_COMMAND_EXCERPT=1` (deferred to v4.2 — v4.1 hard-excludes).
>
> **Additive v4.1 fields:** `profile`, `phase_models`, `parallel_agents`,
> `external_research_enabled`, `profile_source`. All are forward-compat: existing
> v4.0 consumers ignore unknown keys, v4.1 consumers get richer signal.
## Field table per JSONL writer
| schema_id | fields | writer_path | line_ref | v4.1 additive | PII |
|-----------|--------|-------------|----------|---------------|-----|
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1140-1164 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:828-861 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1305-1333 | profile, phase_models, parallel_agents, profile_source | none |
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:3016-3038 | profile, phase_models, profile_source | none |
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:682-735 | profile, phase_models, profile_source | none |
| trekcontinue-stats | ts, project, next_session_label, status | lib/stats/stats-append.mjs (trekcontinue.md Phase 5) | trekcontinue.md:271-275 | profile, profile_source | none |
| token-usage | ts, session_id, scope, model, tokens_input, tokens_output, tokens_cache_creation, tokens_cache_read, cost_usd, is_estimate, price_table_version | lib/stats/token-usage.mjs `captureTokenUsage` (via hooks/scripts/otel-export.mjs Stop hook, opt-in `VOYAGE_TOKEN_METER`) | token-usage.mjs:buildRecord | n/a (SKAL-2 schema) | session_id (stripped at export) |
The `main-merge-declined` event's `payload.reason` (the autonomy-events row) is one of the literal words of
`commands/trekexecute.md` Phase 8: `review-block`, `review-stale`, `review-unanswered`, `review-not-measured`,
`run-not-completed`, `no-main-branch`, `hold-for-review`, `merge-conflict`, `merge-refused`.
## Field-allowlist input for Step 11
The OTel exporter (Step 11 `lib/exporters/field-allowlist.mjs`) MUST inline the
following static const arrays (NOT load from this file at runtime — Step 11
explicit constraint: INLINE static const, IKKE runtime fra tests/fixtures):
**EXPORT_ALLOWLIST** (numeric/bool/short-string fields safe for OTel metric labels):
```
ts, slug, mode, brief_quality, auto_research, auto_result,
codebase_size, codebase_files, agents_deployed, deep_dives,
agents_local, agents_external, gemini_used, dimensions, confidence,
contradictions, open_questions, interview_turns, review_iterations,
research_topics, research_briefs_used, research_scout_used,
critic_verdict, guardian_verdict, outcome, plan_type, result,
steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step,
verdict, reviewed_files_count, duration_ms, status, next_session_label,
event, known_event, success, scope,
profile, profile_source, parallel_agents, external_research_enabled,
model, tokens_input, tokens_output, tokens_cache_creation, tokens_cache_read,
cost_usd, is_estimate, price_table_version
```
**EXPORT_DENYLIST** (PII or high-cardinality, never export):
```
task, question, project_dir, project, plan, brief_path, command_excerpt, payload, counts, phase_models, session_id,
order_id, order_claimed_at, order_reason
```
> Notes:
> - `task` and `question` may contain user-content prose → high-cardinality + PII risk.
> - `project_dir` and paths leak filesystem layout.
> - `command_excerpt` per CWE-212 above.
> - `phase_models` is a structured object (6 keys) — too high-cardinality for label;
> profile name (`profile`) is the safe summary. v4.2 may revisit if operators ask.
> - `counts` (review BLOCKER/MAJOR/MINOR/SUGGESTION) is a nested object — Step 11
> exporter flattens to `voyage_review_counts_blocker`/`_major`/`_minor`/`_suggestion`
> metrics rather than a label.
> - `session_id` is a UUID — high-cardinality, not useful as a label, log-only.
## Cross-reference
- Step 8 (stats plumbing) — adds `profile` + `phase_models` + `profile_source` to all
6 orchestrator-emit sites listed above.
- Step 11 (field-allowlist) — codifies the EXPORT_ALLOWLIST/DENYLIST arrays above
as inline static consts in `lib/exporters/field-allowlist.mjs`.
- Step 9 (Prometheus textfile) — emits one metric line per allowlist-numeric field
per JSONL writer; PII-flagged fields are dropped at format-layer, not export-layer.

View file

@ -1,5 +0,0 @@
{"_schema_id":"trekplan","ts":"2026-05-09T08:00:00.000Z","slug":"add-auth","mode":"default","codebase_files":156,"agents_deployed":7,"deep_dives":2,"research_briefs_used":3,"profile":"premium","profile_source":"flag"}
{"_schema_id":"trekexecute","ts":"2026-05-09T08:30:00.000Z","plan":"trekplan-add-auth.md","plan_type":"plan","mode":"execute","result":"completed","steps_total":12,"steps_passed":12,"steps_failed":0,"steps_skipped":0,"profile":"premium","profile_source":"inheritance"}
{"_schema_id":"trekreview","ts":"2026-05-09T09:00:00.000Z","slug":"add-auth","verdict":"ALLOW","reviewed_files_count":18,"mode":"default","duration_ms":4521,"profile":"balanced","profile_source":"flag"}
{"_schema_id":"trekbrief","ts":"2026-05-09T07:00:00.000Z","slug":"add-auth","mode":"default","interview_turns":7,"review_iterations":2,"research_topics":3,"profile":"economy","profile_source":"env"}
{"_schema_id":"trekresearch","ts":"2026-05-09T07:30:00.000Z","slug":"add-auth","mode":"default","scope":"both","dimensions":4,"dimensions_baseline":3,"effort":"high","conv_turns":5,"empty_turns":1,"unique_sources":17,"agents_local":5,"agents_external":3,"contradictions":1,"open_questions":2,"profile":"premium","profile_source":"default"}

View file

@ -1,5 +0,0 @@
{"ts":"2026-05-09T07:00:00.000Z","slug":"add-auth","mode":"default","interview_turns":7,"review_iterations":2,"brief_quality":"complete","research_topics":3,"profile":"economy","phase_models":{"brief":"sonnet","research":"sonnet","plan":"sonnet","execute":"sonnet","review":"sonnet","continue":"sonnet"},"profile_source":"flag"}
{"ts":"2026-05-09T07:30:00.000Z","slug":"add-auth","mode":"default","scope":"local","dimensions":4,"agents_local":3,"agents_external":0,"profile":"economy","phase_models":{"brief":"sonnet","research":"sonnet","plan":"sonnet","execute":"sonnet","review":"sonnet","continue":"sonnet"},"parallel_agents":3,"external_research_enabled":false,"profile_source":"env"}
{"ts":"2026-05-09T08:00:00.000Z","slug":"add-auth","mode":"default","codebase_files":156,"agents_deployed":6,"deep_dives":2,"critic_verdict":"PASS","guardian_verdict":"ALIGNED","outcome":"execute","profile":"balanced","phase_models":{"brief":"sonnet","research":"sonnet","plan":"opus","execute":"sonnet","review":"opus","continue":"sonnet"},"parallel_agents":6,"profile_source":"default"}
{"ts":"2026-05-09T08:30:00.000Z","plan":"trekplan-add-auth.md","plan_type":"plan","mode":"execute","result":"completed","steps_total":12,"steps_passed":12,"steps_failed":0,"steps_skipped":0,"profile":"balanced","phase_models":{"brief":"sonnet","research":"sonnet","plan":"opus","execute":"sonnet","review":"opus","continue":"sonnet"},"profile_source":"inheritance"}
{"ts":"2026-05-09T09:00:00.000Z","slug":"add-auth","verdict":"ALLOW","reviewed_files_count":18,"mode":"default","duration_ms":4521,"profile":"premium","phase_models":{"brief":"opus","research":"opus","plan":"opus","execute":"opus","review":"opus","continue":"opus"},"profile_source":"flag"}

View file

@ -1,8 +0,0 @@
{"type":"user","message":{"role":"user","content":"hi"}}
{"type":"assistant","isSidechain":false,"requestId":"req_A","message":{"model":"claude-opus-4-8","usage":{"input_tokens":1,"output_tokens":1,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}
{"type":"assistant","isSidechain":false,"requestId":"req_A","message":{"model":"claude-opus-4-8","usage":{"input_tokens":1000,"output_tokens":200,"cache_creation_input_tokens":400,"cache_read_input_tokens":5000}}}
{"type":"assistant","isSidechain":false,"requestId":"req_B","message":{"model":"claude-opus-4-8","usage":{"input_tokens":2000,"output_tokens":300,"cache_creation_input_tokens":600,"cache_read_input_tokens":1000}}}
{"type":"assistant","isSidechain":true,"requestId":"req_SIDE","message":{"model":"claude-opus-4-8","usage":{"input_tokens":9999,"output_tokens":9999,"cache_creation_input_tokens":9999,"cache_read_input_tokens":9999}}}
{"type":"system","content":"some system note with no usage"}
this is not valid json and must be skipped
{"type":"assistant","isSidechain":false,"requestId":"req_B","message":{"model":"claude-opus-4-8","usage":{"input_tokens":2000,"output_tokens":300,"cache_creation_input_tokens":600,"cache_read_input_tokens":1000}}}

View file

@ -527,7 +527,7 @@ test('write snippet then real hook: the loop’s own writer arms the enforcement
});
// -----------------------------------------------------------------------
// Wiring — pattern from tests/hooks/hooks-json-stop-wired.test.mjs
// Wiring
// -----------------------------------------------------------------------
function invocationOf(h) {
return [h.command || '', ...(h.args || [])].join(' ').trim();

View file

@ -1,73 +0,0 @@
// SC-13: hooks.json wires Stop event to otel-export.mjs
// HIGH-risk-mitigering — verify deterministic config-pinning (mønster fra
// tests/lib/doc-consistency.test.mjs).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const HOOKS_JSON_PATH = resolve(__dirname, '../../hooks/hooks.json');
function loadHooksJson() {
const raw = readFileSync(HOOKS_JSON_PATH, 'utf8');
return JSON.parse(raw);
}
// Hooks may use shell form ({command: "node X.mjs"}) or exec form
// ({command: "node", args: ["X.mjs"]}, CC 2.1.139). Normalize both to a single
// invocation string so assertions are form-agnostic.
function invocationOf(h) {
return [h.command || '', ...(h.args || [])].join(' ').trim();
}
test('hooks.json — Stop key exists with at least one entry', () => {
const cfg = loadHooksJson();
assert.ok(cfg.hooks, 'hooks.json mangler top-level "hooks" object');
assert.ok(Array.isArray(cfg.hooks.Stop), 'hooks.json mangler "Stop" array');
assert.ok(cfg.hooks.Stop.length >= 1, 'Stop array er tom — forventet ≥1 entry');
});
test('hooks.json — Stop entry refererer otel-export.mjs', () => {
const cfg = loadHooksJson();
const stopEntries = cfg.hooks.Stop;
const allInvocations = stopEntries.flatMap((entry) =>
(entry.hooks || []).map(invocationOf),
);
const hasOtelExport = allInvocations.some((cmd) => cmd.includes('otel-export.mjs'));
assert.ok(
hasOtelExport,
`ingen Stop-hook refererer otel-export.mjs. Funnet: ${JSON.stringify(allInvocations)}`,
);
});
test('hooks.json — Stop entry bruker ${CLAUDE_PLUGIN_ROOT}-substitusjon', () => {
const cfg = loadHooksJson();
const stopEntries = cfg.hooks.Stop;
const otelEntry = stopEntries
.flatMap((entry) => entry.hooks || [])
.find((h) => invocationOf(h).includes('otel-export.mjs'));
assert.ok(otelEntry, 'fant ikke otel-export-entry i Stop');
const otelInvocation = invocationOf(otelEntry);
assert.match(
otelInvocation,
/\$\{CLAUDE_PLUGIN_ROOT\}/,
'otel-export-invocation bruker ikke ${CLAUDE_PLUGIN_ROOT}-prefix — relative paths feiler i headless',
);
assert.match(
otelInvocation,
/^node\s+/,
'otel-export-invocation starter ikke med "node " — invocation-form ikke korrekt',
);
});
test('hooks.json — Stop entry har "type": "command"', () => {
const cfg = loadHooksJson();
const stopEntries = cfg.hooks.Stop;
const otelHook = stopEntries
.flatMap((entry) => entry.hooks || [])
.find((h) => invocationOf(h).includes('otel-export.mjs'));
assert.equal(otelHook.type, 'command', 'otel-export-hook mangler "type": "command"');
});

View file

@ -1,110 +0,0 @@
// tests/hooks/otel-export-otlp.test.mjs
// SC #13: lib/exporters/otlp-format.mjs returns OTLP/JSON v1.0 metrics payload
// with INTEGER (not string) enum constants and timeUnixNano as decimal STRING
// (JS precision-loss mitigation per research/01 + risk-assessor CRITICAL 2).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
transformToOtlpJson,
AGG_TEMPORALITY_CUMULATIVE,
AGG_TEMPORALITY_DELTA,
AGG_TEMPORALITY_UNSPECIFIED,
} from '../../lib/exporters/otlp-format.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURES = join(__dirname, '..', 'fixtures');
function loadJsonl(name) {
const text = readFileSync(join(FIXTURES, name), 'utf-8');
return text.trim().split('\n').filter(Boolean).map(l => JSON.parse(l));
}
test('SC #13: aggregationTemporality is INTEGER (typeof === "number"), not string', () => {
const records = loadJsonl('stats-sample.jsonl');
const payload = transformToOtlpJson(records);
// Find a sum metric (steps_passed is a counter)
const metrics = payload.resourceMetrics[0].scopeMetrics[0].metrics;
const sumMetric = metrics.find(m => 'sum' in m);
assert.ok(sumMetric, `expected at least one sum-metric in payload, got ${metrics.length} metrics`);
// CRITICAL: this assertion is the heart of SC #13 — typeof MUST be 'number'
assert.equal(typeof sumMetric.sum.aggregationTemporality, 'number',
`aggregationTemporality must be INTEGER (typeof number), got ${typeof sumMetric.sum.aggregationTemporality}`);
assert.equal(sumMetric.sum.aggregationTemporality, AGG_TEMPORALITY_CUMULATIVE);
assert.equal(sumMetric.sum.aggregationTemporality, 2);
});
test('SC #13: enum constants exported as integer literals (drift-pin)', () => {
assert.equal(typeof AGG_TEMPORALITY_UNSPECIFIED, 'number');
assert.equal(AGG_TEMPORALITY_UNSPECIFIED, 0);
assert.equal(typeof AGG_TEMPORALITY_DELTA, 'number');
assert.equal(AGG_TEMPORALITY_DELTA, 1);
assert.equal(typeof AGG_TEMPORALITY_CUMULATIVE, 'number');
assert.equal(AGG_TEMPORALITY_CUMULATIVE, 2);
});
test('SC #13: timeUnixNano is decimal STRING (typeof === "string"), JS precision-loss mitigation', () => {
const records = loadJsonl('stats-sample.jsonl');
const payload = transformToOtlpJson(records);
const metrics = payload.resourceMetrics[0].scopeMetrics[0].metrics;
// Pick first metric with a data point
const m = metrics.find(x => (x.sum?.dataPoints?.length || x.gauge?.dataPoints?.length) > 0);
const dp = (m.sum || m.gauge).dataPoints[0];
assert.equal(typeof dp.timeUnixNano, 'string',
`timeUnixNano must be decimal STRING, got ${typeof dp.timeUnixNano}: ${dp.timeUnixNano}`);
assert.equal(typeof dp.startTimeUnixNano, 'string');
// Should be a valid decimal-digit string
assert.match(dp.timeUnixNano, /^\d+$/);
});
test('SC #13: structural shape — resourceMetrics[].scopeMetrics[].metrics[] hierarchy', () => {
const records = loadJsonl('stats-sample.jsonl');
const payload = transformToOtlpJson(records);
assert.ok(Array.isArray(payload.resourceMetrics));
assert.ok(payload.resourceMetrics.length >= 1);
assert.ok(payload.resourceMetrics[0].resource);
assert.ok(Array.isArray(payload.resourceMetrics[0].scopeMetrics));
assert.ok(payload.resourceMetrics[0].scopeMetrics[0].scope);
assert.equal(payload.resourceMetrics[0].scopeMetrics[0].scope.name, 'voyage');
assert.ok(Array.isArray(payload.resourceMetrics[0].scopeMetrics[0].metrics));
});
test('Empty input: returns valid OTLP envelope with empty metrics array', () => {
const payload = transformToOtlpJson([]);
assert.ok(Array.isArray(payload.resourceMetrics));
assert.equal(payload.resourceMetrics[0].scopeMetrics[0].metrics.length, 0);
});
test('isSum heuristic: counter-named metrics get sum + isMonotonic; others get gauge', () => {
const records = [
{ _schema_id: 'test', ts: '2026-05-09T08:00:00.000Z', steps_total: 10 }, // counter
{ _schema_id: 'test', ts: '2026-05-09T08:00:00.000Z', cpu_pct: 42.5 }, // gauge
];
const payload = transformToOtlpJson(records);
const metrics = payload.resourceMetrics[0].scopeMetrics[0].metrics;
const totalMetric = metrics.find(m => m.name.endsWith('steps_total'));
const cpuMetric = metrics.find(m => m.name.endsWith('cpu_pct'));
assert.ok(totalMetric.sum, 'counter should have sum');
assert.equal(totalMetric.sum.isMonotonic, true);
assert.equal(typeof totalMetric.sum.aggregationTemporality, 'number');
assert.ok(cpuMetric.gauge, 'non-counter should have gauge');
assert.ok(!cpuMetric.sum, 'gauge should not have sum');
});
test('Allowlist redacted: callers strip command_excerpt before passing — verify nothing leaks', () => {
const record = {
_schema_id: 'post_bash_stats',
ts: '2026-05-09T08:00:00.000Z',
duration_ms: 152,
success: true,
};
const payload = transformToOtlpJson([record]);
const json = JSON.stringify(payload);
assert.ok(!json.includes('command_excerpt'));
assert.ok(!json.includes('session_id'));
// Should contain duration_ms metric
assert.match(json, /post_bash_stats\.duration_ms/);
});

View file

@ -1,135 +0,0 @@
// tests/hooks/otel-export-textfile.test.mjs
// SC #12: lib/exporters/textfile-format.mjs produces deterministic Prometheus
// text-format output that matches expected.prom byte-for-byte.
//
// To regenerate snapshot:
// node scripts/gen-expected-prom.mjs > tests/fixtures/expected.prom
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { transformToPrometheus, normalizeMetricName } from '../../lib/exporters/textfile-format.mjs';
import { transformToOtlpJson } from '../../lib/exporters/otlp-format.mjs';
import { applyFieldAllowlist } from '../../lib/exporters/field-allowlist.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURES = join(__dirname, '..', 'fixtures');
function loadJsonl(name) {
const text = readFileSync(join(FIXTURES, name), 'utf-8');
return text.trim().split('\n').filter(Boolean).map(l => JSON.parse(l));
}
test('SC #12: stats-sample.jsonl → expected.prom snapshot byte-for-byte match', () => {
const records = loadJsonl('stats-sample.jsonl');
const actual = transformToPrometheus(records);
const expected = readFileSync(join(FIXTURES, 'expected.prom'), 'utf-8');
assert.equal(actual, expected,
`Snapshot drift detected. Regenerate via:\n` +
` node scripts/gen-expected-prom.mjs > tests/fixtures/expected.prom`);
});
test('Step 9: the four numeric STORM fields are metric families and effort is a label', () => {
const expected = readFileSync(join(FIXTURES, 'expected.prom'), 'utf-8');
for (const field of ['unique_sources', 'dimensions_baseline', 'conv_turns', 'empty_turns']) {
assert.match(
expected,
new RegExp(`^# TYPE voyage_trekresearch_${field} `, 'm'),
`${field} must appear as its own metric family — a numeric that never becomes a metric cannot be measured`,
);
}
// effort is a low-cardinality string: it must ride along as a LABEL, never
// as a metric family (a label is what makes high-vs-standard groupable).
assert.match(expected, /effort="[a-z]+"/, 'effort must be emitted as a label');
assert.doesNotMatch(
expected,
/^# TYPE voyage_trekresearch_effort /m,
'effort must not become a metric family',
);
});
test('empty-input handling: [] returns empty string (no headers)', () => {
assert.equal(transformToPrometheus([]), '');
assert.equal(transformToPrometheus(null), '');
assert.equal(transformToPrometheus(undefined), '');
});
test('allowlist-redaction: caller-redacted records (without command_excerpt/session_id) emit cleanly', () => {
// Simulate an allowlist-applied post-bash-stats record (command_excerpt + session_id removed)
const record = {
_schema_id: 'post_bash_stats',
ts: '2026-05-09T08:00:00.000Z',
duration_ms: 152,
success: true,
};
const out = transformToPrometheus([record]);
// Must NOT contain command_excerpt nor session_id (caller's responsibility, but verify)
assert.ok(!out.includes('command_excerpt'), 'command_excerpt leaked into output');
assert.ok(!out.includes('session_id'), 'session_id leaked into output');
// Must contain duration_ms metric
assert.match(out, /voyage_post_bash_stats_duration_ms/);
assert.match(out, / 152$/m);
// Boolean coerced to 1
assert.match(out, /voyage_post_bash_stats_success.* 1$/m);
});
test('NO client-side timestamps in output (per research/01 node_exporter#1284 mitigation)', () => {
const records = loadJsonl('stats-sample.jsonl');
const out = transformToPrometheus(records);
const lines = out.split('\n');
for (const line of lines) {
if (line.startsWith('#') || line === '') continue;
// Sample line format: metric{labels} value [timestamp]
// We must NOT have a trailing numeric timestamp after the value.
const parts = line.trim().split(' ');
assert.ok(parts.length === 2,
`Line has unexpected token count (timestamp leaked?): ${line}`);
}
});
test('normalizeMetricName: dots/dashes/spaces → underscore, lowercase, voyage_ prefix', () => {
assert.equal(normalizeMetricName('voyage.hook.duration_ms'), 'voyage_voyage_hook_duration_ms');
assert.equal(normalizeMetricName('Plan-Critic Verdict'), 'voyage_plan_critic_verdict');
assert.equal(normalizeMetricName('METRIC NAME'), 'voyage_metric_name');
});
test('SC#6: allowlisted token-usage record → Prometheus + OTLP metrics; PII absent (end-to-end)', () => {
// Raw record as written to token-usage-stats.jsonl (carries PII for upsert keying).
const raw = {
session_id: 'x',
transcript_path: '/p',
cwd: '/c',
ts: '2026-06-26T12:00:00.000Z',
scope: 'main-context',
model: 'claude-opus-4-8',
tokens_input: 12345,
tokens_output: 6789,
cost_usd: 0.42,
is_estimate: false,
price_table_version: '2026-06-26',
};
const rec = applyFieldAllowlist(raw, 'token-usage');
// Prometheus: numeric fields auto-promote to voyage_token_usage_* metrics.
const prom = transformToPrometheus([rec]);
assert.match(prom, /voyage_token_usage_tokens_input\b/);
assert.match(prom, /voyage_token_usage_cost_usd\b/);
// CWE-212: PII must never appear in exporter output.
assert.ok(!prom.includes('session_id'), 'session_id leaked into Prometheus output');
assert.ok(!prom.includes('transcript_path'), 'transcript_path leaked into Prometheus output');
assert.ok(!prom.includes('/p'), 'transcript_path value leaked into Prometheus output');
// OTLP parity: dot-separated, dash-preserved naming (NOT the Prometheus underscore form).
const otlp = JSON.stringify(transformToOtlpJson([rec]));
assert.ok(otlp.includes('voyage.token-usage.tokens_input'), 'OTLP token metric missing');
assert.ok(!otlp.includes('transcript_path'), 'transcript_path leaked into OTLP output');
});
test('determinism: identical input produces identical output (sorted keys)', () => {
const records = loadJsonl('stats-sample.jsonl');
const out1 = transformToPrometheus(records);
const out2 = transformToPrometheus([...records]);
assert.equal(out1, out2);
});

View file

@ -1,147 +0,0 @@
// tests/hooks/otel-export-token-capture.test.mjs
// SKAL-2 Step 4 — opt-in main-context token/cost capture folded into the Stop
// hook (otel-export.mjs), gated by VOYAGE_TOKEN_METER.
//
// Fail-open contract: any error → exit 0, no token file, Stop never blocked.
// Pattern: tests/hooks/otel-export.test.mjs (setupDataDir + runHookWithEnv).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { runHookWithEnv } from '../helpers/hook-helper.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const HOOK_PATH = join(HERE, '..', '..', 'hooks', 'scripts', 'otel-export.mjs');
const FIXTURE = join(HERE, '..', 'fixtures', 'token-usage', 'sample-transcript.jsonl');
const TOKEN_FILE = 'token-usage-stats.jsonl';
const mkDataDir = () => mkdtempSync(join(tmpdir(), 'voyage-token-capture-'));
const stdinFor = (extra = {}) => JSON.stringify({ transcript_path: FIXTURE, session_id: 'sess-1', ...extra });
const readToken = (dir) => readFileSync(join(dir, TOKEN_FILE), 'utf-8').trim().split('\n').filter(Boolean).map(JSON.parse);
test('VOYAGE_TOKEN_METER=1 → writes token-usage-stats.jsonl with expected totals + scope:main-context', async () => {
const dir = mkDataDir();
try {
const r = await runHookWithEnv(HOOK_PATH, stdinFor(), {
VOYAGE_TOKEN_METER: '1',
VOYAGE_EXPORT_MODE: 'off',
CLAUDE_PLUGIN_DATA: dir,
});
assert.equal(r.code, 0);
assert.equal(existsSync(join(dir, TOKEN_FILE)), true, 'token file must be written');
const recs = readToken(dir);
assert.equal(recs.length, 1);
const rec = recs[0];
// Fixture totals: dedup-by-requestId + sidechain-exclusion (verified in Step 1).
assert.equal(rec.tokens_input, 3000);
assert.equal(rec.tokens_output, 500);
assert.equal(rec.tokens_cache_creation, 1000);
assert.equal(rec.tokens_cache_read, 6000);
assert.equal(rec.scope, 'main-context');
assert.equal(rec.model, 'claude-opus-4-8');
assert.ok(Math.abs(rec.cost_usd - 0.03675) < 1e-9, `cost ${rec.cost_usd}`);
assert.equal(rec.session_id, 'sess-1');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('UPSERT — running twice for the same session_id keeps ONE line (not two)', async () => {
const dir = mkDataDir();
try {
const env = { VOYAGE_TOKEN_METER: '1', VOYAGE_EXPORT_MODE: 'off', CLAUDE_PLUGIN_DATA: dir };
await runHookWithEnv(HOOK_PATH, stdinFor(), env);
const r2 = await runHookWithEnv(HOOK_PATH, stdinFor(), env);
assert.equal(r2.code, 0);
const recs = readToken(dir);
assert.equal(recs.length, 1, 'upsert must NOT append a second line for the same session');
assert.equal(recs[0].tokens_input, 3000);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('UPSERT — distinct session_ids append (one line per session)', async () => {
const dir = mkDataDir();
try {
const env = { VOYAGE_TOKEN_METER: '1', VOYAGE_EXPORT_MODE: 'off', CLAUDE_PLUGIN_DATA: dir };
await runHookWithEnv(HOOK_PATH, stdinFor({ session_id: 'A' }), env);
await runHookWithEnv(HOOK_PATH, stdinFor({ session_id: 'B' }), env);
const recs = readToken(dir);
assert.equal(recs.length, 2);
assert.deepEqual(recs.map(r => r.session_id).sort(), ['A', 'B']);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('OPT-IN — VOYAGE_TOKEN_METER unset → NO token file written', async () => {
const dir = mkDataDir();
try {
const r = await runHookWithEnv(HOOK_PATH, stdinFor(), {
VOYAGE_TOKEN_METER: '', // explicit empty mimics unset
VOYAGE_EXPORT_MODE: 'off',
CLAUDE_PLUGIN_DATA: dir,
});
assert.equal(r.code, 0);
assert.equal(existsSync(join(dir, TOKEN_FILE)), false, 'no token file without opt-in');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('FAIL-OPEN — malformed stdin → exit 0, no token file, no throw', async () => {
const dir = mkDataDir();
try {
const r = await runHookWithEnv(HOOK_PATH, 'not valid json {{{', {
VOYAGE_TOKEN_METER: '1',
VOYAGE_EXPORT_MODE: 'off',
CLAUDE_PLUGIN_DATA: dir,
});
assert.equal(r.code, 0);
assert.equal(existsSync(join(dir, TOKEN_FILE)), false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('FAIL-OPEN — missing transcript file → exit 0, no token file', async () => {
const dir = mkDataDir();
try {
const r = await runHookWithEnv(
HOOK_PATH,
JSON.stringify({ transcript_path: '/no/such/transcript.jsonl', session_id: 'x' }),
{ VOYAGE_TOKEN_METER: '1', VOYAGE_EXPORT_MODE: 'off', CLAUDE_PLUGIN_DATA: dir },
);
assert.equal(r.code, 0);
assert.equal(existsSync(join(dir, TOKEN_FILE)), false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('capture coexists with export — VOYAGE_TOKEN_METER=1 + textfile mode writes BOTH', async () => {
const dir = mkDataDir();
try {
// Seed a stats file so the export path has something to write.
writeFileSync(join(dir, 'trekplan-stats.jsonl'),
JSON.stringify({ ts: '2026-06-26T08:00:00.000Z', slug: 't', mode: 'default', codebase_files: 10, profile: 'premium' }) + '\n');
const r = await runHookWithEnv(HOOK_PATH, stdinFor(), {
VOYAGE_TOKEN_METER: '1',
VOYAGE_EXPORT_MODE: 'textfile',
CLAUDE_PLUGIN_DATA: dir,
});
assert.equal(r.code, 0);
assert.equal(existsSync(join(dir, TOKEN_FILE)), true, 'token file written');
assert.equal(existsSync(join(dir, 'voyage.prom')), true, 'export still completed');
// The exported textfile should now also carry the token metric (STATS_FILES wiring).
const prom = readFileSync(join(dir, 'voyage.prom'), 'utf-8');
assert.match(prom, /voyage_token_usage_tokens_input/);
assert.ok(!prom.includes('sess-1'), 'session_id must not leak into export (CWE-212)');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

View file

@ -1,349 +0,0 @@
// tests/hooks/otel-export-validators.test.mjs
// Step 11 validators: path, endpoint, field-allowlist.
// CWE-22, CWE-918, CWE-212 mitigation.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { validateTextfilePath, FORBIDDEN_PREFIXES } from '../../lib/exporters/path-validator.mjs';
import { validateOtlpEndpoint } from '../../lib/exporters/endpoint-validator.mjs';
import {
applyFieldAllowlist,
POST_BASH_STATS_ALLOWED,
EVENT_EMIT_PAYLOAD_ALLOWED,
TOKEN_USAGE_ALLOWED,
TREKRESEARCH_ALLOWED,
} from '../../lib/exporters/field-allowlist.mjs';
// ---- path-validator: CWE-22 mitigation -------------------------------------
test('path-validator: rejects ../etc/passwd traversal (CWE-22)', () => {
const r = validateTextfilePath('../../etc/passwd');
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'PATH_TRAVERSAL'));
});
test('path-validator: rejects /etc/voyage.prom (forbidden system prefix)', () => {
const r = validateTextfilePath('/etc/voyage.prom');
assert.equal(r.valid, false);
// Either forbidden-system or parent-missing (both are deny-paths)
const denied = r.errors.find(e =>
e.code === 'PATH_FORBIDDEN_SYSTEM' || e.code === 'PATH_PARENT_MISSING');
assert.ok(denied, `expected deny, got: ${JSON.stringify(r.errors)}`);
});
test('path-validator: rejects ~ home shorthand', () => {
const r = validateTextfilePath('~/voyage.prom');
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'PATH_HOME_SHORTHAND'));
});
test('path-validator: accepts path under allowedRoots', () => {
const tmp = mkdtempSync(join(tmpdir(), 'voyage-path-allow-'));
try {
const target = join(tmp, 'voyage.prom');
const r = validateTextfilePath(target, { allowedRoots: [tmp] });
assert.equal(r.valid, true, JSON.stringify(r.errors));
assert.match(r.parsed.path, /voyage\.prom$/);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
});
test('path-validator: rejects path outside allowedRoots', () => {
const tmp = mkdtempSync(join(tmpdir(), 'voyage-path-deny-'));
const otherTmp = mkdtempSync(join(tmpdir(), 'voyage-path-other-'));
try {
const target = join(otherTmp, 'voyage.prom');
const r = validateTextfilePath(target, { allowedRoots: [tmp] });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'PATH_OUT_OF_ALLOWLIST'));
} finally {
rmSync(tmp, { recursive: true, force: true });
rmSync(otherTmp, { recursive: true, force: true });
}
});
test('path-validator: FORBIDDEN_PREFIXES exports drift-pin', () => {
// Ensure all the high-risk system paths are present
for (const prefix of ['/etc/', '/proc/', '/sys/', '/var/', '/usr/']) {
assert.ok(FORBIDDEN_PREFIXES.includes(prefix),
`FORBIDDEN_PREFIXES missing critical path: ${prefix}`);
}
});
// ---- endpoint-validator: CWE-918 mitigation -------------------------------
test('endpoint-validator: rejects http://169.254.169.254/ — PERMANENTLY blocked (CWE-918 cloud metadata)', () => {
const r = validateOtlpEndpoint('http://169.254.169.254/v1/metrics', { env: {} });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_HARD_BLOCKED'));
});
test('endpoint-validator: 169.254.169.254 stays blocked EVEN WITH VOYAGE_OTEL_ALLOW_PRIVATE=1', () => {
// Cloud metadata service is qualitatively different from RFC-1918 home-lab
// access — operator-trust is NOT extended here. AWS/GCP/Azure metadata
// exposes IAM credentials and can compromise the entire cloud account.
const r = validateOtlpEndpoint('http://169.254.169.254/v1/metrics',
{ env: { VOYAGE_OTEL_ALLOW_PRIVATE: '1' } });
assert.equal(r.valid, false, 'cloud metadata MUST stay blocked even with opt-in');
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_HARD_BLOCKED'));
});
test('endpoint-validator: AliCloud metadata 100.100.100.200 PERMANENTLY blocked', () => {
const r = validateOtlpEndpoint('http://100.100.100.200/latest/meta-data',
{ env: { VOYAGE_OTEL_ALLOW_PRIVATE: '1' } });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_HARD_BLOCKED'));
});
test('endpoint-validator: metadata.google.internal hostname PERMANENTLY blocked', () => {
const r = validateOtlpEndpoint('http://metadata.google.internal/computeMetadata/v1',
{ env: { VOYAGE_OTEL_ALLOW_PRIVATE: '1' } });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_HARD_BLOCKED'));
});
test('endpoint-validator: rejects http://example.com/ (requires https)', () => {
const r = validateOtlpEndpoint('http://example.com/v1/metrics', { env: {} });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_HTTPS_REQUIRED'));
});
test('endpoint-validator: rejects http://localhost without VOYAGE_OTEL_ALLOW_PRIVATE', () => {
const r = validateOtlpEndpoint('http://localhost:4318/v1/metrics', { env: {} });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_LOOPBACK_REJECTED'));
});
test('endpoint-validator: accepts http://localhost when VOYAGE_OTEL_ALLOW_PRIVATE=1 (home-lab opt-in)', () => {
const r = validateOtlpEndpoint('http://localhost:4318/v1/metrics',
{ env: { VOYAGE_OTEL_ALLOW_PRIVATE: '1' } });
assert.equal(r.valid, true, JSON.stringify(r.errors));
assert.equal(r.parsed.isPrivate, true);
});
test('endpoint-validator: accepts https://example.com/v1/metrics (public)', () => {
const r = validateOtlpEndpoint('https://otel.example.com/v1/metrics', { env: {} });
assert.equal(r.valid, true, JSON.stringify(r.errors));
assert.equal(r.parsed.isPrivate, false);
});
test('endpoint-validator: rejects RFC-1918 192.168.1.1 without opt-in', () => {
const r = validateOtlpEndpoint('http://192.168.1.1:4318/v1/metrics', { env: {} });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_RFC1918_REJECTED'));
});
// IPv4-mapped IPv6 bypass (S21): `::ffff:a.b.c.d` literals render as
// `::ffff:HHHH:HHHH` and route to the embedded IPv4. The host must be
// canonicalized to its embedded IPv4 BEFORE classification, or loopback /
// RFC-1918 / link-local / cloud-metadata guards are all bypassable over https.
test('endpoint-validator: rejects IPv4-mapped IPv6 loopback [::ffff:127.0.0.1] (S21 bypass)', () => {
const r = validateOtlpEndpoint('https://[::ffff:127.0.0.1]/v1/metrics', { env: {} });
assert.equal(r.valid, false, 'IPv4-mapped loopback must be rejected');
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_LOOPBACK_REJECTED'));
});
test('endpoint-validator: rejects IPv4-mapped IPv6 RFC-1918 [::ffff:192.168.0.5] (S21 bypass)', () => {
const r = validateOtlpEndpoint('https://[::ffff:192.168.0.5]/v1/metrics', { env: {} });
assert.equal(r.valid, false, 'IPv4-mapped RFC-1918 must be rejected');
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_RFC1918_REJECTED'));
});
test('endpoint-validator: IPv4-mapped metadata [::ffff:169.254.169.254] HARD_BLOCKED even with opt-in (S21 bypass)', () => {
const r = validateOtlpEndpoint('https://[::ffff:169.254.169.254]/latest/meta-data',
{ env: { VOYAGE_OTEL_ALLOW_PRIVATE: '1' } });
assert.equal(r.valid, false, 'IPv4-mapped cloud-metadata MUST stay hard-blocked');
assert.ok(r.errors.find(e => e.code === 'ENDPOINT_HARD_BLOCKED'));
});
test('endpoint-validator: accepts IPv4-mapped IPv6 public [::ffff:8.8.8.8] over https (no over-block)', () => {
const r = validateOtlpEndpoint('https://[::ffff:8.8.8.8]/v1/metrics', { env: {} });
assert.equal(r.valid, true, JSON.stringify(r.errors));
assert.equal(r.parsed.isPrivate, false);
});
test('endpoint-validator: rejects empty / non-string', () => {
assert.equal(validateOtlpEndpoint('').valid, false);
assert.equal(validateOtlpEndpoint(null).valid, false);
assert.equal(validateOtlpEndpoint(undefined).valid, false);
});
// ---- field-allowlist: CWE-212 mitigation -----------------------------------
test('field-allowlist: post-bash-stats DROPS command_excerpt + session_id (CWE-212)', () => {
const record = {
ts: '2026-05-09T08:00:00.000Z',
session_id: 'uuid-12345',
command_excerpt: 'git clone https://example.com/secret/repo',
duration_ms: 152,
success: true,
};
const out = applyFieldAllowlist(record, 'post-bash-stats');
assert.equal('command_excerpt' in out, false, 'command_excerpt MUST be stripped');
assert.equal('session_id' in out, false, 'session_id MUST be stripped');
assert.equal(out.duration_ms, 152);
assert.equal(out.success, true);
assert.equal(out._schema_id, 'post-bash-stats');
});
test('field-allowlist: trekplan DROPS task / project_dir / brief_path (PII)', () => {
const record = {
ts: '2026-05-09T08:00:00.000Z',
task: 'private user prose with PII',
slug: 'add-auth',
project_dir: '/home/user/secret/project',
brief_path: '/home/user/secret/brief.md',
codebase_files: 156,
profile: 'premium',
};
const out = applyFieldAllowlist(record, 'trekplan');
assert.equal('task' in out, false);
assert.equal('project_dir' in out, false);
assert.equal('brief_path' in out, false);
assert.equal(out.slug, 'add-auth');
assert.equal(out.codebase_files, 156);
assert.equal(out.profile, 'premium');
});
// M5: 2045432 added intent_approved to the trekbrief allowlist and no test
// guarded it — removing the line left the suite green.
test('field-allowlist: trekbrief KEEPS intent_approved (veikart steg 1) and drops task / project_dir', () => {
const record = {
ts: '2026-09-23T10:00:00.000Z',
task: 'private user prose',
slug: 'dark-mode-toggle',
project_dir: '/home/user/secret/project',
intent_approved: true,
};
const out = applyFieldAllowlist(record, 'trekbrief');
assert.equal(out.intent_approved, true);
assert.equal(out.slug, 'dark-mode-toggle');
assert.equal('task' in out, false);
assert.equal('project_dir' in out, false);
assert.equal(applyFieldAllowlist({ ...record, intent_approved: false }, 'trekbrief').intent_approved, false);
});
test('field-allowlist: trekbrief DROPS the order fields (coordination metadata) and still KEEPS intent_approved', () => {
const out = applyFieldAllowlist({
ts: '2026-10-04T10:00:00.000Z', slug: 's', intent_approved: true,
order_id: 'o-1', order_claimed_at: '2026-10-04T09:00:00Z', order_reason: null,
}, 'trekbrief');
for (const k of ['order_id', 'order_claimed_at', 'order_reason']) assert.equal(k in out, false, `${k} must never be exported`);
assert.equal(out.intent_approved, true);
});
test('field-allowlist: event-emit applies sub-allowlist to payload', () => {
const record = {
ts: '2026-05-09T08:00:00.000Z',
event: 'main-merge-gate',
known_event: true,
payload: {
profile: 'balanced',
profile_source: 'inheritance',
command_excerpt: 'should be stripped from payload',
raw_user_prose: 'should be stripped',
},
};
const out = applyFieldAllowlist(record, 'event-emit');
assert.equal(out.event, 'main-merge-gate');
assert.equal(out.payload.profile, 'balanced');
assert.equal(out.payload.profile_source, 'inheritance');
assert.equal('command_excerpt' in out.payload, false);
assert.equal('raw_user_prose' in out.payload, false);
});
test('field-allowlist: unknown schema-type returns conservative {ts, _schema_id} only', () => {
const out = applyFieldAllowlist(
{ ts: '2026-05-09T08:00:00.000Z', sensitive: 'secret' },
'totally-unknown-schema',
);
assert.equal('sensitive' in out, false);
assert.equal(out.ts, '2026-05-09T08:00:00.000Z');
assert.equal(out._schema_id, 'totally-unknown-schema');
});
test('field-allowlist: Object.freeze on allowlists (drift-pin)', () => {
assert.equal(Object.isFrozen(POST_BASH_STATS_ALLOWED), true,
'POST_BASH_STATS_ALLOWED must be frozen — runtime mutation prevention');
assert.equal(Object.isFrozen(EVENT_EMIT_PAYLOAD_ALLOWED), true);
assert.equal(Object.isFrozen(TOKEN_USAGE_ALLOWED), true,
'TOKEN_USAGE_ALLOWED must be frozen — runtime mutation prevention');
});
// ---- token-usage allowlist (SKAL-2, CWE-212) -------------------------------
test('field-allowlist: token-usage INCLUDES numeric/label fields, EXCLUDES session_id/transcript_path/cwd (two-sided)', () => {
const record = {
ts: '2026-06-26T12:00:00.000Z',
session_id: 'uuid-secret',
transcript_path: '/Users/ktg/.claude/projects/x/sesn.jsonl',
cwd: '/Users/ktg/secret/project',
scope: 'main-context',
model: 'claude-opus-4-8',
tokens_input: 12345,
tokens_output: 6789,
tokens_cache_creation: 400,
tokens_cache_read: 5000,
cost_usd: 0.42,
is_estimate: false,
price_table_version: '2026-06-26',
};
const out = applyFieldAllowlist(record, 'token-usage');
// INCLUDED (numeric + low-cardinality labels)
for (const k of ['tokens_input', 'tokens_output', 'tokens_cache_creation',
'tokens_cache_read', 'cost_usd', 'is_estimate', 'price_table_version', 'scope', 'model']) {
assert.equal(k in out, true, `${k} MUST be allowlisted`);
}
assert.equal(out.tokens_input, 12345);
assert.equal(out._schema_id, 'token-usage');
// EXCLUDED (CWE-212 boundary)
assert.equal('session_id' in out, false, 'session_id MUST be stripped (CWE-212)');
assert.equal('transcript_path' in out, false, 'transcript_path MUST be stripped (CWE-212)');
assert.equal('cwd' in out, false, 'cwd MUST be stripped (CWE-212)');
});
// ---- trekresearch allowlist: the `engine` field ----------------------------
test('field-allowlist: trekresearch INCLUDES engine, EXCLUDES question/project_dir/brief_path (two-sided)', () => {
const record = {
ts: '2026-08-09T12:00:00.000Z',
question: 'which retrieval strategy survives contradiction?',
mode: 'default',
scope: 'both',
engine: 'deep-research',
slug: 'storm-upgrade',
project_dir: '/Users/ktg/secret/project',
brief_path: '/Users/ktg/secret/project/brief.md',
dimensions: 4,
agents_local: 7,
agents_external: 4,
gemini_used: false,
confidence: 0.82,
contradictions: 1,
open_questions: 3,
};
const out = applyFieldAllowlist(record, 'trekresearch');
// INCLUDED — low-cardinality label, emitted (trekresearch.md:533) and
// promised in prose (:570-572); it was silently dropped before this pin.
assert.equal('engine' in out, true, 'engine MUST be allowlisted — it is emitted and documented');
assert.equal(out.engine, 'deep-research');
assert.equal(out._schema_id, 'trekresearch');
// EXCLUDED (CWE-212 boundary)
assert.equal('question' in out, false, 'question MUST be stripped (prose, CWE-212)');
assert.equal('project_dir' in out, false, 'project_dir MUST be stripped (path, CWE-212)');
assert.equal('brief_path' in out, false, 'brief_path MUST be stripped (path, CWE-212)');
});
test('field-allowlist: TREKRESEARCH_ALLOWED is frozen (drift-pin)', () => {
assert.equal(Object.isFrozen(TREKRESEARCH_ALLOWED), true,
'TREKRESEARCH_ALLOWED must be frozen — runtime mutation prevention');
});
test('field-allowlist: null/undefined record handled safely', () => {
assert.deepEqual(applyFieldAllowlist(null, 'trekplan'), {});
assert.deepEqual(applyFieldAllowlist(undefined, 'trekplan'), {});
});

View file

@ -1,134 +0,0 @@
// tests/hooks/otel-export.test.mjs
// SC #14: Stop-hook orchestration — opt-in via VOYAGE_EXPORT_MODE.
// Fail-soft contract: any error → exit 0, [voyage] stderr, no Stop blocking.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { runHookWithEnv } from '../helpers/hook-helper.mjs';
import { fastestWithin } from '../helpers/timing.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const HOOK_PATH = join(__dirname, '..', '..', 'hooks', 'scripts', 'otel-export.mjs');
function setupDataDir() {
const dir = mkdtempSync(join(tmpdir(), 'voyage-otel-data-'));
// Seed minimal stats files
writeFileSync(join(dir, 'trekplan-stats.jsonl'),
JSON.stringify({ ts: '2026-05-09T08:00:00.000Z', slug: 'test', mode: 'default', codebase_files: 100, profile: 'premium' }) + '\n');
return dir;
}
test('SC #14: VOYAGE_EXPORT_MODE=off → silent exit 0, no file written', async () => {
const dataDir = setupDataDir();
try {
const target = join(dataDir, 'voyage.prom');
const r = await runHookWithEnv(HOOK_PATH, '{}', {
VOYAGE_EXPORT_MODE: 'off',
CLAUDE_PLUGIN_DATA: dataDir,
});
assert.equal(r.code, 0);
assert.equal(existsSync(target), false, 'voyage.prom should NOT be written in off-mode');
assert.equal(r.stderr, '', 'no stderr expected');
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
});
test('SC #14: VOYAGE_EXPORT_MODE unset → silent exit 0 (default off behavior)', async () => {
const dataDir = setupDataDir();
try {
const target = join(dataDir, 'voyage.prom');
const r = await runHookWithEnv(HOOK_PATH, '{}', {
CLAUDE_PLUGIN_DATA: dataDir,
VOYAGE_EXPORT_MODE: '', // explicit empty (mimics unset)
});
assert.equal(r.code, 0);
assert.equal(existsSync(target), false);
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
});
test('SC #14: VOYAGE_EXPORT_MODE=textfile + valid CLAUDE_PLUGIN_DATA → writes voyage.prom', async () => {
const dataDir = setupDataDir();
try {
const target = join(dataDir, 'voyage.prom');
const r = await runHookWithEnv(HOOK_PATH, '{}', {
VOYAGE_EXPORT_MODE: 'textfile',
CLAUDE_PLUGIN_DATA: dataDir,
});
assert.equal(r.code, 0);
assert.equal(existsSync(target), true, `voyage.prom should be written; stderr: ${r.stderr}`);
const text = readFileSync(target, 'utf-8');
assert.match(text, /# HELP /);
assert.match(text, /# TYPE /);
assert.match(text, /voyage_trekplan_codebase_files/);
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
});
test('SC #14: VOYAGE_EXPORT_MODE=invalid → stderr [voyage] warning + exit 0 (NOT blocking)', async () => {
const dataDir = setupDataDir();
try {
const r = await runHookWithEnv(HOOK_PATH, '{}', {
VOYAGE_EXPORT_MODE: 'banana',
CLAUDE_PLUGIN_DATA: dataDir,
});
assert.equal(r.code, 0, 'invalid mode MUST NOT block Stop');
assert.match(r.stderr, /\[voyage\]/);
assert.match(r.stderr, /banana/);
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
});
test('SC #14: VOYAGE_EXPORT_MODE=otlp + invalid endpoint → stderr [voyage] warn + exit 0', async () => {
const dataDir = setupDataDir();
try {
const r = await runHookWithEnv(HOOK_PATH, '{}', {
VOYAGE_EXPORT_MODE: 'otlp',
VOYAGE_OTEL_ENDPOINT: 'http://example.com/v1/metrics', // public-http rejected
CLAUDE_PLUGIN_DATA: dataDir,
});
assert.equal(r.code, 0);
assert.match(r.stderr, /\[voyage\]/);
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
});
test('SC #14: tail-latency for textfile mode < 200ms (NFR)', async () => {
const dataDir = setupDataDir();
try {
// The fastest of up to three cold runs against the same 800 ms limit
// (tests/helpers/timing.mjs): load only adds time, so a real slowness
// still fails every run, and one run slowed by a busy machine does not.
const t = await fastestWithin(800, async () => {
const start = performance.now();
const r = await runHookWithEnv(HOOK_PATH, '{}', {
VOYAGE_EXPORT_MODE: 'textfile',
CLAUDE_PLUGIN_DATA: dataDir,
});
assert.equal(r.code, 0);
return performance.now() - start;
});
// 200ms NFR with extra headroom for cold-start node spawn (~100ms typical)
assert.ok(t.ok,
`textfile export tail-latency too slow: fastest ${t.fastest.toFixed(0)}ms of ${t.runs.map((x) => x.toFixed(0)).join('/')}ms (NFR <200ms in-process; <800ms allowed for cold spawn)`);
} finally {
rmSync(dataDir, { recursive: true, force: true });
}
});
test('SC #14: missing CLAUDE_PLUGIN_DATA → silent exit 0', async () => {
const r = await runHookWithEnv(HOOK_PATH, '{}', {
VOYAGE_EXPORT_MODE: 'textfile',
CLAUDE_PLUGIN_DATA: '',
});
assert.equal(r.code, 0);
});

View file

@ -1,59 +0,0 @@
// SC #16 — skip-if-no-docker compose-config validation.
// First test in tests/integration/ — establishes the skip-on-missing-tool
// pattern voyage uses for environment-dependent integration tests.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { execFileSync, spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, '../..');
const COMPOSE_FILE = resolve(REPO_ROOT, 'examples/observability/docker-compose.yml');
const dockerAvailable = (() => {
try {
execFileSync('docker', ['info'], { stdio: 'ignore' });
return true;
} catch {
return false;
}
})();
test(
'compose config parses and contains expected services',
{ skip: !dockerAvailable && 'Docker not installed' },
() => {
const r = spawnSync(
'docker',
['compose', '-f', COMPOSE_FILE, 'config'],
{ encoding: 'utf8' },
);
assert.equal(r.status, 0, `docker compose config exited ${r.status}: ${r.stderr}`);
assert.match(r.stdout, /otel-collector/, 'otel-collector service missing');
assert.match(r.stdout, /prometheus/, 'prometheus service missing');
assert.match(r.stdout, /grafana/, 'grafana service missing');
assert.match(r.stdout, /node-exporter/, 'node-exporter service missing');
},
);
test(
'compose config pins required image versions',
{ skip: !dockerAvailable && 'Docker not installed' },
() => {
const r = spawnSync(
'docker',
['compose', '-f', COMPOSE_FILE, 'config'],
{ encoding: 'utf8' },
);
assert.equal(r.status, 0);
assert.match(r.stdout, /prom\/prometheus:v3\.0\.1/, 'prometheus pin missing');
assert.match(r.stdout, /grafana\/grafana:11\.4\.0/, 'grafana pin missing');
assert.match(
r.stdout,
/otel\/opentelemetry-collector-contrib:0\.115\.0/,
'otel-collector pin missing',
);
},
);

View file

@ -1,84 +0,0 @@
// tests/lib/cache-analyzer.test.mjs
// SKAL-2 Step 3 — token/cost aggregation in cache-analyzer's summarize().
// Also the first test coverage for cache-analyzer (previously zero-coverage):
// includes a regression guard that the pre-existing duration/event-name fields
// still compute correctly on mixed input.
//
// Hermetic: no LLM, network, time, or randomness. Idiom:
// tests/lib/token-usage.test.mjs (pure-module node:test style).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { summarize } from '../../lib/stats/cache-analyzer.mjs';
const jsonl = (objs) => objs.map(o => JSON.stringify(o));
// ---- token/cost aggregation ------------------------------------------------
test('summarize — two token-bearing records → summed totals + cost', () => {
const s = summarize(jsonl([
{ ts: '2026-06-26T12:00:00Z', session_id: 'S1', scope: 'main-context', tokens_input: 1000, tokens_output: 200, tokens_cache_creation: 400, tokens_cache_read: 5000, cost_usd: 0.03675 },
{ ts: '2026-06-26T13:00:00Z', session_id: 'S2', scope: 'main-context', tokens_input: 2000, tokens_output: 300, tokens_cache_creation: 600, tokens_cache_read: 1000, cost_usd: 0.05 },
]));
assert.equal(s.total_tokens_input, 3000);
assert.equal(s.total_tokens_output, 500);
assert.equal(s.total_tokens_cache_creation, 1000);
assert.equal(s.total_tokens_cache_read, 6000);
assert.equal(s.sessions_with_tokens, 2);
assert.ok(Math.abs(s.total_cost_usd - 0.08675) < 1e-9, `cost ${s.total_cost_usd}`);
});
test('summarize — cost_usd:null counts the session but is excluded from total_cost_usd', () => {
const s = summarize(jsonl([
{ session_id: 'S1', tokens_input: 100, cost_usd: 0.01 },
{ session_id: 'S2', tokens_input: 200, cost_usd: null, is_estimate: true },
]));
assert.equal(s.sessions_with_tokens, 2, 'null-cost session must still be counted');
assert.equal(s.total_tokens_input, 300);
assert.ok(Math.abs(s.total_cost_usd - 0.01) < 1e-9, `cost ${s.total_cost_usd}`);
});
test('summarize — no token records → token totals stay zero (additive default)', () => {
const s = summarize(jsonl([
{ event: 'main-merge-gate', duration_ms: 100 },
]));
assert.equal(s.total_tokens_input, 0);
assert.equal(s.total_cost_usd, 0);
assert.equal(s.sessions_with_tokens, 0);
});
// ---- regression guard: pre-existing fields still computed ------------------
test('summarize — existing duration/event-name fields still correct on mixed input', () => {
const s = summarize(jsonl([
{ event: 'main-merge-gate', duration_ms: 100, ts: '2026-06-26T10:00:00Z' },
{ event: 'main-merge-approved', duration_ms: 200, ts: '2026-06-26T11:00:00Z' },
{ session_id: 'S1', tokens_input: 1000, cost_usd: 0.04, ts: '2026-06-26T12:00:00Z' },
]));
// Pre-existing behavior (regression guard)
assert.equal(s.total_events, 3);
assert.equal(s.events_with_duration, 2);
// Percentiles: durations [100,200] → floor(2·0.5)=floor(2·0.9)=idx 1 → both 200.
assert.equal(s.wall_time_ms_p50, 200);
assert.equal(s.wall_time_ms_p90, 200);
assert.equal(s.wall_time_ms_max, 200);
assert.deepEqual(s.unique_event_names, ['main-merge-approved', 'main-merge-gate']);
// Time range spans all ts-bearing lines (incl. the token-only record at 12:00).
assert.equal(s.oldest_event_iso, '2026-06-26T10:00:00.000Z');
assert.equal(s.newest_event_iso, '2026-06-26T12:00:00.000Z');
// New behavior coexists
assert.equal(s.total_tokens_input, 1000);
assert.equal(s.sessions_with_tokens, 1);
assert.ok(Math.abs(s.total_cost_usd - 0.04) < 1e-9);
});
test('summarize — malformed/empty lines tolerated alongside token records', () => {
const s = summarize([
'',
'not json',
JSON.stringify({ session_id: 'S1', tokens_input: 50, cost_usd: 0.001 }),
]);
assert.equal(s.total_events, 1);
assert.equal(s.sessions_with_tokens, 1);
assert.equal(s.total_tokens_input, 50);
});

View file

@ -1,294 +0,0 @@
// tests/lib/claimed-order.test.mjs
// The recorder that tells /trekbrief which coord-queue order it is running.
//
// It mirrors repo-mailbox's claim contract (coord root, mailbox = basename of the
// git toplevel, `_*` reserved, the claimed file's stem is the order id, a
// `<id>.claim` sidecar carries `claimed-at:`), and it must never guess: exactly
// one claimed order is an attribution, zero or several is `null` plus a reason.
// Every test builds its own coord dir and git repos; nothing reads the real
// queue. The module is loaded per test, so a missing module fails each test on
// its own instead of failing the file at link time.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { spawnSync } from 'node:child_process';
const load = () => import('../../lib/stats/claimed-order.mjs');
// --- helpers ---------------------------------------------------------------
function fixture(files) {
const dir = mkdtempSync(join(tmpdir(), 'claimed-order-'));
for (const [rel, body] of Object.entries(files)) {
const p = join(dir, rel);
mkdirSync(dirname(p), { recursive: true });
writeFileSync(p, body);
}
return dir;
}
const cleanup = (...dirs) => { for (const d of dirs) rmSync(d, { recursive: true, force: true }); };
function orderFile({ id, to, quoted = false }) {
const lines = ['---', 'from: .claude'];
if (to !== undefined) lines.push(`to: ${to}`);
lines.push(`order-id: ${quoted ? `"${id}"` : id}`, `subject: order ${id}`, 'date: 2026-10-01T10:00:00Z', '---', '', `# ORDER ${id}`, '');
return lines.join('\n');
}
const claimFile = (at) => `claimed-at: ${at}\nclaimed-by-pid: 1\n`;
// A claimed order in <mailbox>, with its sidecar unless `claim: false`.
function claimed(mailbox, id, { at = '2026-10-04T10:00:00Z', claim = true, to = mailbox } = {}) {
const files = { [`${mailbox}/orders/claimed/${id}.md`]: orderFile({ id, to }) };
if (claim) files[`${mailbox}/orders/claimed/${id}.claim`] = claimFile(at);
return files;
}
function git(cwd, ...args) {
const r = spawnSync('git', ['-c', 'user.email=t@example.invalid', '-c', 'user.name=t', ...args], { cwd, encoding: 'utf8' });
assert.equal(r.status, 0, `git ${args.join(' ')}: ${r.stderr}`);
return r.stdout.trim();
}
// A git repo whose directory is named `name`, with one commit (a worktree needs a HEAD).
function repo(name) {
const parent = mkdtempSync(join(tmpdir(), 'claimed-order-git-'));
const dir = join(parent, name);
mkdirSync(dir);
git(dir, 'init', '-q');
writeFileSync(join(dir, 'README'), 'x\n');
git(dir, 'add', 'README');
git(dir, 'commit', '-q', '-m', 'init');
return { parent, dir };
}
function snapshot(dir) {
const out = [];
const walk = (d) => {
for (const n of readdirSync(d).sort()) {
const p = join(d, n);
const s = statSync(p);
out.push(`${p} ${s.mtimeMs}`);
if (s.isDirectory()) walk(p);
}
};
walk(dir);
return out.join('\n');
}
// --- findClaimedOrder ----------------------------------------------------------
test('findClaimedOrder: exactly one claimed order gives its order id and its claimed-at', async () => {
const { findClaimedOrder } = await load();
const coord = fixture(claimed('mbx', 'o-1', { at: '2026-10-04T17:10:51Z' }));
try {
assert.deepEqual(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }),
{ order_id: 'o-1', order_claimed_at: '2026-10-04T17:10:51Z', order_reason: null });
} finally { cleanup(coord); }
});
test('findClaimedOrder: no claimed/ directory gives null and none-claimed, never a guess', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({ 'mbx/orders/o-1.md': orderFile({ id: 'o-1', to: 'mbx' }) });
try {
assert.deepEqual(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }),
{ order_id: null, order_claimed_at: null, order_reason: 'none-claimed' });
} finally { cleanup(coord); }
});
test('findClaimedOrder: an empty claimed/ gives null and none-claimed', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({ 'mbx/orders/claimed/.keep': '' });
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }).order_reason, 'none-claimed');
} finally { cleanup(coord); }
});
test('findClaimedOrder: two claimed orders give null and several-claimed:2 - the older one is never picked', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({ ...claimed('mbx', 'o-1', { at: '2026-10-01T00:00:00Z' }), ...claimed('mbx', 'o-2') });
try {
assert.deepEqual(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }),
{ order_id: null, order_claimed_at: null, order_reason: 'several-claimed:2' });
} finally { cleanup(coord); }
});
test('findClaimedOrder: an unparseable claimed .md still counts - "several" is never undercounted', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({ ...claimed('mbx', 'o-1'), 'mbx/orders/claimed/o-2.md': 'no frontmatter at all\n' });
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }).order_reason, 'several-claimed:2');
} finally { cleanup(coord); }
});
test('findClaimedOrder: the .claim sidecar and other non-.md files are not orders', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({ ...claimed('mbx', 'o-1'), 'mbx/orders/claimed/notes.txt': 'x\n' });
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }).order_id, 'o-1');
} finally { cleanup(coord); }
});
test('findClaimedOrder: pending orders/ and archive/ are not claimed', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({
'mbx/orders/o-1.md': orderFile({ id: 'o-1', to: 'mbx' }),
'mbx/orders/archive/o-2.md': orderFile({ id: 'o-2', to: 'mbx' }),
});
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }).order_reason, 'none-claimed');
} finally { cleanup(coord); }
});
test("findClaimedOrder: another mailbox's claimed order is never ours", async () => {
const { findClaimedOrder } = await load();
const coord = fixture(claimed('other', 'o-1'));
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }).order_reason, 'none-claimed');
} finally { cleanup(coord); }
});
test('findClaimedOrder: a missing coord dir gives null and coord-missing, without throwing', async () => {
const { findClaimedOrder } = await load();
const r = findClaimedOrder({ coordDir: join(tmpdir(), 'claimed-order-does-not-exist-'), mailboxes: ['mbx'] });
assert.deepEqual(r, { order_id: null, order_claimed_at: null, order_reason: 'coord-missing' });
});
test('findClaimedOrder: a claimed order without its .claim keeps the id, with no-claim-time', async () => {
const { findClaimedOrder } = await load();
const coord = fixture(claimed('mbx', 'o-1', { claim: false }));
try {
assert.deepEqual(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }),
{ order_id: 'o-1', order_claimed_at: null, order_reason: 'no-claim-time' });
} finally { cleanup(coord); }
});
test('findClaimedOrder: a to: that names another repo is to-mismatch; a missing to: is accepted', async () => {
const { findClaimedOrder } = await load();
const wrong = fixture(claimed('mbx', 'o-1', { to: 'elsewhere' }));
const none = fixture(claimed('mbx', 'o-2', { to: undefined }));
try {
assert.deepEqual(findClaimedOrder({ coordDir: wrong, mailboxes: ['mbx'] }),
{ order_id: null, order_claimed_at: null, order_reason: 'to-mismatch' });
assert.equal(findClaimedOrder({ coordDir: none, mailboxes: ['mbx'] }).order_id, 'o-2');
} finally { cleanup(wrong, none); }
});
test('findClaimedOrder: the id is the file stem, also when the frontmatter quotes order-id', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({
'mbx/orders/claimed/o-1.md': orderFile({ id: 'o-1', to: 'mbx', quoted: true }),
'mbx/orders/claimed/o-1.claim': claimFile('2026-10-04T10:00:00Z'),
});
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] }).order_id, 'o-1');
} finally { cleanup(coord); }
});
test('findClaimedOrder: a _-prefixed mailbox is never read (the claim script reserves the namespace)', async () => {
const { findClaimedOrder } = await load();
const coord = fixture(claimed('_retired', 'o-1'));
try {
assert.equal(findClaimedOrder({ coordDir: coord, mailboxes: ['_retired'] }).order_id, null);
} finally { cleanup(coord); }
});
test('findClaimedOrder: it only reads - the coord dir is identical before and after', async () => {
const { findClaimedOrder } = await load();
const coord = fixture({ ...claimed('mbx', 'o-1'), ...claimed('other', 'o-2') });
try {
const before = snapshot(coord);
findClaimedOrder({ coordDir: coord, mailboxes: ['mbx', 'other'] });
findClaimedOrder({ coordDir: coord, mailboxes: ['mbx'] });
assert.equal(snapshot(coord), before);
} finally { cleanup(coord); }
});
test('findClaimedOrder: one claimed order across two mailbox names is found; one in each is several', async () => {
const { findClaimedOrder } = await load();
const one = fixture(claimed('main-repo', 'o-1'));
const two = fixture({ ...claimed('main-repo', 'o-1'), ...claimed('wt-name', 'o-2') });
try {
assert.equal(findClaimedOrder({ coordDir: one, mailboxes: ['wt-name', 'main-repo'] }).order_id, 'o-1');
assert.equal(findClaimedOrder({ coordDir: two, mailboxes: ['wt-name', 'main-repo'] }).order_reason, 'several-claimed:2');
} finally { cleanup(one, two); }
});
// --- resolveCoordDir -------------------------------------------------------------
test('resolveCoordDir: CLAUDE_COORD_DIR wins, else HOME/.claude/coord from the given env', async () => {
const { resolveCoordDir } = await load();
assert.equal(resolveCoordDir({ CLAUDE_COORD_DIR: '/x/coord', HOME: '/h' }), '/x/coord');
assert.equal(resolveCoordDir({ HOME: '/h' }), join('/h', '.claude', 'coord'));
assert.equal(resolveCoordDir({ CLAUDE_COORD_DIR: '', HOME: '/h' }), join('/h', '.claude', 'coord'));
});
// --- resolveMailboxes ------------------------------------------------------------
test('resolveMailboxes: a repo gives its basename; a worktree gives its own and the main repo name; outside git gives []', async () => {
const { resolveMailboxes } = await load();
const main = repo('main-repo');
const outside = mkdtempSync(join(tmpdir(), 'claimed-order-nogit-'));
try {
assert.deepEqual(resolveMailboxes(main.dir, { env: {} }), ['main-repo']);
const wt = join(main.parent, 'wt-name');
git(main.dir, 'worktree', 'add', '-q', '-b', 'wt-branch', wt);
assert.deepEqual([...resolveMailboxes(wt, { env: {} })].sort(), ['main-repo', 'wt-name']);
assert.deepEqual(resolveMailboxes(outside, { env: {} }), []);
} finally { cleanup(main.parent, outside); }
});
test('resolveMailboxes: an inherited GIT_DIR pointing at another repo is ignored', async () => {
const { resolveMailboxes } = await load();
const mine = repo('mine');
const theirs = repo('theirs');
try {
const env = { GIT_DIR: join(theirs.dir, '.git'), GIT_WORK_TREE: theirs.dir };
assert.deepEqual(resolveMailboxes(mine.dir, { env }), ['mine']);
} finally { cleanup(mine.parent, theirs.parent); }
});
// --- attributeOrder --------------------------------------------------------------
test('attributeOrder: outside git the mailbox is unresolved - null, never a guess', async () => {
const { attributeOrder } = await load();
const outside = mkdtempSync(join(tmpdir(), 'claimed-order-nogit-'));
const coord = fixture(claimed('mbx', 'o-1'));
try {
assert.deepEqual(attributeOrder({ cwd: outside, env: { CLAUDE_COORD_DIR: coord, HOME: outside } }),
{ order_id: null, order_claimed_at: null, order_reason: 'mailbox-unresolved' });
} finally { cleanup(outside, coord); }
});
test('attributeOrder: finds the claimed order of the cwd repo; a projectDir outside cwd cannot steer it elsewhere', async () => {
const { attributeOrder } = await load();
const a = repo('repo-a');
const b = repo('repo-b');
const coord = fixture(claimed('repo-b', 'o-1'));
try {
const env = { CLAUDE_COORD_DIR: coord, HOME: a.parent };
// Positive control: the fixture is findable from repo-b itself.
assert.equal(attributeOrder({ cwd: b.dir, env }).order_id, 'o-1');
// From repo-a, a project_dir that walks out of cwd into repo-b is ignored.
assert.equal(attributeOrder({ cwd: a.dir, env, projectDir: b.dir }).order_reason, 'none-claimed');
assert.equal(attributeOrder({ cwd: a.dir, env, projectDir: '../../' + b.parent.split('/').pop() + '/repo-b' }).order_reason, 'none-claimed');
} finally { cleanup(a.parent, b.parent, coord); }
});
test('attributeOrder: a projectDir inside cwd moves the lookup to the repo that holds it', async () => {
const { attributeOrder } = await load();
const m = repo('mbx');
const coord = fixture(claimed('mbx', 'o-1'));
try {
mkdirSync(join(m.dir, '.claude', 'projects', 'x'), { recursive: true });
const env = { CLAUDE_COORD_DIR: coord, HOME: m.parent };
// Control: cwd (the repo's parent) is outside git, so without projectDir there is no mailbox.
assert.equal(attributeOrder({ cwd: m.parent, env }).order_reason, 'mailbox-unresolved');
assert.deepEqual(attributeOrder({ cwd: m.parent, env, projectDir: 'mbx/.claude/projects/x' }),
{ order_id: 'o-1', order_claimed_at: '2026-10-04T10:00:00Z', order_reason: null });
} finally { cleanup(m.parent, coord); }
});

View file

@ -23,7 +23,6 @@ import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseDocument } from '../../lib/util/frontmatter.mjs';
import { resolveProfile, loadProfile } from '../../lib/profiles/resolver.mjs';
import { TREKRESEARCH_ALLOWED } from '../../lib/exporters/field-allowlist.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
@ -338,9 +337,7 @@ test('operations.md custom-profile prose matches findProfilePath resolution orde
// → {"ok":true,"gates":true|false}. It has no state machine (R9).
// The old prose (operations.md:15 / command-modes.md gates rows) (1) fabricated a
// state machine `idle → approved → executing → merge-pending → main-merged`, and
// later named the removed one (gates_on, auto_running, paused_for_gate); (2) claimed event-emit.mjs "records
// each transition" — it emits 3 named lifecycle events and is decoupled from the
// (pure, no-I/O) autonomy-gate; (3) advertised `--gates {open|closed|adaptive}` —
// later named the removed one (gates_on, auto_running, paused_for_gate); (3) advertised `--gates {open|closed|adaptive}` —
// there is no strictness policy and no `adaptive` value: the flag is boolean, and
// the CLI shim + all 4 command docs take `--gates [true|false|closed|open]`
// (bare, true, closed = on; false, open = off; fix round 4.5a, #1).
@ -362,12 +359,6 @@ test('operations.md + command-modes.md describe the real autonomy-gate, not a fa
'docs/operations.md must describe lib/util/autonomy-gate.mjs as the --gates flag parser',
);
// F2 — event-emit does NOT record each autonomy-gate transition
assert.ok(
!ops.includes('records each transition'),
'docs/operations.md: event-emit records 3 named lifecycle events, not "each transition" of the (decoupled) autonomy-gate; fix the prose',
);
// F3 — --gates is boolean; there is no open/closed/adaptive strictness enum
const GATES_ENUM_FORMS = ['{open|closed|adaptive}', '{open\\|closed\\|adaptive}'];
for (const [label, txt] of [['operations.md', ops], ['command-modes.md', modes]]) {
@ -917,31 +908,6 @@ test('S15: profile tables encode each built-in yaml phase_models exactly', () =>
}
});
// STRUCTURAL pin: the exporter allowlist and the authoring fixture must agree.
// `engine` was emitted, documented in prose, and still dropped at the export
// boundary because nothing tied the two together. Derives one side from the
// frozen Set, so it survives rewording of the fixture row.
test('S74: every TREKRESEARCH_ALLOWED name is declared in the jsonl-schemas fixture row', () => {
const row = read('tests/fixtures/jsonl-schemas.md')
.split('\n')
.find((l) => l.startsWith('| trekresearch-stats '));
assert.ok(row, 'jsonl-schemas.md is missing the `trekresearch-stats` row');
// Columns: '' | schema_id | fields | writer_path | line_ref | v4.1 additive | PII | ''
const cells = row.split('|').map((c) => c.trim());
// Both columns are required: profile/profile_source/parallel_agents live in
// the `v4.1 additive` column only, so checking `fields` alone fails at once.
const declared = new Set(
[cells[2], cells[5]].flatMap((c) => c.split(',').map((f) => f.trim())).filter(Boolean),
);
for (const name of TREKRESEARCH_ALLOWED) {
assert.ok(
declared.has(name),
`\`${name}\` is allowlisted in field-allowlist.mjs but absent from the fixture row's `
+ '`fields` + `v4.1 additive` columns — fix the SOURCE, not this pin',
);
}
});
// --- S34 (V30) — economy is self-declared experimental until the cross-tier
// Jaccard floor (0.55) is empirically calibrated (Step-17 calibration deferred
// to v4.2). The status must be visible in BOTH the profile data
@ -1399,26 +1365,6 @@ test('S32: trekbrief Phase 3 delegates Q&A turn-taking to AskUserQuestion (V01
);
});
// ── S33 — documentation consolidation: record the considered-and-kept decisions ──
//
// D2 (V32 rationale) — observability.md documents why direct export rather
// than a native collector (preserves the S21 in-process guards).
// Fix the SOURCE a pin guards, not the test.
test('S33: docs/observability.md documents the direct-export-vs-collector rationale (D2)', () => {
const t = read('docs/observability.md');
assert.ok(
/direct[- ]export/i.test(t) && /collector/i.test(t),
'observability.md must document why Voyage exports directly rather than via a native collector (D2)',
);
// The rationale must anchor on the in-process security boundary it preserves
// (the S21 hardening), not read as an unjustified re-host.
assert.ok(
/allowlist/i.test(t) && /(SSRF|CWE-918|CWE-212)/.test(t),
'the direct-export rationale must cite the in-process guards it preserves (field allowlist + SSRF/path)',
);
});
// ── S38 — fan-out hedge harmonization + relief-assertion forward-guard (SC d) ──
//
// The brief's SC(d) premise — an unhedged "fan-out relieves main context" string
@ -1560,10 +1506,6 @@ test('v5.9: no commands/*.md frontmatter carries a model: key (session inheritan
// version did or what was measured then, and rewriting them is the same defect
// class as bumping a version string inside a measurement doc.
//
// `gemini_used` is deliberately NOT banned. It is a vestigial trekresearch stats
// field, pinned to false by the drop and kept so the observability export schema
// does not break for existing consumers (see TREKRESEARCH_ALLOWED).
//
// BLIND SPOT, stated rather than hidden: THIS file cannot be in the live-surface
// list, because the S95 block below is necessarily full of the word. So the two
// v5.1.1 assertion messages above (which describe the high-effort rules) are not
@ -1579,7 +1521,6 @@ const GEMINI_LIVE_SURFACES = [
'docs/command-modes.md',
'templates/research-brief-template.md',
'templates/trekbrief-template.md',
'tests/fixtures/jsonl-schemas.md',
...listMd('commands').map((f) => `commands/${f}`),
...listMd('agents').map((f) => `agents/${f}`),
];
@ -1601,10 +1542,7 @@ test('S95: agents/gemini-bridge.md is removed from the agent inventory', () => {
test('S95: no live pipeline surface still names the gemini-bridge agent', () => {
const offenders = [];
for (const rel of GEMINI_LIVE_SURFACES) {
// Everything gemini EXCEPT the vestigial gemini_used stats field.
const hits = (read(rel).match(/gemini[\w-]*/gi) || []).filter(
(m) => m.toLowerCase() !== 'gemini_used',
);
const hits = read(rel).match(/gemini[\w-]*/gi) || [];
if (hits.length) offenders.push(`${rel} (${[...new Set(hits)].join(', ')})`);
}
assert.deepEqual(
@ -1635,13 +1573,6 @@ test('S95: trekplan high-effort Pass 2 degrades explicitly instead of failing',
);
});
test('S95: gemini_used survives as a vestigial stats field (export schema unbroken)', () => {
assert.ok(
TREKRESEARCH_ALLOWED.has('gemini_used'),
'gemini_used stays in the trekresearch export allowlist — the agent is dropped, the field is pinned false, and removing it would break existing consumers',
);
});
// End-state defect D-07: since v5.9.0 no command pins `model:`, so the orchestrator follows
// the session model, and sub-agent models are resolved at the spawn sites (brief signal >
// profile > agent frontmatter). No user-facing doc may claim otherwise. Fix the SOURCE.
@ -1862,94 +1793,6 @@ test('model aliases: profiles.md states the current targets, defaults and sessio
}
});
// --- the order a /trekbrief run records (yardstick use, source C) ---------------
// Five surfaces for order_id / order_claimed_at / order_reason: (1) record format
// = the writer, lib/stats/stats-append.mjs (pinned behaviourally in
// tests/lib/stats-landing.test.mjs); (2) measured-field prose = /trekbrief Phase 7;
// (3) field allowlist = denied, said in the allowlist header; (4) jsonl schemas =
// the trekbrief-stats fixture row + EXPORT_DENYLIST; (5) docs = docs/architecture.md.
const ORDER_FIELDS = ['order_id', 'order_claimed_at', 'order_reason'];
function mdSection(text, heading) {
const start = text.indexOf(`\n## ${heading}`);
assert.ok(start >= 0, `missing "## ${heading}"`);
const next = text.indexOf('\n## ', start + 1);
return next < 0 ? text.slice(start) : text.slice(start, next);
}
function trekbriefRow() {
const row = read('tests/fixtures/jsonl-schemas.md').split('\n').find((l) => l.startsWith('| trekbrief-stats '));
assert.ok(row, 'jsonl-schemas.md is missing the `trekbrief-stats` row');
return row.split('|').map((c) => c.trim());
}
test('order fields: /trekbrief Phase 7 names them and says the writer adds them', () => {
const sec = mdSection(read('commands/trekbrief.md'), 'Phase 7');
for (const f of ORDER_FIELDS) assert.ok(sec.includes(`\`${f}\``), `Phase 7 must name \`${f}\``);
assert.match(sec, /writer adds/i, 'the model never types the order id - the prose must say who adds it');
});
test('order fields: the trekbrief-stats fixture row lists them in `fields`, and EXPORT_DENYLIST names them', () => {
const fields = new Set(trekbriefRow()[2].split(',').map((f) => f.trim()));
for (const f of ORDER_FIELDS) assert.ok(fields.has(f), `\`${f}\` missing from the trekbrief-stats \`fields\` column`);
const text = read('tests/fixtures/jsonl-schemas.md');
const deny = text.slice(text.indexOf('**EXPORT_DENYLIST**'));
const block = deny.slice(deny.indexOf('```') + 3, deny.indexOf('```', deny.indexOf('```') + 3));
for (const f of ORDER_FIELDS) assert.ok(block.split(',').map((x) => x.trim()).includes(f), `\`${f}\` missing from EXPORT_DENYLIST`);
});
test('order fields: the trekbrief-stats row\'s line_ref still points at the Phase 7 stats-append line', () => {
const ref = trekbriefRow()[4];
const m = ref.match(/^trekbrief\.md:(\d+)-(\d+)$/);
assert.ok(m, `line_ref "${ref}" is not trekbrief.md:A-B`);
const lines = read('commands/trekbrief.md').split('\n');
const at = lines.findIndex((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs trekbrief\b/.test(l)) + 1;
assert.ok(at > 0, 'the Phase 7 stats-append line is gone');
assert.ok(at >= Number(m[1]) && at <= Number(m[2]), `line_ref ${ref} does not contain the stats-append line (line ${at})`);
});
/** The jsonl-schemas fixture row whose schema_id cell is `id`, split into cells. */
function schemaRow(id) {
const row = read('tests/fixtures/jsonl-schemas.md').split('\n').find((l) => l.startsWith(`| ${id} |`));
assert.ok(row, `jsonl-schemas.md is missing the \`${id}\` row`);
return row.split('|').map((c) => c.trim());
}
// The Phase 9 line_ref was measured, then more prose landed above Phase 9 and
// nothing re-measured it. The same drift hit the other command stats rows.
// Each row is pinned to the span it names: its phase heading to the closing
// fence of its stats-append stdin block. The trekreview anchor is the full
// "## Phase 8 — Validate output" so the Phase 8.5 / 8.7 headings never match.
// The trekresearch anchor is "### Stats tracking", the one such heading in
// commands/trekresearch.md.
const STATS_ROWS = [
{ id: 'trekbrief-stats', cmd: 'trekbrief', heading: /^## Phase 7\b(?!\.)/ },
{ id: 'trekresearch-stats', cmd: 'trekresearch', heading: /^### Stats tracking$/ },
{ id: 'trekplan-stats', cmd: 'trekplan', heading: /^## Phase 12\b(?!\.)/ },
{ id: 'trekreview-stats', cmd: 'trekreview', heading: /^## Phase 8 — Validate output/ },
{ id: 'trekexecute-stats (Phase 9 record)', cmd: 'trekexecute', heading: /^## Phase 9\b(?!\.)/ },
];
for (const { id, cmd, heading: headingRe } of STATS_ROWS) {
test(`the ${id} row's line_ref spans its phase heading to the end of its stats-append stdin block`, () => {
const ref = schemaRow(id)[4];
const m = ref.match(new RegExp(`^${cmd}\\.md:(\\d+)-(\\d+)$`));
assert.ok(m, `line_ref "${ref}" is not ${cmd}.md:A-B`);
const lines = read(`commands/${cmd}.md`).split('\n');
const headings = lines.map((l, i) => (headingRe.test(l) ? i : -1)).filter((i) => i >= 0);
assert.equal(headings.length, 1, `exactly one heading in commands/${cmd}.md matches ${headingRe}`);
const heading = headings[0] + 1;
const appendRe = new RegExp(`^\\s*node \\$\\{CLAUDE_PLUGIN_ROOT\\}/lib/stats/stats-append\\.mjs ${cmd}\\b`);
const append = lines.findIndex((l, i) => i >= heading && appendRe.test(l)) + 1;
const nextPhase = lines.findIndex((l, i) => i >= heading && /^## Phase /.test(l)) + 1;
assert.ok(append > heading, `a stats-append line follows the heading in commands/${cmd}.md`);
assert.ok(nextPhase === 0 || append < nextPhase, `the stats-append line sits inside the phase (line ${append}, next phase at ${nextPhase})`);
const fence = lines.findIndex((l, i) => i >= append && l.trim() === '```') + 1;
assert.ok(fence > append, 'the stats-append block has a closing fence');
assert.equal(ref, `${cmd}.md:${heading}-${fence}`);
});
}
// A `## Brief Quality` section belongs to the exhausted and force-stopped
// branches only; the passed-gate branch must not tell the model to write one.
test('trekbrief: the passed-gate branch names no `## Brief Quality`, the exhausted branch still does', () => {
@ -1964,62 +1807,6 @@ test('trekbrief: the passed-gate branch names no `## Brief Quality`, the exhaust
assert.ok(text.slice(exStart, exEnd).includes('## Brief Quality'), 'the exhausted branch must still name `## Brief Quality`');
});
test('the autonomy-events row\'s line_ref spans emit() in event-emit.mjs', () => {
const ref = schemaRow('trekexecute-stats (autonomy events)')[4];
const m = ref.match(/^event-emit\.mjs:(\d+)-(\d+)$/);
assert.ok(m, `line_ref "${ref}" is not event-emit.mjs:A-B`);
const lines = read('lib/stats/event-emit.mjs').split('\n');
const start = lines.findIndex((l) => l.startsWith('export function emit(')) + 1;
const end = lines.findIndex((l, i) => i >= start && l === '}') + 1;
assert.ok(start > 0 && end > start, 'emit() and its closing brace');
assert.equal(ref, `event-emit.mjs:${start}-${end}`);
});
test('order fields: docs/architecture.md Stats names them and the order-id join', () => {
const text = read('docs/architecture.md');
const stats = text.slice(text.indexOf('\nStats:'), text.indexOf('\n## Terminology'));
for (const f of ORDER_FIELDS) assert.ok(stats.includes(`\`${f}\``), `docs/architecture.md Stats must name \`${f}\``);
assert.match(stats, /yardstick/);
});
test('order fields: the field-allowlist header says they are denied, and TREKBRIEF_ALLOWED leaves them out', () => {
const text = read('lib/exporters/field-allowlist.mjs');
const header = text.slice(0, text.indexOf('const TREKBRIEF_ALLOWED'));
for (const f of ORDER_FIELDS) assert.ok(header.includes(f), `the allowlist header must name ${f} among the dropped fields`);
const body = text.slice(text.indexOf('const TREKBRIEF_ALLOWED'), text.indexOf(']));', text.indexOf('const TREKBRIEF_ALLOWED')));
for (const f of ORDER_FIELDS) assert.ok(!body.includes(`'${f}'`), `${f} must not be allowlisted for export`);
});
test('yardstick header: use joins on the order id, says where a no comes from, and names the false-positive class', () => {
const text = read('scripts/yardstick.mjs');
const header = text.slice(0, text.indexOf('\nimport '));
assert.match(header, /order id/);
assert.match(header, /arbeidsform: ordre/);
assert.match(header, /false-positive/);
assert.match(header, /USE_COUNTING_SINCE/);
assert.doesNotMatch(header, /slug equals the order's/, 'the slug join is gone');
});
test('/trekbrief --approve writes the trekbrief record: Phase 1 and Phase 4h run Phase 7 with mode "approve"', () => {
const text = read('commands/trekbrief.md');
const item4 = text.slice(text.indexOf('4. `--approve <project-dir | brief-file>`'), text.indexOf('In every other mode'));
assert.match(item4, /Phase 7/, 'approve mode must reach Phase 7 (PM 04.10: the approval leaves the trekbrief record)');
const tail = text.slice(text.indexOf('In `--approve` mode'), text.indexOf('## Phase 5'));
assert.match(tail, /Phase 7/);
const phase7 = mdSection(text, 'Phase 7');
assert.match(phase7, /"mode": "\{default \| quick \| approve\}"/);
assert.match(phase7, /"mode": "approve"/, 'Phase 7 must fix the approve-mode values');
assert.match(text, /Mode: \{default \| quick \| approve\}/, 'the Phase 1 report lists approve');
});
test('/trekbrief --approve: CLAUDE.md, README.md and docs/command-modes.md say the approval writes the trekbrief record', () => {
for (const rel of ['CLAUDE.md', 'README.md', 'docs/command-modes.md']) {
const line = read(rel).split('\n').find((l) => l.includes('--approve <'));
assert.ok(line, `${rel} has no --approve row`);
assert.match(line, /trekbrief (stats )?record/, `${rel}: the --approve row must say it writes the trekbrief record`);
}
});
// --- effort floor: no live doc claims retrieval agents run at medium ------------
// Operator rule (2026-10-05): no agent is pinned below `high`. The model DEFAULT
// sentence ("Opus 5.5 ... default reasoning effort `medium`") stays true and is not a pin.

View file

@ -222,8 +222,8 @@ test('P4: the run stops only on the closed list of four, and the final report sh
}
assert.match(list, /closed/i);
// Phase 8's report template is a fence that holds its own "## " heading, so
// the phase is read up to Phase 9, not up to the next heading.
const report = text.slice(text.indexOf('## Phase 8 — Final report'), text.indexOf('## Phase 9'));
// the phase is read up to Hard rules, not up to the next heading.
const report = text.slice(text.indexOf('## Phase 8 — Final report'), text.indexOf('## Hard rules'));
assert.match(report, /### Stops/);
assert.match(report, /### Decisions taken/);
});

View file

@ -1,49 +0,0 @@
// tests/lib/gold-eval.test.mjs
// SKAL-1·4b — the offline gold-scored output eval (the scoring RUN).
//
// This is the third test-census category (see lib/util/test-census.mjs):
// neither a behavior unit test nor a doc-consistency pin, but a SCORING RUN —
// it feeds a committed agent-run fixture through the deterministic coordinator
// contract (4a) and scores the result against the golden corpus at
// (file, rule_key) granularity. Offline: committed reviewer payloads, no live
// agent spawn, no LLM, no network. The all-agree foundation; the
// LLM-in-the-loop eval is the separate 4c tier.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { runContract } from '../../lib/review/coordinator-contract.mjs';
import { scoreFindings, scoreVerdict } from '../../lib/review/gold-scorer.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const gold = JSON.parse(readFileSync(join(ROOT, 'tests/fixtures/bakeoff-rich/gold.json'), 'utf-8'));
const runPerfect = JSON.parse(
readFileSync(join(ROOT, 'tests/fixtures/bakeoff-rich/runs/run-perfect.json'), 'utf-8'),
);
test('gold-eval — run-perfect reproduces gold at (file,rule_key): precision/recall/f1 = 1.0', () => {
const result = runContract(runPerfect);
const score = scoreFindings(result.findings, gold.findings);
assert.equal(score.precision, 1, `precision ${score.precision}; spurious=${JSON.stringify(score.spurious)}`);
assert.equal(score.recall, 1, `recall ${score.recall}; missed=${JSON.stringify(score.missed)}`);
assert.equal(score.f1, 1);
assert.equal(score.tp, gold.findings.length);
});
test('gold-eval — run-perfect coordinator verdict matches gold expected_verdict (BLOCK)', () => {
const result = runContract(runPerfect);
assert.equal(result.verdict, gold.expected_verdict);
assert.ok(scoreVerdict(result.verdict, gold.expected_verdict));
});
test('gold-eval — run-perfect drops nothing (no suppressed, no schema-skipped payloads)', () => {
// A clean run is the regression guard: any future contract change that
// silently suppresses or skips one of the 5 seeded findings breaks here.
const result = runContract(runPerfect);
assert.equal(result.skipped.length, 0, `skipped payloads: ${JSON.stringify(result.skipped)}`);
assert.equal(result.suppressed.length, 0, `suppressed findings: ${result.suppressed.length}`);
assert.equal(result.findings.length, gold.findings.length);
});

View file

@ -1,126 +0,0 @@
// tests/lib/gold-scorer.test.mjs
// SKAL-1·4b — behavior test for the gold scorer.
//
// scoreFindings compares a recorded agent-run's findings against a golden
// corpus record at (file, rule_key) granularity (line + severity deliberately
// ignored). This pins the precision/recall/f1 math AND the discriminating
// paths (false negatives + false positives), not merely the all-match case —
// a scorer that always returned 1.0 would pass an all-match-only test.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { scoreFindings, scoreVerdict } from '../../lib/review/gold-scorer.mjs';
// A minimal gold set: two distinct (file, rule_key) pairs.
const GOLD = [
{ file: 'a.mjs', line: 10, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' },
{ file: 'b.mjs', line: 0, rule_key: 'MISSING_TEST', severity: 'MAJOR' },
];
test('scoreFindings — perfect match scores precision/recall/f1 = 1', () => {
// Same pairs, different line/severity (must be ignored at (file,rule_key) granularity).
const run = [
{ file: 'a.mjs', line: 99, rule_key: 'SECURITY_INJECTION', severity: 'MINOR' },
{ file: 'b.mjs', line: 7, rule_key: 'MISSING_TEST', severity: 'BLOCKER' },
];
const s = scoreFindings(run, GOLD);
assert.equal(s.tp, 2);
assert.equal(s.fp, 0);
assert.equal(s.fn, 0);
assert.equal(s.precision, 1);
assert.equal(s.recall, 1);
assert.equal(s.f1, 1);
});
test('scoreFindings — a missed gold pair is a false negative (recall < 1)', () => {
const run = [{ file: 'a.mjs', line: 10, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' }];
const s = scoreFindings(run, GOLD);
assert.equal(s.tp, 1);
assert.equal(s.fp, 0);
assert.equal(s.fn, 1);
assert.equal(s.precision, 1);
assert.equal(s.recall, 0.5);
assert.deepEqual(s.missed, ['b.mjs MISSING_TEST']);
});
test('scoreFindings — a spurious pair is a false positive (precision < 1)', () => {
const run = [
{ file: 'a.mjs', line: 10, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' },
{ file: 'b.mjs', line: 0, rule_key: 'MISSING_TEST', severity: 'MAJOR' },
{ file: 'c.mjs', line: 3, rule_key: 'PLACEHOLDER_IN_CODE', severity: 'MAJOR' },
];
const s = scoreFindings(run, GOLD);
assert.equal(s.tp, 2);
assert.equal(s.fp, 1);
assert.equal(s.fn, 0);
assert.equal(s.recall, 1);
assert.equal(s.precision, 2 / 3);
assert.deepEqual(s.spurious, ['c.mjs PLACEHOLDER_IN_CODE']);
});
test('scoreFindings — mixed FN + FP', () => {
const run = [
{ file: 'a.mjs', line: 10, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' }, // match
{ file: 'c.mjs', line: 3, rule_key: 'PLACEHOLDER_IN_CODE', severity: 'MAJOR' }, // spurious
];
const s = scoreFindings(run, GOLD);
assert.equal(s.tp, 1);
assert.equal(s.fp, 1);
assert.equal(s.fn, 1);
assert.equal(s.precision, 0.5);
assert.equal(s.recall, 0.5);
assert.equal(s.f1, 0.5);
});
test('scoreFindings — duplicate (file,rule_key) pairs collapse (set semantics)', () => {
const run = [
{ file: 'a.mjs', line: 10, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' },
{ file: 'a.mjs', line: 22, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' }, // same pair
{ file: 'b.mjs', line: 0, rule_key: 'MISSING_TEST', severity: 'MAJOR' },
];
const s = scoreFindings(run, GOLD);
assert.equal(s.tp, 2);
assert.equal(s.fp, 0);
assert.equal(s.fn, 0);
});
test('scoreFindings — empty run: recall 0 (nothing found), precision vacuously 1, f1 0', () => {
const s = scoreFindings([], GOLD);
assert.equal(s.tp, 0);
assert.equal(s.fp, 0);
assert.equal(s.fn, 2);
assert.equal(s.recall, 0);
assert.equal(s.precision, 1);
assert.equal(s.f1, 0);
});
test('scoreFindings — empty gold: precision 0 (all spurious), recall vacuously 1, f1 0', () => {
const run = [{ file: 'a.mjs', line: 10, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER' }];
const s = scoreFindings(run, []);
assert.equal(s.tp, 0);
assert.equal(s.fp, 1);
assert.equal(s.fn, 0);
assert.equal(s.precision, 0);
assert.equal(s.recall, 1);
assert.equal(s.f1, 0);
});
test('scoreFindings — both empty: precision/recall/f1 = 1 (matched nothing perfectly)', () => {
const s = scoreFindings([], []);
assert.equal(s.precision, 1);
assert.equal(s.recall, 1);
assert.equal(s.f1, 1);
});
test('scoreFindings — tolerates null/undefined finding arrays', () => {
const s = scoreFindings(null, undefined);
assert.equal(s.tp, 0);
assert.equal(s.fp, 0);
assert.equal(s.fn, 0);
});
test('scoreVerdict — exact verdict match is true, mismatch is false', () => {
assert.equal(scoreVerdict('BLOCK', 'BLOCK'), true);
assert.equal(scoreVerdict('WARN', 'BLOCK'), false);
assert.equal(scoreVerdict('ALLOW', 'ALLOW'), true);
});

View file

@ -109,12 +109,6 @@ const EXEMPT = new Map([
['docs/HANDOVER-CONTRACTS.md docs/specs/api.md', TARGET],
['templates/trekreview-template.md lib/foo.mjs', TARGET],
['docs/release-runbook.md scripts/release-plugin.mjs', "the catalog repo's script"],
['docs/T1-cc26-delegated-orchestration.md scripts/q3-cache-prefix-experiment.mjs', RB],
['docs/T1-cc26-delegated-orchestration.md docs/cc-upgrade-2.1.181-decision-matrix.md', RB],
['docs/T1-cc26-delegated-orchestration.md docs/subagent-delegation-audit.md', RB],
['docs/T1-cc26-delegated-orchestration.md agents/planning-orchestrator.md', RB],
['docs/T1-cc26-delegated-orchestration.md docs/T1-synthesis-poc-results.md', RB],
['docs/T1-cc26-delegated-orchestration.md agents/synthesis-agent.md', RB],
['docs/deep-research-engine-research.md docs/cc-upgrade-2.1.181-decision-matrix.md', RB],
]);

View file

@ -51,11 +51,9 @@ const PATTERNS = [
const EXEMPT = new Map([
[SELF, ['self', 17, "the gate's own pattern table and known-positive fixture"]],
['CHANGELOG.md', ['c', 15, 'release history; never rewritten']],
['docs/T1-cc26-delegated-orchestration.md', ['c', 4, 'measurement record (T1/CC-26)']],
['docs/deep-research-engine-research.md', ['c', 6, 'research record (deep-research engine)']],
['docs/operations.md', ['c', 1, 'Path A decision record: why cache-first was rejected']],
['docs/spike-pretooluse-subagent-reach.md', ['c', 2, 'spike record (PreToolUse reach)']],
['scripts/synthesis-measure.mjs', ['c', 2, 'measurement script; says why a child-session instrument was not used, launches nothing']],
['tests/fixtures/research-deep-research-adapted.md', ['c', 1, 'recorded research fixture']],
['docs/proevesett.md', ['operator-instrument', 3, "the operator's own eval run through Anthropic's `claude plugin eval`; Voyage starts nothing - kept by operator decision 2026-10-02"]],
['.gitignore', ['operator-instrument', 1, 'ignores the output of that same operator-run eval']],

View file

@ -1,224 +0,0 @@
// tests/lib/profile-stats-fields.test.mjs
// SC #11 contract-test per brief design — kombinasjonen av:
// (a) fixture-records valideres som JSONL-contracts AND
// (b) command prose promises no profile key its stats record does not write
// er den brief-designede gating-mekanismen. Faktisk runtime-emission av
// feltene er LLM-prose-driven og ikke testbart i node:test alene.
// (b) was turned around in fix round 4-5a (#46): it used to require the prose
// to name profile_source and friends, which no stats heredoc writes.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..', '..');
const PROFILE_FIELDS = [
'profile',
'phase_models',
'parallel_agents',
'external_research_enabled',
'profile_source',
'model_used',
'phase_models_resolved',
];
const VALID_PROFILE_SOURCES = new Set(['flag', 'env', 'default', 'inheritance']);
const COMMAND_FILES = [
'trekbrief.md',
'trekresearch.md',
'trekplan.md',
'trekexecute.md',
'trekreview.md',
'trekcontinue.md',
];
// (a) Fixture validates as JSONL contracts
test('SC #11(a): tests/fixtures/stats-with-profile.jsonl parses as JSONL', () => {
const text = readFileSync(join(REPO_ROOT, 'tests', 'fixtures', 'stats-with-profile.jsonl'), 'utf-8');
const lines = text.trim().split('\n').filter(Boolean);
assert.equal(lines.length, 5, `expected 5 simulated stats records, got ${lines.length}`);
for (const line of lines) {
const record = JSON.parse(line); // throws if malformed
assert.equal(typeof record, 'object');
assert.ok(record.ts, 'record missing ts');
}
});
test('SC #11(a): every fixture record contains profile + profile_source', () => {
const text = readFileSync(join(REPO_ROOT, 'tests', 'fixtures', 'stats-with-profile.jsonl'), 'utf-8');
const records = text.trim().split('\n').filter(Boolean).map(l => JSON.parse(l));
for (const r of records) {
assert.ok('profile' in r, `record missing profile: ${JSON.stringify(r)}`);
assert.ok('profile_source' in r, `record missing profile_source: ${JSON.stringify(r)}`);
}
});
test('SC #11(a): profile_source values are in {flag, env, default, inheritance}', () => {
const text = readFileSync(join(REPO_ROOT, 'tests', 'fixtures', 'stats-with-profile.jsonl'), 'utf-8');
const records = text.trim().split('\n').filter(Boolean).map(l => JSON.parse(l));
for (const r of records) {
assert.ok(VALID_PROFILE_SOURCES.has(r.profile_source),
`profile_source "${r.profile_source}" not in valid set`);
}
});
test('SC #11(a): fixture coverage — all 4 profile_source values represented', () => {
const text = readFileSync(join(REPO_ROOT, 'tests', 'fixtures', 'stats-with-profile.jsonl'), 'utf-8');
const records = text.trim().split('\n').filter(Boolean).map(l => JSON.parse(l));
const seen = new Set(records.map(r => r.profile_source));
for (const expected of VALID_PROFILE_SOURCES) {
assert.ok(seen.has(expected),
`fixture missing profile_source value: ${expected}; seen: ${[...seen].join(', ')}`);
}
});
// (b) Command prose promises no profile key its stats record does not write
// (false-confidence kompensasjon per plan-critic Major 4). A paragraph outside
// a code fence that mentions stats may name a PROFILE_FIELDS key in backticks
// only when the command's own `stats-append.mjs <kind> <<'JSON'` heredoc
// writes it.
/** The keys of every `stats-append.mjs <kind> <<'JSON'` … `JSON` heredoc in `text`. */
function statsHeredocs(text) {
const out = [];
for (const m of text.matchAll(/stats-append\.mjs (\S+) <<'JSON'\n([\s\S]*?)\nJSON\s*$/gm)) {
out.push({ kind: m[1], keys: new Set([...m[2].matchAll(/"([A-Za-z_][A-Za-z0-9_]*)"\s*:/g)].map((k) => k[1])) });
}
return out;
}
/** Backticked PROFILE_FIELDS keys in stats paragraphs (outside fences) that `written` lacks; sorted. */
function unwrittenStatsKeys(text, written) {
const paras = [];
let cur = [];
let fenced = false;
for (const l of text.split('\n')) {
if (/^\s{0,3}(`{3,}|~{3,})/.test(l)) { fenced = !fenced; paras.push(cur); cur = []; continue; }
if (fenced) continue;
if (l.trim() === '') { paras.push(cur); cur = []; } else cur.push(l);
}
paras.push(cur);
const bad = new Set();
for (const p of paras.map((x) => x.join('\n')).filter((p) => /stats/i.test(p))) {
for (const m of p.matchAll(/`([^`\n]+)`/g)) {
if (PROFILE_FIELDS.includes(m[1]) && !written.has(m[1])) bad.add(m[1]);
}
}
return [...bad].sort();
}
for (const filename of COMMAND_FILES) {
test(`SC #11(b): commands/${filename} stats prose promises no profile key its stats record does not write`, () => {
const content = readFileSync(join(REPO_ROOT, 'commands', filename), 'utf-8');
const docs = statsHeredocs(content);
assert.equal(docs.length, 1, `${filename}: expected exactly one stats-append.mjs heredoc; found ${docs.length}`);
assert.ok(docs[0].keys.has('ts'), `${filename}: the stats heredoc's keys were not read`);
assert.deepEqual(unwrittenStatsKeys(content, docs[0].keys), [],
`${filename} stats prose promises keys its stats record does not write`);
assert.doesNotMatch(content, /profile_source/,
`${filename} names profile_source, a key no stats record writes`);
});
}
test('SC #11(b): commands/trekplan.md stats prose names neither phase_models nor parallel_agents, which its record does not write', () => {
const content = readFileSync(join(REPO_ROOT, 'commands', 'trekplan.md'), 'utf-8');
const docs = statsHeredocs(content);
assert.equal(docs.length, 1, `trekplan.md: expected exactly one stats-append.mjs heredoc; found ${docs.length}`);
assert.equal(docs[0].keys.has('phase_models'), false, 'the trekplan stats record now writes phase_models: update this test');
assert.equal(docs[0].keys.has('parallel_agents'), false, 'the trekplan stats record now writes parallel_agents: update this test');
const bad = unwrittenStatsKeys(content, docs[0].keys);
assert.ok(!bad.includes('phase_models'), 'trekplan.md stats prose promises phase_models');
assert.ok(!bad.includes('parallel_agents'), 'trekplan.md stats prose promises parallel_agents');
});
test('SC #11(b): the stats-key checker reports a planted paragraph that promises `profile`', () => {
const written = statsHeredocs(readFileSync(join(REPO_ROOT, 'commands', 'trekplan.md'), 'utf-8'))[0].keys;
assert.deepEqual(unwrittenStatsKeys('Stats rows carry `profile`.', written), ['profile']);
});
// --- Step 9: the five STORM measurement fields -----------------------------
// Four numerics + one low-cardinality label. Without `effort` there is no axis
// to group high-vs-standard runs on, and the measurement gate cannot be
// computed at all.
const STORM_FIELDS = [
'effort',
'unique_sources',
'dimensions_baseline',
'conv_turns',
'empty_turns',
];
test('Step 9: a standard-run trekresearch record parses and survives applyFieldAllowlist', async () => {
const { applyFieldAllowlist } = await import('../../lib/exporters/field-allowlist.mjs');
// A standard run: loop never armed, so no discovered dimensions and no turns.
const raw = JSON.parse(JSON.stringify({
_schema_id: 'trekresearch',
ts: '2026-08-09T12:00:00.000Z',
slug: 'add-auth',
mode: 'default',
scope: 'both',
engine: 'swarm',
question: 'free prose that must never reach the exporter',
project_dir: '/Users/somebody/repos/x',
brief_path: '/Users/somebody/repos/x/brief.md',
dimensions: 4,
dimensions_baseline: 4,
conv_turns: 0,
empty_turns: 0,
unique_sources: 11,
effort: 'standard',
agents_local: 5,
agents_external: 4,
gemini_used: false,
confidence: 0.8,
contradictions: 1,
open_questions: 2,
profile: 'premium',
profile_source: 'default',
}));
assert.equal(raw.dimensions_baseline, raw.dimensions,
'a standard run discovers no dimensions — baseline must equal the final count');
const out = applyFieldAllowlist(raw, 'trekresearch');
for (const field of STORM_FIELDS) {
assert.ok(field in out, `${field} must survive the trekresearch allowlist`);
}
assert.equal(out.conv_turns, 0);
assert.equal(out.empty_turns, 0);
assert.equal(out.effort, 'standard');
// Deny-by-omission must still hold for the PII-ish fields.
for (const denied of ['question', 'project_dir', 'brief_path']) {
assert.equal(denied in out, false, `${denied} must NOT reach the exporter`);
}
});
test('Step 9: commands/trekresearch.md prose names all five measurement fields', () => {
const content = readFileSync(join(REPO_ROOT, 'commands', 'trekresearch.md'), 'utf-8');
for (const field of STORM_FIELDS) {
assert.ok(content.includes(field),
`trekresearch.md prose must name ${field} — an emitted-but-undocumented field is unauditable`);
}
});
test('Step 9: tests/fixtures/jsonl-schemas.md trekresearch row lists the five fields', () => {
const doc = readFileSync(join(REPO_ROOT, 'tests', 'fixtures', 'jsonl-schemas.md'), 'utf-8');
const row = doc.split('\n').find(l => l.startsWith('| trekresearch-stats '));
assert.ok(row, 'jsonl-schemas.md is missing the trekresearch-stats row');
for (const field of STORM_FIELDS) {
assert.ok(row.includes(field), `authoring reference must list ${field}`);
}
});
test('SC #11(b): commands/trekresearch.md prose mentions external_research_enabled', () => {
const content = readFileSync(join(REPO_ROOT, 'commands', 'trekresearch.md'), 'utf-8');
assert.match(content, /external_research_enabled/,
'trekresearch.md prose must mention external_research_enabled (additive stats field)');
});

View file

@ -47,11 +47,21 @@ export const GROUPS = {
// Groups whose removal stage has not run yet. Each stage of R-b empties its
// own entry; REMOVED_SURFACES_ALL=1 checks every group (the red proof).
const PENDING = new Set(['R1', 'R2', 'R3']);
const PENDING = new Set(['R2', 'R3']);
// Named exceptions: "path:line" -> reason. Each must still hit (a stale
// exemption fails below).
const EXEMPT = new Map([]);
const R2_FILE = 'R2 removes this file (the STORM gate reads the old trekresearch stats file); drop the entry with it';
const EXEMPT = new Map([
['docs/storm-measurement.md:80', R2_FILE],
['docs/storm-measurement.md:97', R2_FILE],
['docs/storm-measurement.md:100', R2_FILE],
['docs/storm-measurement.md:109', R2_FILE],
['scripts/storm-measure.mjs:4', R2_FILE],
['scripts/storm-measure.mjs:34', R2_FILE],
['scripts/storm-measure.mjs:47', R2_FILE],
['tests/scripts/storm-measure.test.mjs:8', R2_FILE],
]);
const matches = (term, line) => (typeof term === 'string' ? line.includes(term) : term.test(line));

View file

@ -900,7 +900,7 @@ test('review 630 r8 / criteria-runner.mjs:175: a # inside a word is kept by drop
'the doc says the allowlist still refuses the word');
});
test('review 630 r8 / criteria-runner.mjs:890: no CLI compares process.argv[1] itself, and the three that did work through a symlinked path with a space', () => {
test('review 630 r8 / criteria-runner.mjs:890: no CLI compares process.argv[1] itself, and criteria-runner.mjs works through a symlinked path with a space', () => {
const files = spawnSync('git', ['ls-files', 'lib', 'scripts', 'hooks'], { cwd: ROOT, encoding: 'utf8' }).stdout.split('\n').filter((p) => p.endsWith('.mjs'));
assert.deepEqual(files.filter((p) => p !== 'lib/util/is-main.mjs' && /process\.argv\[1\]/.test(read(p))), [],
'only lib/util/is-main.mjs reads process.argv[1]');
@ -908,7 +908,7 @@ test('review 630 r8 / criteria-runner.mjs:890: no CLI compares process.argv[1] i
const link = join(base, 'voyage link');
try {
symlinkSync(ROOT, link);
for (const cli of ['lib/verification/criteria-runner.mjs', 'scripts/comparison-gate.mjs', 'scripts/end-state-gate.mjs']) {
for (const cli of ['lib/verification/criteria-runner.mjs']) {
const r = spawnSync(process.execPath, [join(link, cli), '--no-such-flag'], { encoding: 'utf8', input: '', timeout: 30_000 });
assert.equal(r.status, 2, `${cli} through "${link}": ${r.stdout}${r.stderr}`);
assert.match(r.stderr, /unknown argument --no-such-flag/);

View file

@ -255,7 +255,7 @@ test('[FXL] /trekreview Phase 8.8 runs review-answers.mjs pending and record, as
assert.match(f, /`"committed": false`/, 'a failed commit is said in one line');
assert.match(f, /ask that finding again once/, 'a refusal asks that finding again once');
// Phase 8 says the review-unanswered exit 2 is expected before Phase 8.8.
const p8 = section(cmd, '## Phase 8 — Validate output + stats');
const p8 = section(cmd, '## Phase 8 — Validate output');
assert.ok(sentencesWith(p8, '`review-unanswered:`').some((s) => /Phase 8\.8/.test(s) && /expected/.test(s)),
'Phase 8 says an exit 2 with review-unanswered: before Phase 8.8 is expected');
});

View file

@ -63,11 +63,11 @@ test('run coupling: the writing-criteria quiet case is owned by run, with no sta
// also one with no app to launch (the "no — {reason}" form).
const EXEC = 'commands/trekexecute.md';
/** Phase 8 of /trekexecute, read up to Phase 9 (its report fence holds its own `## ` heading). */
/** Phase 8 of /trekexecute, read up to Hard rules (its report fence holds its own `## ` heading). */
function phase8(text) {
const start = text.indexOf('## Phase 8 — Final report');
const end = text.indexOf('## Phase 9');
assert.ok(start !== -1 && end > start, 'Phase 8 — Final report and Phase 9 must both exist, in that order');
const end = text.indexOf('## Hard rules');
assert.ok(start !== -1 && end > start, 'Phase 8 — Final report and Hard rules must both exist, in that order');
return text.slice(start, end);
}
@ -151,7 +151,7 @@ test('[E14] Phase 8 reads run_seen from progress.json', () => {
assert.equal(rec.length, 1, `Phase 7 must hold one "Record \`run_seen\`" sentence; found ${rec.length}`);
assert.match(rec[0], /\bin the progress file\b|\bto the progress file\b|`progress\.json`/,
`Phase 7 records run_seen without writing it to the progress file: ${rec[0]}`);
const p8 = between(EXEC, '## Phase 8 — Final report', '## Phase 9 — Stats tracking');
const p8 = between(EXEC, '## Phase 8 — Final report', '## Hard rules');
const hits = sentencesWith(p8, '`run_seen`');
const ok = hits.find((s) => /\bread\b|\breads\b/.test(s)
&& /progress file|`progress\.json`/.test(s)

View file

@ -1,348 +0,0 @@
// tests/lib/stats-event-emit.test.mjs
// Cover lib/stats/event-emit.mjs:
// - emit appends a JSONL line with required ISO-8601 ts
// - known_event flag distinguishes recognized vs unknown events
// - missing CLAUDE_PLUGIN_DATA falls back to the directory stats-append and
// scripts/yardstick.mjs use, and a failed write never throws
// - CLI shim parses --payload JSON, writes via emit(), exits 1 on any
// written:false, and runs through a symlink or a path with a space
// - concurrent appends don't corrupt the file (smoke test)
//
// Isolation: every test that reaches the fallback pins HOME to a temp dir and
// empties CLAUDE_PLUGIN_DATA. `env: {}` is never used - resolveStatsDir would fall
// back to os.homedir(), the operator's REAL data directory.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { execFileSync, spawnSync } from 'node:child_process';
import { cpSync, mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, symlinkSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { emit, buildRecord, resolveStatsPath, KNOWN_EVENTS } from '../../lib/stats/event-emit.mjs';
import { resolveStatsDir } from '../../lib/stats/stats-append.mjs';
import { childEnv } from '../helpers/child-env.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const SHIM = join(HERE, '..', '..', 'lib', 'stats', 'event-emit.mjs');
const ISO_8601_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
function tmp(prefix = 'stats-event-emit-') {
return mkdtempSync(join(tmpdir(), prefix));
}
const ROOT = join(HERE, '..', '..');
const DATA_REL = join('.claude', 'plugins', 'data', 'voyage-ktg-plugin-marketplace');
/** process.env with HOME pinned and CLAUDE_PLUGIN_DATA empty: never the real data dir. */
function isolatedEnv(home) {
const env = childEnv({ CLAUDE_PLUGIN_DATA: '', HOME: home });
delete env.CLAUDE_COORD_DIR;
return env;
}
function runShim(cli, args, env) {
return spawnSync(process.execPath, [cli, ...args], { encoding: 'utf-8', env });
}
function records(file) {
return readFileSync(file, 'utf-8').trim().split('\n').map((l) => JSON.parse(l));
}
test('KNOWN_EVENTS contains plan-v2 spec set', () => {
for (const e of ['brief-approved', 'main-merge-gate', 'user_input']) {
assert.ok(KNOWN_EVENTS.has(e), `missing recognized event: ${e}`);
}
});
test('buildRecord emits ISO-8601 ts (REQUIRED per SC4)', () => {
const r = buildRecord('brief-approved', { foo: 1 });
assert.match(r.ts, ISO_8601_RE);
assert.equal(r.event, 'brief-approved');
assert.equal(r.known_event, true);
assert.deepEqual(r.payload, { foo: 1 });
});
test('buildRecord marks unrecognized events known_event: false', () => {
const r = buildRecord('totally-made-up-event');
assert.equal(r.known_event, false);
assert.deepEqual(r.payload, {});
});
test('buildRecord rejects empty event name', () => {
assert.throws(() => buildRecord(''), TypeError);
assert.throws(() => buildRecord(null), TypeError);
});
test('emit appends one JSONL line per call', () => {
const dir = tmp();
try {
const path = join(dir, 'stats.jsonl');
const r1 = emit('brief-approved', { ok: true }, { path });
const r2 = emit('main-merge-gate', { branch: 'main' }, { path });
assert.equal(r1.written, true);
assert.equal(r2.written, true);
const lines = readFileSync(path, 'utf-8').trim().split('\n');
assert.equal(lines.length, 2);
const a = JSON.parse(lines[0]);
const b = JSON.parse(lines[1]);
assert.match(a.ts, ISO_8601_RE);
assert.match(b.ts, ISO_8601_RE);
assert.equal(a.event, 'brief-approved');
assert.equal(b.event, 'main-merge-gate');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('emit creates the stats directory on demand', () => {
const dir = tmp();
try {
const path = join(dir, 'nested', 'stats.jsonl');
const r = emit('user_input', {}, { path });
assert.equal(r.written, true);
assert.ok(existsSync(path));
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// Inverted contract (was: "returns { written: false } (silent skip)"). The silent
// skip is how Phase 8's events never landed: the Bash tool env carries no
// CLAUDE_PLUGIN_DATA.
test('emit with no CLAUDE_PLUGIN_DATA lands in the fallback dir under HOME', () => {
const home = tmp();
try {
const r = emit('brief-approved', {}, { env: { HOME: home } });
assert.equal(r.written, true, JSON.stringify(r));
assert.equal(r.path, join(home, DATA_REL, 'trekexecute-stats.jsonl'));
assert.equal(records(r.path).length, 1);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
// Inverted contract (was: env {} as the "unwritable" case - after the fallback an
// empty env resolves to the real home, so it is no longer unwritable).
test('emit never throws when stats path is unwritable', () => {
const dir = tmp();
try {
const file = join(dir, 'a-file');
writeFileSync(file, 'not a directory');
let r;
assert.doesNotThrow(() => { r = emit('user_input', { foo: 'bar' }, { path: join(file, 'x.jsonl') }); });
assert.equal(r.written, false);
assert.ok(r.reason && r.reason.length > 0, JSON.stringify(r));
mkdirSync(join(dir, '.claude', 'plugins', 'data'), { recursive: true });
writeFileSync(join(dir, DATA_REL), 'not a directory');
assert.doesNotThrow(() => { r = emit('user_input', {}, { env: { HOME: dir } }); });
assert.equal(r.written, false);
assert.ok(r.reason && r.reason.length > 0, JSON.stringify(r));
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// Inverted contract (was: resolveStatsPath({}) === null).
test('resolveStatsPath honors CLAUDE_PLUGIN_DATA, else the stats-append directory', () => {
const r = resolveStatsPath({ CLAUDE_PLUGIN_DATA: '/var/data/plugin' });
assert.equal(r, '/var/data/plugin/trekexecute-stats.jsonl');
assert.equal(resolveStatsPath({ HOME: '/h' }), join(resolveStatsDir({ HOME: '/h' }), 'trekexecute-stats.jsonl'));
assert.equal(resolveStatsPath({ HOME: '/h', CLAUDE_PLUGIN_DATA: '' }), join('/h', DATA_REL, 'trekexecute-stats.jsonl'));
});
// gates_mode_flag_override stays a known event; no command emits it since fix round 4.5a
test('the Phase 8 and override events are known events', () => {
for (const e of ['main-merge-approved', 'main-merge-declined', 'gates_mode_flag_override']) {
assert.equal(buildRecord(e).known_event, true, `${e} is a known event`);
}
});
// Regression guard: one resolver - event-emit carries no second copy of the fallback path.
test('event-emit has no copy of the fallback directory name', () => {
assert.doesNotMatch(readFileSync(SHIM, 'utf-8'), /voyage-ktg-plugin-marketplace/);
});
test('CLI shim writes via emit when CLAUDE_PLUGIN_DATA is set', () => {
const dir = tmp();
try {
execFileSync(process.execPath, [
SHIM, '--event', 'brief-approved', '--payload', '{"foo":42}',
], {
env: childEnv({ CLAUDE_PLUGIN_DATA: dir }),
encoding: 'utf-8',
});
const path = join(dir, 'trekexecute-stats.jsonl');
assert.ok(existsSync(path));
const line = readFileSync(path, 'utf-8').trim();
const parsed = JSON.parse(line);
assert.equal(parsed.event, 'brief-approved');
assert.deepEqual(parsed.payload, { foo: 42 });
assert.match(parsed.ts, ISO_8601_RE);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// Inverted contract (was: "... payload-not-json (exit 0)"): every written:false exits 1.
test('CLI shim with malformed --payload returns reason payload-not-json (exit 1)', () => {
const home = tmp();
try {
const r = runShim(SHIM, ['--event', 'user_input', '--payload', 'not-json{{'], isolatedEnv(home));
assert.equal(r.status, 1, r.stdout + r.stderr);
const parsed = JSON.parse(r.stdout.trim());
assert.equal(parsed.written, false);
assert.equal(parsed.reason, 'payload-not-json');
} finally {
rmSync(home, { recursive: true, force: true });
}
});
// `--payload -` reads the payload from stdin, so the command prose can pass it
// through a quoted heredoc: the shell expands nothing, and a plan path with a
// quote character can no longer close a single-quoted argument.
test('CLI shim with --payload - reads the payload from stdin, quote characters and all', () => {
const home = tmp();
try {
const plan = ".claude/projects/it's a'$(touch PWNED)'b/plan.md";
const r = spawnSync(process.execPath, [SHIM, '--event', 'main-merge-gate', '--payload', '-'],
{ encoding: 'utf-8', env: isolatedEnv(home), input: JSON.stringify({ plan, wave_count: 1 }) });
assert.equal(r.status, 0, r.stdout + r.stderr);
const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl'));
assert.deepEqual(rec.payload, { plan, wave_count: 1 });
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('CLI shim with --payload - and a stdin that is not JSON returns payload-not-json (exit 1)', () => {
const home = tmp();
try {
const r = spawnSync(process.execPath, [SHIM, '--event', 'main-merge-gate', '--payload', '-'],
{ encoding: 'utf-8', env: isolatedEnv(home), input: 'not-json{{' });
assert.equal(r.status, 1, r.stdout + r.stderr);
assert.equal(JSON.parse(r.stdout.trim()).reason, 'payload-not-json');
assert.ok(!existsSync(join(home, DATA_REL, 'trekexecute-stats.jsonl')));
} finally {
rmSync(home, { recursive: true, force: true });
}
});
// The emit() docstring said failures are "swallowed silently" after the contract
// became {written:false, reason}.
test('emit() is not documented as swallowing failures silently', () => {
assert.doesNotMatch(readFileSync(SHIM, 'utf-8'), /swallowed silently/);
});
test('CLI: with CLAUDE_PLUGIN_DATA empty the event lands under HOME', () => {
const home = tmp();
try {
const r = runShim(SHIM, ['--event', 'main-merge-gate', '--payload', '{}'], isolatedEnv(home));
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.equal(JSON.parse(r.stdout.trim()).written, true, r.stdout);
const recs = records(join(home, DATA_REL, 'trekexecute-stats.jsonl'));
assert.equal(recs.length, 1);
assert.equal(recs[0].event, 'main-merge-gate');
assert.equal(recs[0].known_event, true);
assert.deepEqual(recs[0].payload, {});
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('CLI: with CLAUDE_PLUGIN_DATA absent the event lands under HOME', () => {
const home = tmp();
try {
const env = isolatedEnv(home);
delete env.CLAUDE_PLUGIN_DATA;
const r = runShim(SHIM, ['--event', 'main-merge-gate', '--payload', '{}'], env);
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.equal(records(join(home, DATA_REL, 'trekexecute-stats.jsonl')).length, 1);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('CLI: an unwritable target exits 1 with a reason', () => {
const home = tmp();
try {
mkdirSync(join(home, '.claude', 'plugins', 'data'), { recursive: true });
writeFileSync(join(home, DATA_REL), 'not a directory');
const r = runShim(SHIM, ['--event', 'main-merge-gate', '--payload', '{}'], isolatedEnv(home));
assert.equal(r.status, 1, r.stdout + r.stderr);
const out = JSON.parse(r.stdout.trim());
assert.equal(out.written, false);
assert.ok(out.reason && out.reason.length > 0, r.stdout);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('CLI: a usage error exits 1 with a reason', () => {
const home = tmp();
try {
const r = runShim(SHIM, [], isolatedEnv(home));
assert.equal(r.status, 1, r.stdout + r.stderr);
const out = JSON.parse(r.stdout.trim());
assert.equal(out.written, false);
assert.match(out.reason, /usage/);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
test('CLI: event-emit writes when it is run through a symlink', () => {
const home = tmp();
const links = tmp('stats-event-emit-link-');
try {
const link = join(links, 'event-emit.mjs');
symlinkSync(SHIM, link);
const r = runShim(link, ['--event', 'main-merge-gate', '--payload', '{}'], isolatedEnv(home));
assert.ok(r.stdout.trim().length > 0, `no result line - the CLI guard did not fire; stderr: ${r.stderr}`);
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.ok(existsSync(join(home, DATA_REL, 'trekexecute-stats.jsonl')), `no record landed; stdout: "${r.stdout}"`);
} finally {
rmSync(home, { recursive: true, force: true });
rmSync(links, { recursive: true, force: true });
}
});
test('CLI: event-emit writes when its path contains a space', () => {
const home = tmp();
const spaced = tmp('stats event emit space-');
try {
cpSync(join(ROOT, 'lib'), join(spaced, 'with space', 'lib'), { recursive: true });
const cli = join(spaced, 'with space', 'lib', 'stats', 'event-emit.mjs');
const r = runShim(cli, ['--event', 'main-merge-gate', '--payload', '{}'], isolatedEnv(home));
assert.ok(r.stdout.trim().length > 0, `no result line - the CLI guard did not fire; stderr: ${r.stderr}`);
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.ok(existsSync(join(home, DATA_REL, 'trekexecute-stats.jsonl')), `no record landed; stdout: "${r.stdout}"`);
} finally {
rmSync(home, { recursive: true, force: true });
rmSync(spaced, { recursive: true, force: true });
}
});
test('concurrent appends do not corrupt JSONL (smoke)', async () => {
const dir = tmp();
try {
const path = join(dir, 'stats.jsonl');
const N = 25;
await Promise.all(
Array.from({ length: N }, (_, i) =>
Promise.resolve().then(() => emit('user_input', { i }, { path })),
),
);
const lines = readFileSync(path, 'utf-8').trim().split('\n');
assert.equal(lines.length, N);
for (const l of lines) {
const parsed = JSON.parse(l); // throws if any line is corrupt
assert.ok('ts' in parsed);
assert.equal(parsed.event, 'user_input');
}
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

View file

@ -1,453 +0,0 @@
// tests/lib/stats-landing.test.mjs
// Veikart steg 1, økt 2's countable form: after a real /trekbrief → /trekplan run
// there must be (1) a `brief-approved` record with the slug, (2) a trekbrief-stats
// record with `intent_approved: true`, and (3) a trekplan-stats record with the
// same slug — all in the directory scripts/yardstick.mjs reads.
//
// The Bash tool's env carries no CLAUDE_PLUGIN_DATA. Phase 7 of /trekbrief and
// Phase 12 of /trekplan used to say "if ${CLAUDE_PLUGIN_DATA} is not set, skip
// silently" — the same silent skip that left brief-approved at 0 records. These
// tests execute the command lines the prose spells, with CLAUDE_PLUGIN_DATA
// EMPTY, and require the records to land in the yardstick's directory.
//
// The trekbrief record also carries the coord-queue order the run is working on
// (order_id, order_claimed_at, order_reason). The WRITER adds those from the
// queue, so these tests run the line inside a temp git repo against a temp coord
// dir - and every spawn drops an inherited CLAUDE_COORD_DIR, because the real
// queue can hold a claimed order while the suite runs.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { childEnv } from '../helpers/child-env.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8');
const DATA_REL = join('.claude', 'plugins', 'data', 'voyage-ktg-plugin-marketplace');
/** The text of `## <heading>` up to the next `## ` heading. */
function section(rel, heading) {
const text = read(rel);
const start = text.indexOf(`\n## ${heading}`);
assert.ok(start >= 0, `${rel} must have "## ${heading}"`);
const next = text.indexOf('\n## ', start + 1);
return next < 0 ? text.slice(start) : text.slice(start, next);
}
/** The one line in `sec` that runs stats-append.mjs for `kind`. */
function appendLine(sec, kind, rel) {
const lines = sec.split('\n').filter((l) =>
/^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs\b/.test(l) && l.includes(` ${kind}`));
assert.equal(lines.length, 1, `${rel} must carry exactly one \`stats-append.mjs ${kind}\` line; found ${lines.length}`);
return lines[0].trim().replace(/\s*<<-?'?JSON'?\s*$/, '');
}
/** process.env without CLAUDE_COORD_DIR: a test never reads the real coord queue. */
function baseEnv() {
const env = childEnv();
delete env.CLAUDE_COORD_DIR;
return env;
}
/** Run a prose command line with a record on stdin, CLAUDE_PLUGIN_DATA empty, HOME = home. */
function runLine(line, record, home, { cwd, env: extra = {} } = {}) {
const cmd = line.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT);
return spawnSync('bash', ['-c', cmd], {
encoding: 'utf8',
cwd,
input: JSON.stringify(record),
env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home, ...extra },
});
}
/** A git repo whose directory is named `name` (its mailbox), under a fresh temp parent. */
function gitRepo(name) {
const parent = mkdtempSync(join(tmpdir(), 'stats-landing-git-'));
const dir = join(parent, name);
mkdirSync(dir);
assert.equal(spawnSync('git', ['init', '-q'], { cwd: dir }).status, 0);
return { parent, dir };
}
/** A coord dir holding claimed orders: `{ mailbox: [id, ...] }`, each with a .claim sidecar. */
function coordWith(claims) {
const coord = mkdtempSync(join(tmpdir(), 'stats-landing-coord-'));
for (const [mailbox, ids] of Object.entries(claims)) {
const dir = join(coord, mailbox, 'orders', 'claimed');
mkdirSync(dir, { recursive: true });
for (const id of ids) {
writeFileSync(join(dir, `${id}.md`), `---\nfrom: .claude\nto: ${mailbox}\norder-id: ${id}\ndate: 2026-10-04T10:00:00Z\n---\n\n# ORDER ${id}\n`);
writeFileSync(join(dir, `${id}.claim`), 'claimed-at: 2026-10-04T17:10:51Z\nclaimed-by-pid: 1\n');
}
}
return coord;
}
function records(file) {
return readFileSync(file, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
}
const TB = 'commands/trekbrief.md';
const TP = 'commands/trekplan.md';
test('M6: /trekbrief Phase 7 writes through stats-append (code), not "skip silently" (prose)', () => {
const sec = section(TB, 'Phase 7');
appendLine(sec, 'trekbrief', TB);
assert.doesNotMatch(sec, /skip silently/i, 'a silent skip is how the record never landed');
assert.match(sec, /"intent_approved"/);
assert.match(sec, /"slug"/);
});
test('M6: /trekplan Phase 12 writes through stats-append (code), not "skip silently" (prose)', () => {
const sec = section(TP, 'Phase 12');
appendLine(sec, 'trekplan', TP);
assert.doesNotMatch(sec, /skip (tracking )?silently/i);
assert.match(sec, /"slug"/);
});
test('M6: with CLAUDE_PLUGIN_DATA empty the trekbrief record (intent_approved) lands where the yardstick reads', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
try {
const line = appendLine(section(TB, 'Phase 7'), 'trekbrief', TB);
const r = runLine(line, { ts: '2026-09-23T10:00:00Z', slug: 'dark-mode-toggle', intent_approved: true }, home);
assert.equal(r.status, 0, `stats-append failed: ${r.stdout} ${r.stderr}`);
const file = join(home, DATA_REL, 'trekbrief-stats.jsonl');
assert.ok(existsSync(file), `record must land in ${file}`);
const recs = records(file);
assert.equal(recs.length, 1);
assert.equal(recs[0].intent_approved, true);
assert.equal(recs[0].slug, 'dark-mode-toggle');
} finally { rmSync(home, { recursive: true, force: true }); }
});
test('M6: a failed write is reported, never silent (exit 1 + reason on stdout)', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
try {
// the data dir path is occupied by a FILE, so the directory cannot be created
mkdirSync(join(home, '.claude', 'plugins', 'data'), { recursive: true });
writeFileSync(join(home, DATA_REL), 'not a directory');
const line = appendLine(section(TB, 'Phase 7'), 'trekbrief', TB);
const r = runLine(line, { ts: '2026-09-23T10:00:00Z', slug: 'x', intent_approved: false }, home);
assert.equal(r.status, 1);
const out = JSON.parse(r.stdout);
assert.equal(out.written, false);
assert.ok(out.reason && out.reason.length > 0, r.stdout);
} finally { rmSync(home, { recursive: true, force: true }); }
});
test('M6: stats-append refuses a record that is not a JSON object, and an unknown kind', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
try {
const cli = join(ROOT, 'lib', 'stats', 'stats-append.mjs');
const env = { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home };
const bad = spawnSync('node', [cli, 'trekbrief'], { encoding: 'utf8', input: 'not json', env });
assert.equal(bad.status, 1, bad.stdout + bad.stderr);
assert.equal(JSON.parse(bad.stdout).written, false);
const unknown = spawnSync('node', [cli, 'no-such-command'], { encoding: 'utf8', input: '{}', env });
assert.equal(unknown.status, 1);
assert.ok(!existsSync(join(home, DATA_REL, 'no-such-command-stats.jsonl')));
} finally { rmSync(home, { recursive: true, force: true }); }
});
test('M6: økt 2\'s countable form lands end to end with CLAUDE_PLUGIN_DATA empty (stamp → trekbrief → trekplan)', async () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const proj = mkdtempSync(join(tmpdir(), 'stats-landing-proj-'));
try {
writeFileSync(join(proj, 'brief.md'), `---
type: trekbrief
brief_version: "2.1"
task: "Add a dark-mode toggle to a recipe app"
slug: dark-mode-toggle
---
## Intent
Readers cook in dim kitchens and the bright page glares.
## Goal
A toggle in settings, defaulting to the system theme.
`);
// (1) the Phase 4h stamp line, as trekbrief.md spells it
const stampLine = read(TB).split('\n').find((l) =>
/^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/validators\/intent-approval\.mjs\b.*--stamp/.test(l));
assert.ok(stampLine, 'trekbrief.md must carry the --stamp line');
let cmd = stampLine.trim().split('${CLAUDE_PLUGIN_ROOT}').join(ROOT);
cmd = cmd.split('{BRIEF_PATH}').join(join(proj, 'brief.md')).split('{PROJECT_DIR}').join(proj);
const s = spawnSync('bash', ['-c', cmd], { encoding: 'utf8', env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home } });
assert.equal(s.status, 0, s.stdout + s.stderr);
// (2) and (3): the stats lines
assert.equal(runLine(appendLine(section(TB, 'Phase 7'), 'trekbrief', TB),
{ ts: '2026-09-23T10:00:00Z', slug: 'dark-mode-toggle', intent_approved: true }, home).status, 0);
assert.equal(runLine(appendLine(section(TP, 'Phase 12'), 'trekplan', TP),
{ ts: '2026-09-23T10:05:00Z', slug: 'dark-mode-toggle', outcome: 'save' }, home).status, 0);
const { loadStats } = await import(join(ROOT, 'scripts', 'yardstick.mjs'));
const stats = loadStats(join(home, DATA_REL));
const all = stats.files.flatMap((f) => f.records.map((r) => ({ file: f.name, r })));
const approved = all.filter(({ r }) => r.event === 'brief-approved' && r.payload?.slug === 'dark-mode-toggle');
const brief = all.filter(({ file, r }) => file === 'trekbrief-stats.jsonl' && r.intent_approved === true && r.slug === 'dark-mode-toggle');
const plan = all.filter(({ file, r }) => file === 'trekplan-stats.jsonl' && r.slug === 'dark-mode-toggle');
assert.equal(approved.length, 1, 'brief-approved with slug');
assert.equal(brief.length, 1, 'trekbrief record with intent_approved: true');
assert.equal(plan.length, 1, 'trekplan record with the same slug');
} finally {
rmSync(home, { recursive: true, force: true });
rmSync(proj, { recursive: true, force: true });
}
});
test('M6: stats-append and intent-approval resolve the same data dir as the yardstick', async () => {
const { resolveStatsDir } = await import(join(ROOT, 'lib', 'stats', 'stats-append.mjs'));
const { resolveApprovalDataDir } = await import(join(ROOT, 'lib', 'validators', 'intent-approval.mjs'));
const env = { HOME: '/home/someone', CLAUDE_PLUGIN_DATA: '' };
assert.equal(resolveStatsDir(env), join('/home/someone', DATA_REL));
assert.equal(resolveApprovalDataDir(env), resolveStatsDir(env));
assert.equal(resolveStatsDir({ HOME: '/h', CLAUDE_PLUGIN_DATA: '/x/y' }), '/x/y');
});
// --- the order the run is working on (written by the writer, never the model) -------
const ORDER_KEYS = ['order_id', 'order_claimed_at', 'order_reason'];
/** Run Phase 7's line in `repo` against `coord` (or none) and return the one landed record. */
function landTrekbrief(record, { repo, coord, home }) {
const line = appendLine(section(TB, 'Phase 7'), 'trekbrief', TB);
const env = coord ? { CLAUDE_COORD_DIR: coord } : {};
const r = runLine(line, record, home, { cwd: repo, env });
assert.equal(r.status, 0, `stats-append failed: ${r.stdout} ${r.stderr}`);
const recs = records(join(home, DATA_REL, 'trekbrief-stats.jsonl'));
assert.equal(recs.length, 1);
return recs[0];
}
test('order: one claimed order in the repo\'s mailbox lands in the trekbrief record, with its claim time', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('mbx');
const coord = coordWith({ mbx: ['o-1'] });
try {
const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 'dark-mode-toggle', intent_approved: true },
{ repo: repo.dir, coord, home });
assert.equal(rec.order_id, 'o-1');
assert.equal(rec.order_claimed_at, '2026-10-04T17:10:51Z');
assert.equal(rec.order_reason, null);
assert.equal(rec.slug, 'dark-mode-toggle', 'the record keeps its own slug');
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); }
});
test('order: the record\'s own project_dir finds the mailbox when the shell stands outside the repo', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('mbx');
const coord = coordWith({ mbx: ['o-1'] });
try {
mkdirSync(join(repo.dir, '.claude', 'projects', 'x'), { recursive: true });
const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's', project_dir: 'mbx/.claude/projects/x' },
{ repo: repo.parent, coord, home });
assert.equal(rec.order_id, 'o-1');
assert.equal(rec.order_reason, null);
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); }
});
test('order: zero claimed orders - the three keys are PRESENT, order_id null, reason none-claimed', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('mbx');
const coord = coordWith({ other: ['o-9'] });
try {
const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's' }, { repo: repo.dir, coord, home });
for (const k of ORDER_KEYS) assert.ok(k in rec, `${k} must be present (absent means: written before the recorder existed)`);
assert.equal(rec.order_id, null);
assert.equal(rec.order_reason, 'none-claimed');
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); }
});
test('order: two claimed orders - order_id null, reason several-claimed:2, never a guess', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('mbx');
const coord = coordWith({ mbx: ['o-1', 'o-2'] });
try {
const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's' }, { repo: repo.dir, coord, home });
assert.equal(rec.order_id, null);
assert.equal(rec.order_reason, 'several-claimed:2');
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); }
});
test('order: order_* keys the model put in the record are replaced by the writer\'s', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('mbx');
const coord = coordWith({ mbx: ['o-1'] });
try {
const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's', order_id: 'forged', order_extra: 1 },
{ repo: repo.dir, coord, home });
assert.equal(rec.order_id, 'o-1');
assert.ok(!('order_extra' in rec), 'only the writer\'s three order keys survive');
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); }
});
test('order: a trekplan record gains no order keys - only /trekbrief records the order', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('mbx');
const coord = coordWith({ mbx: ['o-1'] });
try {
const r = runLine(appendLine(section(TP, 'Phase 12'), 'trekplan', TP), { ts: '2026-10-04T18:00:00Z', slug: 's' }, home,
{ cwd: repo.dir, env: { CLAUDE_COORD_DIR: coord } });
assert.equal(r.status, 0, r.stdout + r.stderr);
const [rec] = records(join(home, DATA_REL, 'trekplan-stats.jsonl'));
for (const k of ORDER_KEYS) assert.ok(!(k in rec), `${k} must not be on a trekplan record`);
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); }
});
test('order: with CLAUDE_COORD_DIR unset and HOME a temp dir the real queue is never read - order_id null', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const repo = gitRepo('voyage');
try {
const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's' }, { repo: repo.dir, home });
assert.ok('order_id' in rec);
assert.equal(rec.order_id, null);
assert.equal(rec.order_reason, 'coord-missing');
} finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); }
});
test('CLI: stats-append writes when it is run through a symlink', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const links = mkdtempSync(join(tmpdir(), 'stats-landing-link-'));
try {
const link = join(links, 'stats-append.mjs');
symlinkSync(join(ROOT, 'lib', 'stats', 'stats-append.mjs'), link);
const r = spawnSync('node', [link, 'trekplan'], { encoding: 'utf8', input: '{"slug":"s"}',
env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home } });
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.ok(existsSync(join(home, DATA_REL, 'trekplan-stats.jsonl')), `no record landed; stdout: "${r.stdout}"`);
} finally { rmSync(home, { recursive: true, force: true }); rmSync(links, { recursive: true, force: true }); }
});
test('CLI: stats-append writes when its path contains a space', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
const spaced = mkdtempSync(join(tmpdir(), 'stats landing space-'));
try {
cpSync(join(ROOT, 'lib'), join(spaced, 'with space', 'lib'), { recursive: true });
const cli = join(spaced, 'with space', 'lib', 'stats', 'stats-append.mjs');
const r = spawnSync('node', [cli, 'trekplan'], { encoding: 'utf8', input: '{"slug":"s"}',
env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home } });
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.ok(existsSync(join(home, DATA_REL, 'trekplan-stats.jsonl')), `no record landed; stdout: "${r.stdout}"`);
} finally { rmSync(home, { recursive: true, force: true }); rmSync(spaced, { recursive: true, force: true }); }
});
// ---- /trekexecute: every telemetry write lands through a writer ----------------
// Phase 8's three event-emit calls and Phase 9's stats record; the
// gates_mode_flag_override record is gone, since no effort sets gates_mode (#33).
// Phase 9 said "skip silently"; Phase 8's payloads spliced ${PLAN_PATH} (set in an earlier
// Bash call that does not survive) and ${WAVE_COUNT} (never assigned) into JSON,
// so a literal run printed payload-not-json. Every block runs here in a fresh
// shell with NOTHING exported, CLAUDE_PLUGIN_DATA empty and HOME pinned.
const TE = 'commands/trekexecute.md';
/** The fenced bash blocks in `sec` that contain `needle`. */
function fencedBlocksWith(sec, needle) {
const blocks = [];
const re = /```bash\n([\s\S]*?)```/g;
let m;
while ((m = re.exec(sec)) !== null) if (m[1].includes(needle)) blocks.push(m[1]);
return blocks;
}
/** Fill the prose placeholders an executor fills in, then run the block. */
function runBlock(block, home, { planPath = '.claude/projects/2026-10-05-x/plan.md', cwd } = {}) {
const cmd = block.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT)
.split('{plan-path}').join(planPath)
.split('{wave-count}').join('1');
return spawnSync('bash', ['-c', cmd], {
encoding: 'utf8',
cwd,
env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home },
});
}
test('M6: /trekexecute Phase 9 writes through stats-append (code), never "skip silently" (prose)', () => {
appendLine(section(TE, 'Phase 9'), 'trekexecute', TE);
assert.doesNotMatch(read(TE), /skip (tracking )?silently/i, 'a silent skip is how the record never landed');
});
test('M6: with CLAUDE_PLUGIN_DATA empty the trekexecute record lands where the yardstick reads', () => {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
try {
const line = appendLine(section(TE, 'Phase 9'), 'trekexecute', TE);
const r = runLine(line, { ts: '2026-10-05T10:00:00Z', plan: 'plan.md', result: 'completed' }, home);
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.equal(records(join(home, DATA_REL, 'trekexecute-stats.jsonl')).length, 1);
} finally { rmSync(home, { recursive: true, force: true }); }
});
test('trekexecute.md names no bare ${CLAUDE_PLUGIN_DATA}/...jsonl write target', () => {
const hits = read(TE).split('\n')
.map((l, i) => [i + 1, l]).filter(([, l]) => /\$\{CLAUDE_PLUGIN_DATA\}\/[\w-]+\.jsonl/.test(l));
assert.deepEqual(hits, [], 'every stats or event write goes through a writer that resolves the directory');
});
test('no --payload in trekexecute.md expands a shell variable', () => {
const hits = read(TE).split('\n')
.map((l, i) => [i + 1, l]).filter(([, l]) => /--payload\b.*\$\{/.test(l));
assert.deepEqual(hits, [], 'a payload built from shell state that does not survive between Bash calls never parses');
});
test('Phase 8: each event block, run as written, lands one known event', () => {
const blocks = fencedBlocksWith(section(TE, 'Phase 8'), 'event-emit.mjs');
assert.equal(blocks.length, 3, 'main-merge-gate, main-merge-approved, main-merge-declined');
for (const block of blocks) {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
try {
const r = runBlock(block, home);
assert.equal(r.status, 0, `${block}\n${r.stdout}${r.stderr}`);
assert.equal(JSON.parse(r.stdout.trim()).written, true, r.stdout);
const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl'));
assert.equal(rec.known_event, true, rec.event);
} finally { rmSync(home, { recursive: true, force: true }); }
}
});
// The plan path is operator-chosen (positional [plan.md], --project <dir>). Spliced
// into a single-quoted argument, a lone quote broke the shell parse (event lost,
// no written:false to report) and a quoted pair let a command substitution run.
test('Phase 8: a plan path with a quote character lands literally and the shell runs nothing in it', () => {
const blocks = fencedBlocksWith(section(TE, 'Phase 8'), 'event-emit.mjs');
assert.equal(blocks.length, 3, 'main-merge-gate, main-merge-approved, main-merge-declined');
for (const plan of [".claude/projects/2026-10-05-it's/plan.md", ".claude/projects/a'$(touch PWNED)'b/plan.md"]) {
for (const block of blocks) {
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
try {
const r = runBlock(block, home, { planPath: plan, cwd: home });
assert.ok(!existsSync(join(home, 'PWNED')), `the shell ran a command from the plan path:\n${block}`);
assert.equal(r.status, 0, `${block}\n${r.stdout}${r.stderr}`);
const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl'));
assert.equal(rec.payload.plan, plan);
} finally { rmSync(home, { recursive: true, force: true }); }
}
}
});
test('gates_mode_flag_override: the Composition rule holds no override record to report on', () => {
const flat = section(TE, 'Composition rule (v5.1)').replace(/\s+/g, ' ');
assert.doesNotMatch(flat, /--event gates_mode_flag_override/, 'no effort sets gates_mode, so there is no override to log');
assert.doesNotMatch(flat, /override record never blocks the run/);
});
test('Phase 8: a written:false is printed in one line and the run continues', () => {
const sec = section(TE, 'Phase 8');
const flat = sec.replace(/\s+/g, ' ');
assert.match(flat, /On `"written": false` \(exit 1\), print its `reason` in one line and continue/);
assert.match(flat, /never chained with `&&`/);
});
test('gates_mode_flag_override: /trekexecute holds no --event gates_mode_flag_override block', () => {
const sec = section(TE, 'Composition rule (v5.1)');
assert.deepEqual(fencedBlocksWith(sec, '--event gates_mode_flag_override'), [],
'high effort sets no gates_mode, so no override is written');
assert.deepEqual(fencedBlocksWith(read(TE), 'gates_mode_flag_override'), []);
assert.doesNotMatch(sec, /\{ts, event, known_event, payload:\{flag, signal_effort\}\}/);
});

View file

@ -1,188 +0,0 @@
// tests/lib/token-usage.test.mjs
// SKAL-2 Step 1 — pure token-usage parser + cache-aware cost derivation.
//
// Hermetic: no LLM, network, time, or randomness. Fixture transcript exercises
// the dedup-by-requestId (streaming-placeholder) and isSidechain-exclusion
// paths that were verified against a real local transcript at execute time.
// Idiom: tests/lib/coordinator-contract.test.mjs (pure-module node:test style).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
parseTranscriptUsage,
deriveCost,
buildRecord,
upsertSessionRecord,
lastMainChainModel,
PRICE_TABLE,
PRICE_TABLE_VERSION,
} from '../../lib/stats/token-usage.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const FIXTURE = join(HERE, '..', 'fixtures', 'token-usage', 'sample-transcript.jsonl');
// ---- parseTranscriptUsage --------------------------------------------------
test('parseTranscriptUsage — sums main-chain final-per-requestId; excludes sidechain + placeholder dup', () => {
const text = readFileSync(FIXTURE, 'utf-8');
const totals = parseTranscriptUsage(text);
// req_A: placeholder {1,1,0,0} then real {1000,200,400,5000} → LAST wins.
// req_B: appears twice identically → dedup → counted once {2000,300,600,1000}.
// req_SIDE (isSidechain:true, all 9999) → EXCLUDED. user/system/malformed → skipped.
assert.deepEqual(totals, {
tokens_input: 3000, // 1000 + 2000 (NOT 1 — placeholder dropped)
tokens_output: 500, // 200 + 300
tokens_cache_creation: 1000, // 400 + 600
tokens_cache_read: 6000, // 5000 + 1000
});
// Sidechain 9999s must not leak into any field.
assert.ok(totals.tokens_input < 9999, 'sidechain tokens leaked into total');
});
test('parseTranscriptUsage — empty / nullish input → zeroed totals', () => {
const zero = { tokens_input: 0, tokens_output: 0, tokens_cache_creation: 0, tokens_cache_read: 0 };
assert.deepEqual(parseTranscriptUsage(''), zero);
assert.deepEqual(parseTranscriptUsage(null), zero);
assert.deepEqual(parseTranscriptUsage(undefined), zero);
});
test('parseTranscriptUsage — single-pass O(n) shape on a 500-line transcript', () => {
// 500 distinct-requestId main-chain assistant records, each input_tokens:10.
const lines = [];
for (let i = 0; i < 500; i++) {
lines.push(JSON.stringify({
type: 'assistant',
isSidechain: false,
requestId: `req_${i}`,
message: { model: 'claude-opus-4-8', usage: { input_tokens: 10, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 } },
}));
}
const totals = parseTranscriptUsage(lines.join('\n'));
assert.equal(totals.tokens_input, 5000); // 500 × 10
});
// ---- deriveCost ------------------------------------------------------------
test('deriveCost — hand-computed value for a known model (cache-aware)', () => {
const totals = { tokens_input: 3000, tokens_output: 500, tokens_cache_creation: 1000, tokens_cache_read: 6000 };
const { cost_usd, is_estimate } = deriveCost(totals, 'claude-opus-4-8');
// (3000×5 + 500×25 + 1000×6.25 + 6000×0.5) / 1e6
// = (15000 + 12500 + 6250 + 3000) / 1e6 = 36750 / 1e6 = 0.03675
assert.equal(cost_usd, 0.03675);
assert.equal(is_estimate, false);
});
test('deriveCost — hand-computed value for claude-fable-5 (v5.9 fable tier)', () => {
const totals = { tokens_input: 3000, tokens_output: 500, tokens_cache_creation: 1000, tokens_cache_read: 6000 };
const { cost_usd, is_estimate } = deriveCost(totals, 'claude-fable-5');
// (3000×10 + 500×50 + 1000×12.5 + 6000×1) / 1e6
// = (30000 + 25000 + 12500 + 6000) / 1e6 = 73500 / 1e6 = 0.0735
assert.equal(cost_usd, 0.0735);
assert.equal(is_estimate, false);
});
test('deriveCost — refuse-to-estimate for an unknown model', () => {
const totals = { tokens_input: 3000, tokens_output: 500, tokens_cache_creation: 1000, tokens_cache_read: 6000 };
const out = deriveCost(totals, 'claude-unknown-9');
assert.equal(out.cost_usd, null);
assert.equal(out.is_estimate, true);
});
test('PRICE_TABLE — frozen, opus-4-8 carries all five rate fields', () => {
assert.ok(Object.isFrozen(PRICE_TABLE), 'PRICE_TABLE must be frozen (drift-pin)');
const p = PRICE_TABLE['claude-opus-4-8'];
for (const k of ['input', 'output', 'cache_read', 'cache_write_5m', 'cache_write_1h']) {
assert.equal(typeof p[k], 'number', `missing rate field ${k}`);
}
});
// ---- buildRecord -----------------------------------------------------------
test('buildRecord — stamps scope:main-context + price_table_version + flat numerics', () => {
const totals = { tokens_input: 3000, tokens_output: 500, tokens_cache_creation: 1000, tokens_cache_read: 6000 };
const rec = buildRecord({ sessionId: 'S1', model: 'claude-opus-4-8', totals, now: '2026-06-26T12:00:00Z' });
assert.equal(rec.scope, 'main-context');
assert.equal(rec.price_table_version, PRICE_TABLE_VERSION);
assert.equal(rec.ts, '2026-06-26T12:00:00Z');
assert.equal(rec.session_id, 'S1');
assert.equal(rec.model, 'claude-opus-4-8');
assert.equal(rec.tokens_input, 3000);
assert.equal(rec.cost_usd, 0.03675);
assert.equal(rec.is_estimate, false);
});
test('buildRecord — unknown model → cost_usd null + is_estimate true (honesty contract)', () => {
const rec = buildRecord({ sessionId: 'S9', model: 'mystery', totals: { tokens_input: 1 }, now: 't' });
assert.equal(rec.cost_usd, null);
assert.equal(rec.is_estimate, true);
});
// ---- upsertSessionRecord ---------------------------------------------------
test('upsertSessionRecord — REPLACES a same-session line (not append)', () => {
const existing =
JSON.stringify({ session_id: 'S1', tokens_input: 1, scope: 'main-context' }) + '\n' +
JSON.stringify({ session_id: 'S2', tokens_input: 2, scope: 'main-context' }) + '\n';
const rec = buildRecord({ sessionId: 'S1', model: 'claude-opus-4-8', totals: { tokens_input: 999 }, now: 't' });
const out = upsertSessionRecord(existing, rec);
const recs = out.trim().split('\n').map(JSON.parse);
assert.equal(recs.length, 2, 'replace must NOT add a line');
const s1 = recs.find(r => r.session_id === 'S1');
assert.equal(s1.tokens_input, 999, 'S1 must carry the new totals');
assert.ok(recs.some(r => r.session_id === 'S2'), 'S2 must be preserved');
});
test('upsertSessionRecord — APPENDS a new-session line', () => {
const existing = JSON.stringify({ session_id: 'S1', tokens_input: 1 }) + '\n';
const rec = buildRecord({ sessionId: 'S3', model: 'claude-opus-4-8', totals: { tokens_input: 7 }, now: 't' });
const out = upsertSessionRecord(existing, rec);
const recs = out.trim().split('\n').map(JSON.parse);
assert.equal(recs.length, 2, 'new session must append');
assert.ok(recs.some(r => r.session_id === 'S3' && r.tokens_input === 7));
});
test('upsertSessionRecord — empty file → single record, trailing newline', () => {
const rec = buildRecord({ sessionId: 'S1', model: 'claude-opus-4-8', totals: { tokens_input: 5 }, now: 't' });
const out = upsertSessionRecord('', rec);
assert.ok(out.endsWith('\n'));
assert.deepEqual(out.trim().split('\n').map(JSON.parse).length, 1);
});
// ---- lastMainChainModel ----------------------------------------------------
test('lastMainChainModel — last main-chain model wins; sidechain excluded even when last', () => {
// Two distinct main-chain models (opus then sonnet) → sonnet (last) wins.
// A sidechain record sits AFTER sonnet: if exclusion broke it would win;
// if last-wins broke, opus (first) would win. Correct answer pins both.
const text = [
JSON.stringify({ type: 'assistant', isSidechain: false, message: { model: 'claude-opus-4-8', usage: {} } }),
JSON.stringify({ type: 'assistant', isSidechain: false, message: { model: 'claude-sonnet-4-6', usage: {} } }),
JSON.stringify({ type: 'assistant', isSidechain: true, message: { model: 'claude-sidechain-ZZZ', usage: {} } }),
JSON.stringify({ type: 'user', message: { content: 'ignored' } }),
].join('\n');
assert.equal(lastMainChainModel(text), 'claude-sonnet-4-6');
});
test('lastMainChainModel — no main-chain model → null → deriveCost refuses to estimate', () => {
// Only a sidechain model, a user line, and a malformed line: no main-chain model.
const text = [
JSON.stringify({ type: 'assistant', isSidechain: true, message: { model: 'claude-opus-4-8', usage: {} } }),
JSON.stringify({ type: 'user', message: { content: 'hi' } }),
'not json',
].join('\n');
const model = lastMainChainModel(text);
assert.equal(model, null);
// null model must propagate to refuse-to-estimate (honesty contract).
const { cost_usd, is_estimate } = deriveCost({ tokens_input: 1000 }, model);
assert.equal(cost_usd, null);
assert.equal(is_estimate, true);
});
test('lastMainChainModel — empty / nullish input → null', () => {
assert.equal(lastMainChainModel(''), null);
assert.equal(lastMainChainModel(null), null);
assert.equal(lastMainChainModel(undefined), null);
});

View file

@ -1,422 +0,0 @@
// tests/scripts/comparison-gate.test.mjs
// The comparison gate counts the areas where Voyage + the operator's way of working is
// level with or ahead of every reference framework, and fells the table when a claim
// has no evidence behind it. It also counts the "tidy" denominator: every Voyage part a
// documented Claude Code primitive covers, and how many of those are resolved.
//
// Like the yardstick's tests, these pin the MECHANICS in both directions against
// throwaway fixtures. They deliberately do NOT assert that the real table is red today:
// the gate is supposed to turn green, and a test that pins today's distance would fail
// on the day the work is done. Nothing here reads the real table or the real source.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { execFileSync } from 'node:child_process';
import {
parseLoc,
parseSourceAreas,
listParts,
evaluate,
render,
main,
} from '../../scripts/comparison-gate.mjs';
// --- helpers ---------------------------------------------------------------
const lines = (n) => `${Array.from({ length: n }, (_, i) => `line ${i + 1}`).join('\n')}\n`;
const SOURCE = [
'# report',
'',
'## 2. Per kapabilitet',
'',
'| Kapabilitet | A | B |',
'|---|---|---|',
'| Alpha | x | y |',
'| Beta | x | y |',
'',
'## 3. Next section',
'',
'| Not | an | area |',
'|---|---|---|',
'| Gamma | x | y |',
'',
].join('\n');
function world() {
const dir = mkdtempSync(join(tmpdir(), 'comparison-gate-'));
const files = {
'vy/own.md': lines(5),
'pm/source.md': SOURCE,
'pm/why.md': lines(3),
'sp/ref.md': lines(10),
};
for (const [rel, body] of Object.entries(files)) {
const p = join(dir, rel);
mkdirSync(dirname(p), { recursive: true });
writeFileSync(p, body);
}
return { dir, roots: { VY: join(dir, 'vy'), PM: join(dir, 'pm'), SP: join(dir, 'sp') } };
}
const cleanup = (w) => rmSync(w.dir, { recursive: true, force: true });
// A table that is fully green: every area level or ahead, every cell with evidence,
// an independent reader, and the one covered part resolved with a measured reason.
function greenData(w) {
return {
schema: 'voyage-comparison/1',
source: { loc: 'PM:source.md', heading: '## 2. Per kapabilitet' },
roots: w.roots,
references: [{ id: 'SP', name: 'obra/superpowers' }],
reader: { kind: 'uavhengig', who: 'fixture', date: '2026-10-02' },
areas: [
{
id: 'alpha',
title: 'Alpha',
own: { evidence: [{ loc: 'VY:own.md:1-2', says: 'own side' }] },
cells: {
SP: { verdict: 'likt', why: 'level', evidence: [{ loc: 'SP:ref.md:3-10', says: 'ref side' }] },
},
},
{
id: 'beta',
title: 'Beta',
own: { evidence: [{ loc: 'VY:own.md:5', says: 'own side' }] },
cells: {
SP: {
verdict: 'foran',
why: 'the reference has nothing here',
evidence: [{
absence: { query: 'grep -rn beta .', hits: 0, known_positive: 'grep -rn line . finds 10' },
says: 'no beta in the reference',
}],
},
},
},
],
tidy: {
primitives: [{ id: 'subagents', name: 'Subagents', doc: 'https://code.claude.com/docs/en/sub-agents', quote: 'q' }],
parts: [
{
path: 'commands/x.md', covered_by: ['subagents'], coverage: 'delvis', why: 'w',
resolution: 'maalt-grunn', reason: { loc: 'PM:why.md:1-3', says: 'measured' },
},
{ path: 'lib/y.mjs', covered_by: [], coverage: 'ikke-dekket', why: 'w', resolution: null, reason: null },
],
},
};
}
const PARTS = ['commands/x.md', 'lib/y.mjs'];
const run = (w, data) => evaluate(data, { parts: PARTS });
const problemText = (r) => r.problems.join('\n');
// --- parsing -----------------------------------------------------------------
test('parseLoc: root, path and a line or a line range', () => {
assert.deepEqual(parseLoc('SP:skills/a/SKILL.md:38-88'), { root: 'SP', path: 'skills/a/SKILL.md', start: 38, end: 88 });
assert.deepEqual(parseLoc('VY:lib/x.mjs:7'), { root: 'VY', path: 'lib/x.mjs', start: 7, end: 7 });
assert.equal(parseLoc('lib/x.mjs:7'), null, 'a loc without a root is not evidence');
assert.equal(parseLoc('VY:lib/x.mjs'), null, 'a loc without a line is not evidence');
assert.equal(parseLoc('VY:lib/x.mjs:9-3'), null, 'a range that runs backwards is not evidence');
assert.equal(parseLoc('VY:lib/x.mjs:0'), null, 'lines count from 1');
});
test('parseSourceAreas: reads the first column of the table under the heading, and only that table', () => {
assert.deepEqual(parseSourceAreas(SOURCE, '## 2. Per kapabilitet'), ['Alpha', 'Beta']);
assert.equal(parseSourceAreas(SOURCE, '## 9. Missing'), null);
});
test('listParts: tracked files in the scope, eval cases as one part each, nothing outside the scope', () => {
const dir = mkdtempSync(join(tmpdir(), 'comparison-gate-parts-'));
try {
for (const rel of ['commands/a.md', 'lib/u/b.mjs', 'evals/case-1/eval.json', 'evals/case-1/fixture/x.md',
'evals/case-2/eval.json', 'docs/not-a-part.md', 'settings.json', 'untracked/z.md']) {
mkdirSync(dirname(join(dir, rel)), { recursive: true });
writeFileSync(join(dir, rel), 'x\n');
}
execFileSync('git', ['init', '-q'], { cwd: dir });
execFileSync('git', ['add', 'commands', 'lib', 'evals', 'docs', 'settings.json'], { cwd: dir });
assert.deepEqual(listParts(dir), ['commands/a.md', 'evals/case-1/', 'evals/case-2/', 'lib/u/b.mjs', 'settings.json']);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// --- the comparison ------------------------------------------------------------
test('a fully evidenced table, every area level or ahead, read by an independent reader, is green', () => {
const w = world();
try {
const r = run(w, greenData(w));
assert.deepEqual(r.problems, []);
assert.equal(r.comparison.level_or_ahead, 2);
assert.equal(r.comparison.total, 2);
assert.equal(r.green, true);
} finally { cleanup(w); }
});
test('a cell with NO evidence fells the table, whatever its verdict says', () => {
const w = world();
try {
const data = greenData(w);
data.areas[0].cells.SP.evidence = [];
const r = run(w, data);
assert.match(problemText(r), /alpha.*SP.*no evidence/);
assert.equal(r.green, false);
} finally { cleanup(w); }
});
test('an area with no evidence on our own side fells the table', () => {
const w = world();
try {
const data = greenData(w);
delete data.areas[1].own;
const r = run(w, data);
assert.match(problemText(r), /beta.*own.*no evidence/);
assert.equal(r.green, false);
} finally { cleanup(w); }
});
test('a cited line past the end of the file is not evidence', () => {
const w = world();
try {
const data = greenData(w);
data.areas[0].cells.SP.evidence[0].loc = 'SP:ref.md:3-11';
const r = run(w, data);
assert.match(problemText(r), /SP:ref\.md:3-11.*10 lines/);
assert.equal(r.green, false);
} finally { cleanup(w); }
});
test('a cited file that does not exist, or that escapes its root, is not evidence', () => {
const w = world();
try {
const data = greenData(w);
data.areas[0].cells.SP.evidence[0].loc = 'SP:missing.md:1';
data.areas[0].own.evidence[0].loc = 'VY:../pm/source.md:1';
const r = run(w, data);
assert.match(problemText(r), /SP:missing\.md:1.*does not exist/);
assert.match(problemText(r), /VY:\.\.\/pm\/source\.md:1.*outside its root/);
} finally { cleanup(w); }
});
test('an absence without a known-positive control is not evidence, and neither is one with hits', () => {
const w = world();
try {
const data = greenData(w);
delete data.areas[1].cells.SP.evidence[0].absence.known_positive;
let r = run(w, data);
assert.match(problemText(r), /beta.*SP.*known_positive/);
const again = greenData(w);
again.areas[1].cells.SP.evidence[0].absence.hits = 2;
r = run(w, again);
assert.match(problemText(r), /beta.*SP.*hits/);
} finally { cleanup(w); }
});
test('a web citation needs a verbatim quote', () => {
const w = world();
try {
const data = greenData(w);
data.areas[0].cells.SP.evidence = [{ url: 'https://example.org/playbook#review', says: 'x' }];
let r = run(w, data);
assert.match(problemText(r), /alpha.*SP.*quote/);
data.areas[0].cells.SP.evidence = [{ url: 'https://example.org/playbook#review', quote: 'every change is reviewed', says: 'x' }];
r = run(w, data);
assert.deepEqual(r.problems, []);
} finally { cleanup(w); }
});
test('the denominator comes from the SOURCE: an area the source lists but the table drops fells it', () => {
const w = world();
try {
const data = greenData(w);
data.areas.pop();
const r = run(w, data);
assert.match(problemText(r), /Beta.*missing from the table/);
assert.equal(r.comparison.total, 2, 'the total is the source count, not the table count');
assert.equal(r.green, false);
} finally { cleanup(w); }
});
test('an area the source does not list is not counted and is reported', () => {
const w = world();
try {
const data = greenData(w);
data.areas.push({ ...data.areas[0], id: 'gamma', title: 'Gamma' });
const r = run(w, data);
assert.match(problemText(r), /Gamma.*not in the source/);
assert.equal(r.comparison.total, 2);
} finally { cleanup(w); }
});
test('a missing reference cell fells the table; an unknown verdict fells it', () => {
const w = world();
try {
const data = greenData(w);
data.references.push({ id: 'PS', name: 'mattpocock/skills' });
let r = run(w, data);
assert.match(problemText(r), /alpha.*PS.*missing/);
const again = greenData(w);
again.areas[0].cells.SP.verdict = 'probably fine';
r = run(w, again);
assert.match(problemText(r), /alpha.*SP.*verdict/);
} finally { cleanup(w); }
});
test('one reference ahead of us in one area: that area is not counted, and it is named', () => {
const w = world();
try {
const data = greenData(w);
data.areas[0].cells.SP.verdict = 'bak';
const r = run(w, data);
assert.deepEqual(r.problems, []);
assert.equal(r.comparison.level_or_ahead, 1);
assert.deepEqual(r.comparison.behind, [{ area: 'alpha', references: ['SP'] }]);
assert.equal(r.green, false);
} finally { cleanup(w); }
});
test('the count per reference shows which framework pulls the total down', () => {
const w = world();
try {
const data = greenData(w);
data.references.push({ id: 'PS', name: 'mattpocock/skills' });
for (const a of data.areas) a.cells.PS = { ...a.cells.SP };
data.areas[0].cells.PS = { ...data.areas[0].cells.SP, verdict: 'bak' };
const r = run(w, data);
assert.deepEqual(r.problems, []);
assert.deepEqual(r.comparison.per_reference, { SP: 2, PS: 1 });
assert.equal(r.comparison.level_or_ahead, 1);
assert.match(render(r), /PS: 1 of 2/);
} finally { cleanup(w); }
});
test('14 of 14 is not green until an INDEPENDENT reader judged it', () => {
const w = world();
try {
const data = greenData(w);
data.reader.kind = 'pm-rapport';
const r = run(w, data);
assert.deepEqual(r.problems, []);
assert.equal(r.comparison.level_or_ahead, 2);
assert.equal(r.green, false);
assert.match(r.reasons.join('\n'), /independent reader/);
} finally { cleanup(w); }
});
test('a pinned reference whose clone has moved is not evidence for the pinned revision', () => {
const w = world();
try {
execFileSync('git', ['init', '-q'], { cwd: w.roots.SP });
execFileSync('git', ['add', '.'], { cwd: w.roots.SP });
execFileSync('git', ['-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', 'x'], { cwd: w.roots.SP });
const data = greenData(w);
data.references[0].pin = '0000000';
const r = run(w, data);
assert.match(problemText(r), /SP.*pinned 0000000.*HEAD/);
data.references[0].pin = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: w.roots.SP, encoding: 'utf-8' }).trim();
assert.deepEqual(run(w, data).problems, []);
} finally { cleanup(w); }
});
// --- tidy ------------------------------------------------------------------------
test('tidy: a tracked part the list leaves out fells it; the denominator is the tree, not the list', () => {
const w = world();
try {
const r = evaluate(greenData(w), { parts: [...PARTS, 'agents/new.md'] });
assert.match(problemText(r), /agents\/new\.md.*missing from the tidy list/);
assert.equal(r.tidy.parts, 3);
assert.equal(r.green, false);
} finally { cleanup(w); }
});
test('tidy: a covered part counts as resolved only with a measured reason or as removed from the tree', () => {
const w = world();
try {
const data = greenData(w);
data.tidy.parts[0].resolution = 'aapen';
data.tidy.parts[0].reason = null;
let r = run(w, data);
assert.deepEqual(r.problems, []);
assert.equal(r.tidy.covered, 1);
assert.equal(r.tidy.resolved, 0);
assert.equal(r.green, false);
const noReason = greenData(w);
noReason.tidy.parts[0].reason = null;
r = run(w, noReason);
assert.match(problemText(r), /commands\/x\.md.*maalt-grunn.*reason/);
const removed = greenData(w);
removed.tidy.parts[0] = { ...removed.tidy.parts[0], resolution: 'fjernet', reason: null, commit: 'abc1234' };
r = run(w, removed);
assert.match(problemText(r), /commands\/x\.md.*fjernet.*still in the tree/);
r = evaluate(removed, { parts: ['lib/y.mjs'] });
assert.deepEqual(r.problems, []);
assert.equal(r.tidy.resolved, 1);
} finally { cleanup(w); }
});
test('tidy: a primitive without a documentation URL, or a part covered by an unknown primitive, fells it', () => {
const w = world();
try {
const data = greenData(w);
data.tidy.primitives[0].doc = 'somewhere';
data.tidy.parts[0].covered_by = ['subagents', 'nonexistent'];
const r = run(w, data);
assert.match(problemText(r), /subagents.*documentation URL/);
assert.match(problemText(r), /commands\/x\.md.*nonexistent/);
} finally { cleanup(w); }
});
// --- output and CLI ------------------------------------------------------------------
test('render names the count, the behind areas and the reader', () => {
const w = world();
try {
const data = greenData(w);
data.areas[0].cells.SP.verdict = 'bak';
data.reader.kind = 'pm-rapport';
const text = render(run(w, data));
assert.match(text, /1 of 2/);
assert.match(text, /alpha \(SP\)/);
assert.match(text, /pm-rapport/);
assert.match(text, /RED/);
} finally { cleanup(w); }
});
test('main: a missing table is NOT MEASURED and exits 1, never 0; an unknown flag exits 2', () => {
const w = world();
try {
assert.equal(main(['--data', join(w.dir, 'nope.json'), '--json']), 1);
assert.equal(main(['--nope']), 2);
} finally { cleanup(w); }
});
test('main: exit 0 only for a green table, 1 for a red one', () => {
const w = world();
try {
const file = join(w.dir, 'table.json');
writeFileSync(file, JSON.stringify(greenData(w)));
// --parts-from pins the denominator to a fixture repo instead of the real tree
const repo = join(w.dir, 'repo');
for (const rel of PARTS) {
mkdirSync(dirname(join(repo, rel)), { recursive: true });
writeFileSync(join(repo, rel), 'x\n');
}
execFileSync('git', ['init', '-q'], { cwd: repo });
execFileSync('git', ['add', '.'], { cwd: repo });
assert.equal(main(['--data', file, '--parts-from', repo, '--json']), 0);
const red = greenData(w);
red.areas[0].cells.SP.evidence = [];
writeFileSync(file, JSON.stringify(red));
assert.equal(main(['--data', file, '--parts-from', repo, '--json']), 1);
} finally { cleanup(w); }
});

View file

@ -1,950 +0,0 @@
// tests/scripts/end-state-gate.test.mjs
// The end-state gate counts the distance to "Voyage is finished" as five tallies:
// open pipeline defects, unresolved experiments/opt-ins, dormant agents, open operator
// decisions, and feat commits after the freeze tag. "Finished" = all five at 0 AND the
// registry the gate counts from is intact against its frozen denominator.
//
// These tests pin the gate's MECHANICS in both directions — every tally can be 0 and can
// be > 0 — against throwaway fixture trees. They deliberately do NOT assert that the real
// repo is red today: the gate is supposed to go green, and a test that pins the current
// distance would fail on the day the work is done. What they DO pin about the real repo
// is the denominator itself (see "the frozen denominator" below).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import {
evaluateCondition,
evaluateCheck,
measure,
loadRegistry,
loadFrozen,
spawnedNames,
checkSignature,
verifyIntegrity,
render,
PROBE_NOTES,
} from '../../scripts/end-state-gate.mjs';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
const GATE = join(ROOT, 'scripts', 'end-state-gate.mjs');
// --- the frozen denominator ------------------------------------------------
// Denominator frozen 2026-09-17, the day the end-state
// direction was chosen. An entry may CLOSE; it may never be removed or have its check
// changed, and the gate's agent/decision/freeze configuration may not move. To change
// anything below you must change this test — say in the commit message who decided it.
const FROZEN = {
defects: {
'D-01': '48f7f5cca070091e8f9b9ca0c7cdb7a1880733691935c25d07ea1d3e8d508fc9',
'D-02': 'dfc94dba04a9116ae9be2097a0c5a9d8313b02c7de1ca6b49a472c1823a6d1e1',
// Amended 2026-09-18 (operator decision): D-03/D-04 became behaviour probes.
// Amended a second time the same day, as a maintenance decision: those
// behaviour probes were greps for an export name, and a two-line stub
// exporting both names closed them on an unfixed tree (mutant M6, pinned
// below). They now RUN a named test.
// Amended a third time the same day, same standing: a named test can be
// EMPTY, and two empty tests with the right names closed both defects on a
// tree without lib/verification/ (mutant M7, pinned below). Each condition
// now also names the module whose STUB its test must fell. Every amendment
// moved a signature on purpose and removed nothing.
'D-03': '98698ce74c243353713786b8604160f6672568b090884e9428d9e5e9fbcad89d',
'D-04': 'df11a2e95db7b014ce077db0473e5dde4195ace92d8077cacdc1cee273947b47',
'D-05': '8fe6df52b43496c3302400507cb98002dcf8d2a12d92085e14bda4c7819bc7ba',
'D-06': '72a787c8154d1c18849e9856dcf1a67703d6b5541f88db81d6bab13cab14b418',
'D-07': '1bf3e0cd36c621720697475b0a3fd7db78611d66b58a9ce035e7695e57c69f1b',
},
experiments: {
'E-01': 'f7718897cec8ace6879fe79f5f32760ec957c3642e7186f8aa1688ce77b3f4e8',
'E-02': 'c8ff12c504ba7a9ac36f11bb8f9b713263e9b4eb28c07d984d9e2115980e740d',
'E-03': '17153ad8808615a833a49ae0c3523264ce055aa690e5542a75bae1874495e7e8',
},
agents: {
dir: 'agents',
spawnSites: 'commands/*.md',
referenceMarker: 'Reference document, not a spawnable capability',
},
decisions: {
file: 'STATE.md',
section: '## Åpne operatørbeslutninger',
open: '^- \\[ \\] ',
closed: '^- \\[x\\] ',
},
freeze: { tag: 'end-state-freeze', featPattern: '^feat(\\(|!|:)' },
};
// --- helpers ---------------------------------------------------------------
function fixture(files) {
const dir = mkdtempSync(join(tmpdir(), 'end-state-gate-'));
for (const [rel, body] of Object.entries(files)) {
const p = join(dir, rel);
mkdirSync(dirname(p), { recursive: true });
writeFileSync(p, body);
}
return dir;
}
const cleanup = (...dirs) => { for (const d of dirs) rmSync(d, { recursive: true, force: true }); };
const DECISIONS = { file: 'STATE.md', section: '## Open decisions', open: '^- \\[ \\] ', closed: '^- \\[x\\] ' };
const AGENTS = { dir: 'agents', spawnSites: 'commands/*.md', referenceMarker: 'Reference document, not a spawnable capability' };
const FREEZE = { tag: 'end-state-freeze', featPattern: '^feat(\\(|!|:)' };
function registry(overrides = {}) {
return { defects: [], experiments: [], agents: AGENTS, decisions: DECISIONS, freeze: FREEZE, ...overrides };
}
// A frozen manifest that matches `reg` exactly.
function frozenFor(reg) {
const sigs = (list) => Object.fromEntries(list.map((e) => [e.id, checkSignature(e.check)]));
return { defects: sigs(reg.defects), experiments: sigs(reg.experiments), agents: reg.agents, decisions: reg.decisions, freeze: reg.freeze };
}
function git(dir, ...args) {
const r = spawnSync('git', [
'-c', 'user.name=fixture', '-c', 'user.email=fixture@example.invalid',
'-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', '-c', 'tag.gpgsign=false',
...args,
], { cwd: dir, encoding: 'utf8' });
assert.equal(r.status, 0, `git ${args.join(' ')}: ${r.stderr}`);
return r.stdout;
}
function gitInit(dir) {
git(dir, 'init', '-q');
git(dir, 'add', '-A');
git(dir, 'commit', '-q', '--allow-empty', '-m', 'chore: fixture baseline');
}
function gitCommit(dir, subject) {
git(dir, 'commit', '-q', '--allow-empty', '-m', subject);
}
const rowById = (result, id) => result.rows.find((r) => r.id === id);
// --- conditions -----------------------------------------------------------
test('evaluateCondition: match / no-match on a single file, multiline regex', () => {
const dir = fixture({ 'a.md': 'intro\n### Heading\nbody\n' });
try {
assert.equal(evaluateCondition(dir, { path: 'a.md', pattern: '^### Heading', flags: 'm', expect: 'match' }), true);
assert.equal(evaluateCondition(dir, { path: 'a.md', pattern: '^### Other', flags: 'm', expect: 'match' }), false);
assert.equal(evaluateCondition(dir, { path: 'a.md', pattern: '^### Other', flags: 'm', expect: 'no-match' }), true);
} finally { cleanup(dir); }
});
test('evaluateCondition: globs (dir/*.ext, dir/**/*.ext) and path arrays match ANY file', () => {
const dir = fixture({
'commands/x.md': 'nothing here\n',
'lib/deep/y.mjs': 'const FLAG = process.env.SOME_FLAG;\n',
});
try {
assert.equal(evaluateCondition(dir, { path: 'commands/*.md', pattern: 'SOME_FLAG', expect: 'match' }), false);
assert.equal(evaluateCondition(dir, { path: 'lib/**/*.mjs', pattern: 'SOME_FLAG', expect: 'match' }), true);
assert.equal(evaluateCondition(dir, { path: ['commands/*.md', 'lib/**/*.mjs'], pattern: 'SOME_FLAG', expect: 'match' }), true);
assert.equal(evaluateCondition(dir, { path: ['commands/*.md', 'lib/**/*.mjs'], pattern: 'SOME_FLAG', expect: 'no-match' }), false);
} finally { cleanup(dir); }
});
test('evaluateCondition: section scoping reads only from the heading to the next "## "', () => {
const dir = fixture({
'doc.md': '## 5. Design\nno status here\n## 6. PoC\n> **STATUS: RUN AND DECLINED**\n',
});
try {
const inFive = { path: 'doc.md', section: '## 5.', pattern: '^> \\*\\*STATUS:', flags: 'm', expect: 'no-match' };
const inSix = { ...inFive, section: '## 6.' };
assert.equal(evaluateCondition(dir, inFive), true, 'section 5 has no status block');
assert.equal(evaluateCondition(dir, inSix), false, 'section 6 has one');
} finally { cleanup(dir); }
});
test('evaluateCondition: a literal NUL byte is detectable', () => {
const dir = fixture({ 'nul.mjs': "const SEP = '\u0000';\n", 'clean.mjs': "const SEP = '\\x00';\n" });
try {
assert.equal(evaluateCondition(dir, { path: 'nul.mjs', pattern: '\\u0000', expect: 'match' }), true);
assert.equal(evaluateCondition(dir, { path: 'clean.mjs', pattern: '\\u0000', expect: 'match' }), false);
} finally { cleanup(dir); }
});
test('evaluateCondition: a missing file, a missing glob base or a missing section throws', () => {
const dir = fixture({ 'a.md': 'x\n' });
try {
assert.throws(() => evaluateCondition(dir, { path: 'missing.md', pattern: 'x', expect: 'no-match' }), /missing\.md/);
assert.throws(() => evaluateCondition(dir, { path: 'nodir/*.md', pattern: 'x', expect: 'no-match' }), /nodir/);
assert.throws(() => evaluateCondition(dir, { path: 'a.md', section: '## 9.', pattern: 'x', expect: 'no-match' }), /## 9\./);
} finally { cleanup(dir); }
});
test('evaluateCondition: a glob over an EXISTING dir with no matching files throws (no silent no-match)', () => {
// In a no-match condition, "silently nothing matched" would read as the condition holding.
const dir = fixture({ 'docs/readme.txt': 'x\n' });
try {
assert.throws(() => evaluateCondition(dir, { path: 'docs/*.md', pattern: 'x', expect: 'no-match' }), /docs\/\*\.md/);
assert.throws(() => evaluateCondition(dir, { path: 'docs/**/*.md', pattern: 'x', expect: 'no-match' }), /docs\/\*\*\/\*\.md/);
} finally { cleanup(dir); }
});
test('evaluateCheck: open only when ALL conditions hold; null check is not fellable', () => {
const dir = fixture({
'r.md': 'run the `### Bridge agent` block\n',
'fixed.md': 'run the `### Bridge agent` block\n### Bridge agent\n',
});
const dangling = (file) => [
{ path: file, pattern: '`### Bridge agent`', expect: 'match' },
{ path: file, pattern: '^### Bridge agent', flags: 'm', expect: 'no-match' },
];
try {
assert.equal(evaluateCheck(dir, dangling('r.md')).status, 'open');
assert.equal(evaluateCheck(dir, dangling('fixed.md')).status, 'closed');
assert.equal(evaluateCheck(dir, null).status, 'not-fellable');
const broken = evaluateCheck(dir, dangling('gone.md'));
assert.equal(broken.status, 'not-fellable');
assert.match(broken.detail, /gone\.md/);
} finally { cleanup(dir); }
});
// --- spawn instructions (what "referenced by a command" means) -------------
test('spawnedNames: a row of a table headed `Agent` is a spawn instruction; other tables are not', () => {
const md = [
'Launch the new agents:', '',
'| Agent | Purpose |', '|-------|---------|', '| `alpha-agent` | does a |', '| `beta-agent` | does b |', '',
'| Name | Purpose |', '|------|---------|', '| `gamma-agent` | just a list |', '',
].join('\n');
const names = spawnedNames(md);
assert.ok(names.has('alpha-agent') && names.has('beta-agent'));
assert.ok(!names.has('gamma-agent'), 'a table not headed "Agent" is not a roster');
});
test('spawnedNames: an imperative Launch/Spawn line names the agents it launches', () => {
const md = [
'Launch the **alpha-agent** agent:',
'- Launch `beta-agent` (Agent tool) with the findings.',
'3. Spawn `voyage:gamma-agent` in the background.',
'**Launch** the `delta-agent` agent now.',
'launch `epsilon-agent` too',
].join('\n');
const names = spawnedNames(md);
for (const n of ['alpha-agent', 'beta-agent', 'gamma-agent', 'delta-agent', 'epsilon-agent']) {
assert.ok(names.has(n), `${n} should count as spawned`);
}
});
test('spawnedNames: an agent block with a Prompt: line is a spawn instruction; a bare bold name is not', () => {
const md = [
'**alpha-agent** — adversarial review of the plan.',
'Prompt: "Review this plan."',
'',
'**beta-agent** — mentioned for context only.',
'It is not launched here.',
].join('\n');
const names = spawnedNames(md);
assert.ok(names.has('alpha-agent'));
assert.ok(!names.has('beta-agent'));
});
test('spawnedNames: prose, negation, comments, fences and quotes are never spawn instructions', () => {
const md = [
'The alpha-agent is dormant and is never spawned.',
'Never launch `beta-agent`.',
'In quick mode we used to launch `gamma-agent`.',
'<!-- Launch the `delta-agent` agent -->',
'<!--',
'Launch the `epsilon-agent` agent',
'-->',
'```',
'Launch the `zeta-agent` agent',
'| Agent |',
'|---|',
'| `eta-agent` |',
'```',
'> Launch the `theta-agent` agent',
].join('\n');
const names = spawnedNames(md);
for (const n of ['alpha-agent', 'beta-agent', 'gamma-agent', 'delta-agent', 'epsilon-agent', 'zeta-agent', 'eta-agent', 'theta-agent']) {
assert.ok(!names.has(n), `${n} must not count as spawned`);
}
});
// --- the tallies, both directions ------------------------------------------
test('defects tally: > 0 while a check holds, 0 once fixed; not-fellable counts as open', () => {
const defect = {
id: 'D-X',
summary: 'removed tool still referenced',
probe: 'phrase',
check: [{ path: 'commands/plan.md', pattern: '\\bTeamCreate\\b', expect: 'match' }],
};
const unfellable = { id: 'D-Y', summary: 'no runnable check yet', check: null };
const base = { 'STATE.md': '## Open decisions\n', 'agents/a.md': 'x\n', 'commands/plan.md': 'Launch `a`\n' };
const red = fixture({ ...base, 'commands/plan.md': 'Launch `a` and TeamCreate\n' });
const green = fixture(base);
try {
let row = rowById(measure(red, registry({ defects: [defect] })), 'defects');
assert.equal(row.open, 1);
assert.equal(row.total, 1);
row = rowById(measure(green, registry({ defects: [defect] })), 'defects');
assert.equal(row.open, 0);
row = rowById(measure(green, registry({ defects: [defect, unfellable] })), 'defects');
assert.equal(row.open, 1, 'a defect without a runnable check stays open');
assert.equal(row.items.find((i) => i.id === 'D-Y').status, 'not-fellable');
} finally { cleanup(red, green); }
});
test('experiments tally: an env-gated opt-in is open until its gate is gone', () => {
const exp = {
id: 'E-X',
summary: 'default-off loop',
probe: 'phrase',
check: [{ path: ['commands/*.md', 'lib/**/*.mjs'], pattern: 'LOOP_ENABLED', expect: 'match' }],
};
const base = { 'STATE.md': '## Open decisions\n', 'agents/a.md': 'x\n', 'commands/r.md': 'Launch `a`\n' };
const red = fixture({ ...base, 'lib/util/cap.mjs': 'if (process.env.LOOP_ENABLED) {}\n' });
const green = fixture({ ...base, 'lib/util/cap.mjs': 'export const x = 1;\n' });
try {
assert.equal(rowById(measure(red, registry({ experiments: [exp] })), 'experiments').open, 1);
assert.equal(rowById(measure(green, registry({ experiments: [exp] })), 'experiments').open, 0);
} finally { cleanup(red, green); }
});
test('dormant-agents tally: an agent no command spawns counts; reference docs never do', () => {
const files = {
'STATE.md': '## Open decisions\n',
'agents/used-agent.md': '---\nname: used-agent\ndescription: |\n does work\n---\n',
'agents/idle-agent.md': '---\nname: idle-agent\ndescription: |\n never wired\n---\n',
'agents/some-orchestrator.md': '---\nname: some-orchestrator\ndescription: Reference document, not a spawnable capability — docs\n---\n',
'commands/run.md': 'Launch the **used-agent** agent.\n',
};
const red = fixture(files);
const green = fixture({ ...files, 'commands/other.md': 'Spawn `idle-agent` in parallel.\n' });
try {
let row = rowById(measure(red, registry()), 'dormant-agents');
assert.equal(row.open, 1);
assert.equal(row.total, 2, 'denominator = spawnable agents (reference docs excluded)');
assert.deepEqual(row.items.filter((i) => i.status === 'open').map((i) => i.id), ['idle-agent']);
row = rowById(measure(green, registry()), 'dormant-agents');
assert.equal(row.open, 0);
} finally { cleanup(red, green); }
});
test('dormant-agents tally: a prose sentence or an HTML comment does not wake a dormant agent', () => {
const files = {
'STATE.md': '## Open decisions\n',
'agents/idle-agent.md': '---\nname: idle-agent\ndescription: |\n never wired\n---\n',
'commands/plan.md': 'The idle-agent is dormant and is never spawned.\n',
'commands/end.md': '<!-- idle-agent -->\nThe research-idle-agents phase.\n',
};
const dir = fixture(files);
try {
assert.equal(rowById(measure(dir, registry()), 'dormant-agents').open, 1);
} finally { cleanup(dir); }
});
test('dormant-agents tally: an unmeasurable agents dir is n/a (null), never 0', () => {
const dir = fixture({ 'STATE.md': '## Open decisions\n', 'commands/r.md': 'x\n' });
try {
const result = measure(dir, registry());
const row = rowById(result, 'dormant-agents');
assert.equal(row.open, null);
assert.equal(row.total, null);
assert.match(row.detail, /not measurable/);
assert.equal(result.green, false);
} finally { cleanup(dir); }
});
test('decisions tally: counts open markers inside the section only, and says it counts ticks', () => {
const state = [
'# STATE',
'- [ ] outside the section, ignored',
'## Open decisions',
'- [ ] first open',
'- [x] already decided',
'- [ ] second open',
'## Next section',
'- [ ] also ignored',
'',
].join('\n');
const red = fixture({ 'STATE.md': state, 'agents/a.md': 'x\n', 'commands/r.md': 'Launch `a`\n' });
const green = fixture({
'STATE.md': '## Open decisions\n- [x] done\n## Next\n',
'agents/a.md': 'x\n',
'commands/r.md': 'Launch `a`\n',
});
try {
let row = rowById(measure(red, registry()), 'decisions');
assert.equal(row.open, 2);
assert.equal(row.total, 3);
assert.match(row.detail, /tick/i, 'the output must say a tick is not verified as a decision');
row = rowById(measure(green, registry()), 'decisions');
assert.equal(row.open, 0);
assert.equal(row.total, 1);
} finally { cleanup(red, green); }
});
test('decisions tally: a missing STATE.md or section is NOT MEASURABLE (null), never 0', () => {
const noState = fixture({ 'agents/a.md': 'x\n', 'commands/r.md': 'Launch `a`\n' });
const noSection = fixture({ 'STATE.md': '# STATE\n- [ ] x\n', 'agents/a.md': 'x\n', 'commands/r.md': 'Launch `a`\n' });
try {
for (const dir of [noState, noSection]) {
const result = measure(dir, registry());
const row = rowById(result, 'decisions');
assert.equal(row.open, null);
assert.ok(row.detail.length > 0, 'the reason is reported');
assert.equal(result.green, false, 'an unmeasurable tally keeps the gate red');
}
} finally { cleanup(noState, noSection); }
});
test('decisions tally: an unmarked list item in the section is format drift → NOT MEASURABLE', () => {
// A STATE written without the marker convention must not read as "0 open".
for (const item of ['1. **Some decision?** prose', '- plain bullet decision', '* star bullet']) {
const dir = fixture({
'STATE.md': `## Open decisions\n- [ ] marked\n${item}\n## Next\n`,
'agents/a.md': 'x\n',
'commands/r.md': 'Launch `a`\n',
});
try {
const row = rowById(measure(dir, registry()), 'decisions');
assert.equal(row.open, null, `unmarked "${item}" must make the tally unmeasurable`);
assert.match(row.detail, /unmarked/);
} finally { cleanup(dir); }
}
});
test('feat-after-freeze tally: not a git repo → n/a (red), never 0', () => {
const dir = fixture({ 'STATE.md': '## Open decisions\n' });
try {
const result = measure(dir, registry());
const row = rowById(result, 'feat-after-freeze');
assert.equal(row.open, null);
assert.match(row.detail, /not measured/);
assert.equal(result.green, false);
} finally { cleanup(dir); }
});
test('feat-after-freeze tally: no freeze tag yet → n/a (red), and the detail names the tag', () => {
const dir = fixture({ 'STATE.md': '## Open decisions\n' });
try {
gitInit(dir);
const row = rowById(measure(dir, registry()), 'feat-after-freeze');
assert.equal(row.open, null);
assert.match(row.detail, /end-state-freeze/);
} finally { cleanup(dir); }
});
test('feat-after-freeze tally: counts feat commits after the tag, 0 when there are none', () => {
const dir = fixture({ 'STATE.md': '## Open decisions\n' });
try {
gitInit(dir);
gitCommit(dir, 'feat: before the freeze does not count');
git(dir, 'tag', 'end-state-freeze');
let row = rowById(measure(dir, registry()), 'feat-after-freeze');
assert.equal(row.open, 0);
assert.equal(row.total, 0);
gitCommit(dir, 'fix(x): a defect fix is allowed');
gitCommit(dir, 'feature: not a conventional feat');
row = rowById(measure(dir, registry()), 'feat-after-freeze');
assert.equal(row.open, 0);
assert.equal(row.total, 2);
gitCommit(dir, 'feat(research): a new capability');
gitCommit(dir, 'feat!: a breaking capability');
row = rowById(measure(dir, registry()), 'feat-after-freeze');
assert.equal(row.open, 2);
assert.equal(row.total, 4);
} finally { cleanup(dir); }
});
// --- registry integrity (the judged party must not own the ledger) ---------
const SAMPLE = registry({
defects: [
{ id: 'D-01', summary: 'first defect', probe: 'phrase', check: [{ path: 'a.md', pattern: 'bad', expect: 'match' }] },
{ id: 'D-02', summary: 'second defect', probe: 'phrase', check: [{ path: 'a.md', pattern: 'worse', expect: 'match' }] },
],
experiments: [
{ id: 'E-01', summary: 'first experiment', probe: 'phrase', check: [{ path: 'a.md', pattern: 'FLAG', expect: 'match' }] },
{ id: 'E-02', summary: 'second experiment', probe: 'phrase', check: [{ path: 'a.md', pattern: 'OPT', expect: 'match' }] },
],
});
const clone = (v) => JSON.parse(JSON.stringify(v));
test('verifyIntegrity: the frozen registry itself is intact; a new entry may be added', () => {
const frozen = frozenFor(SAMPLE);
assert.deepEqual(verifyIntegrity(SAMPLE, frozen), { ok: true, violations: [] });
const grown = clone(SAMPLE);
grown.defects.push({ id: 'D-03', summary: 'found later', probe: 'phrase', check: [{ path: 'a.md', pattern: 'new', expect: 'match' }] });
assert.equal(verifyIntegrity(grown, frozen).ok, true);
});
test('verifyIntegrity: every registry attack is a violation that names what moved', () => {
const frozen = frozenFor(SAMPLE);
const attacks = {
'delete a defect': (r) => { r.defects = r.defects.filter((d) => d.id !== 'D-02'); },
'delete an experiment': (r) => { r.experiments = r.experiments.filter((e) => e.id !== 'E-01'); },
'weaken a defect check': (r) => { r.defects[0].check[0].pattern = '^NEVER MATCHES$'; },
'weaken an experiment check': (r) => { r.experiments[1].check[0].expect = 'no-match'; },
'drop a condition': (r) => { r.defects[0].check = []; },
'point spawnSites at agents': (r) => { r.agents = { ...r.agents, spawnSites: 'agents/*.md' }; },
'change the reference marker': (r) => { r.agents = { ...r.agents, referenceMarker: 'DORMANT' }; },
'move the decisions section': (r) => { r.decisions = { ...r.decisions, section: '## Elsewhere' }; },
'change the open marker': (r) => { r.decisions = { ...r.decisions, open: '^NEVER' }; },
'rename the freeze tag': (r) => { r.freeze = { ...r.freeze, tag: 'some-future-tag' }; },
'duplicate an id': (r) => { r.defects.push(clone(r.defects[0])); },
};
for (const [name, attack] of Object.entries(attacks)) {
const reg = clone(SAMPLE);
attack(reg);
const v = verifyIntegrity(reg, frozen);
assert.equal(v.ok, false, `${name} must be caught`);
assert.ok(v.violations.length > 0 && v.violations.every((s) => typeof s === 'string' && s.length > 0), name);
}
});
test('verifyIntegrity: spawnSites may never include agents/, even if the manifest says so', () => {
const reg = clone(SAMPLE);
reg.agents = { ...reg.agents, spawnSites: 'agents/*.md' };
const v = verifyIntegrity(reg, frozenFor(reg));
assert.equal(v.ok, false);
assert.ok(v.violations.some((s) => /agents\//.test(s)));
});
test('checkSignature: key order does not matter; every field of a condition does', () => {
const a = [{ path: 'a.md', pattern: 'x', flags: 'm', expect: 'match' }];
const reordered = [{ expect: 'match', flags: 'm', pattern: 'x', path: 'a.md' }];
assert.equal(checkSignature(a), checkSignature(reordered));
for (const change of [{ path: 'b.md' }, { pattern: 'y' }, { flags: '' }, { expect: 'no-match' }, { section: '## 1.' }]) {
assert.notEqual(checkSignature([{ ...a[0], ...change }]), checkSignature(a), JSON.stringify(change));
}
assert.match(checkSignature(a), /^[0-9a-f]{64}$/);
});
test('measure: a missing frozen manifest makes integrity n/a and keeps the gate red', () => {
const dir = fixture({ 'STATE.md': '## Open decisions\n' });
try {
const result = measure(dir, registry());
assert.equal(result.integrity.ok, null);
assert.match(result.integrity.detail, /end-state-frozen\.json/);
assert.equal(result.green, false);
} finally { cleanup(dir); }
});
// --- render ----------------------------------------------------------------
test('render: the header counts only tallies that ARE 0, never n/a ones', () => {
const result = {
green: false,
integrity: { ok: true, violations: [], detail: '' },
rows: [
{ id: 'defects', label: 'l', source: 's', open: 0, total: 1, target: 0, items: [], detail: '' },
{ id: 'experiments', label: 'l', source: 's', open: null, total: null, target: 0, items: [], detail: 'not measurable: x' },
{ id: 'dormant-agents', label: 'l', source: 's', open: 0, total: 3, target: 0, items: [], detail: '' },
],
};
const text = render(result);
assert.match(text, /RED \(2 of 3 tallies at 0\)/);
assert.match(text, /\| experiments \| n\/a \| n\/a \|/);
});
test('render: open phrase probes are labelled, closed ids are listed, integrity is shown', () => {
const result = {
green: false,
integrity: { ok: false, violations: ['D-02 removed from the registry'], detail: '' },
rows: [{
id: 'defects', label: 'open pipeline defects', source: 's', open: 1, total: 2, target: 0, detail: '',
items: [
{ id: 'D-01', summary: 'still open', probe: 'phrase', status: 'open', detail: '' },
{ id: 'D-06', summary: 'fixed', probe: 'byte', status: 'closed', detail: '' },
],
}],
};
const text = render(result);
assert.match(text, /\[open · phrase probe\] D-01/);
assert.match(text, /closed: D-06/);
assert.match(text, /registry integrity: VIOLATED/);
assert.match(text, /D-02 removed from the registry/);
});
// --- CLI -------------------------------------------------------------------
function writeGateFiles(dir, reg, frozen = frozenFor(reg)) {
mkdirSync(join(dir, 'scripts'), { recursive: true });
writeFileSync(join(dir, 'scripts', 'end-state-registry.json'), JSON.stringify(reg, null, 2));
mkdirSync(join(dir, 'tests', 'fixtures'), { recursive: true });
writeFileSync(join(dir, 'tests', 'fixtures', 'end-state-frozen.json'), JSON.stringify(frozen, null, 2));
}
function runGate(dir, extra = []) {
return spawnSync(process.execPath, [GATE, '--root', dir, ...extra], { encoding: 'utf8' });
}
const ALL_IDS = ['defects', 'experiments', 'dormant-agents', 'decisions', 'feat-after-freeze'];
test('CLI: all five tallies at 0 with an intact registry → green, exit 0; --json carries the rows', () => {
const dir = fixture({
'STATE.md': '## Open decisions\n- [x] done\n',
'agents/a.md': '---\nname: a\n---\n',
'commands/r.md': 'Spawn `a`\n',
});
try {
writeGateFiles(dir, registry());
gitInit(dir);
git(dir, 'tag', 'end-state-freeze');
const r = runGate(dir, ['--json']);
assert.equal(r.status, 0, r.stdout + r.stderr);
const out = JSON.parse(r.stdout);
assert.equal(out.green, true);
assert.equal(out.integrity.ok, true);
assert.deepEqual(out.rows.map((x) => x.id), ALL_IDS);
assert.ok(out.rows.every((x) => x.open === 0 && x.target === 0 && typeof x.source === 'string'));
} finally { cleanup(dir); }
});
test('CLI: an intact registry with every measurable tally at 0 but no freeze tag → red, exit 1', () => {
// n/a must never be read as 0 — also when nothing else is wrong.
const dir = fixture({
'STATE.md': '## Open decisions\n- [x] done\n',
'agents/a.md': '---\nname: a\n---\n',
'commands/r.md': 'Spawn `a`\n',
});
try {
writeGateFiles(dir, registry());
gitInit(dir);
const r = runGate(dir, ['--json']);
assert.equal(r.status, 1, r.stdout + r.stderr);
const out = JSON.parse(r.stdout);
assert.equal(out.integrity.ok, true);
assert.equal(out.green, false);
assert.equal(out.rows.find((x) => x.id === 'feat-after-freeze').open, null);
assert.ok(out.rows.filter((x) => x.id !== 'feat-after-freeze').every((x) => x.open === 0));
} finally { cleanup(dir); }
});
test('CLI: all tallies at 0 but a tampered registry → red, exit 1 (the combined green attack)', () => {
const dir = fixture({
'STATE.md': '## Open decisions\n- [x] ticked\n',
'agents/a.md': '---\nname: a\n---\n',
'commands/r.md': 'Spawn `a`\n',
});
try {
const honest = registry({ defects: [{ id: 'D-01', summary: 'a real defect', probe: 'phrase', check: [{ path: 'commands/r.md', pattern: 'Spawn', expect: 'match' }] }] });
const tampered = registry(); // the open defect simply left the ledger
writeGateFiles(dir, tampered, frozenFor(honest));
gitInit(dir);
git(dir, 'tag', 'end-state-freeze');
const r = runGate(dir);
assert.equal(r.status, 1, r.stdout + r.stderr);
assert.match(r.stdout, /RED/);
assert.match(r.stdout, /VIOLATED/);
assert.match(r.stdout, /D-01/);
} finally { cleanup(dir); }
});
test('CLI: any tally > 0 → red, exit 1; the table names every row', () => {
const dir = fixture({
'STATE.md': '## Open decisions\n- [ ] undecided\n',
'agents/a.md': '---\nname: a\n---\n',
'commands/r.md': 'Spawn `a`\n',
});
try {
writeGateFiles(dir, registry());
const r = runGate(dir);
assert.equal(r.status, 1, r.stderr);
for (const label of ALL_IDS) assert.match(r.stdout, new RegExp(label));
assert.match(r.stdout, /RED/);
} finally { cleanup(dir); }
});
test('CLI: an unreadable registry is a usage error (exit 2), not a verdict', () => {
const dir = fixture({ 'STATE.md': '## Open decisions\n' });
try {
const r = runGate(dir);
assert.equal(r.status, 2);
assert.match(r.stderr, /end-state-registry\.json/);
} finally { cleanup(dir); }
});
// --- the real repo: denominator pinned, state never pinned -----------------
test('the frozen denominator: tests/fixtures/end-state-frozen.json is exactly the literal above', () => {
const file = loadFrozen(ROOT);
const { why, ...rest } = file;
assert.ok(typeof why === 'string' && why.length > 20, 'the manifest says why it is frozen');
assert.deepEqual(rest, FROZEN);
});
test('the frozen denominator: the real registry is intact against it', () => {
const v = verifyIntegrity(loadRegistry(ROOT), FROZEN);
assert.deepEqual(v, { ok: true, violations: [] }, v.violations.join('\n'));
});
test('real registry: every counted entry has an id, a summary, a probe kind and a check that RUNS here', () => {
const reg = loadRegistry(ROOT);
const entries = [...reg.defects, ...reg.experiments];
const ids = new Set();
for (const e of entries) {
assert.match(e.id, /^[DE]-\d{2}$/, `bad id ${e.id}`);
assert.ok(!ids.has(e.id), `duplicate id ${e.id}`);
ids.add(e.id);
assert.ok(typeof e.summary === 'string' && e.summary.length > 10, `${e.id} needs a summary`);
assert.ok(Object.keys(PROBE_NOTES).includes(e.probe), `${e.id} must declare a probe kind the gate can explain`);
// A check that cannot evaluate against this very repo is a typo, not a finding.
assert.notEqual(evaluateCheck(ROOT, e.check).status, 'not-fellable', `${e.id} check does not run here`);
}
});
test('real repo: five rows, open is a count or null, exit code agrees with rows + integrity', () => {
const r = spawnSync(process.execPath, [GATE, '--json'], { encoding: 'utf8', cwd: ROOT });
assert.ok(r.status === 0 || r.status === 1, r.stderr);
const out = JSON.parse(r.stdout);
assert.deepEqual(out.rows.map((x) => x.id), ALL_IDS);
for (const row of out.rows) {
assert.ok(row.open === null || (Number.isInteger(row.open) && row.open >= 0), `${row.id}: ${row.open}`);
}
const green = out.rows.every((row) => row.open === 0) && out.integrity.ok === true;
assert.equal(out.green, green);
assert.equal(r.status, green ? 0 : 1);
assert.ok(existsSync(join(ROOT, 'scripts', 'end-state-registry.json')));
assert.ok(readFileSync(join(ROOT, 'tests', 'fixtures', 'end-state-frozen.json'), 'utf8').length > 0);
});
// --- probe labelling -------------------------------------------------------
//
// The gate labelled only `phrase` probes, so every other probe kind rendered
// unlabelled — indistinguishable from an entry that declared nothing. Once
// D-03/D-04 became behaviour probes (operator decision 2026-09-18) the reader
// needs to see WHICH kind of evidence each closed entry rests on.
test('PROBE_NOTES: every probe kind the registry may declare carries a note', () => {
assert.deepEqual(Object.keys(PROBE_NOTES).sort(), ['behaviour', 'byte', 'phrase']);
for (const [kind, note] of Object.entries(PROBE_NOTES)) {
assert.ok(note.length > 20, `${kind} needs a note that says what the probe can and cannot prove`);
}
});
test('render: every probe kind is labelled, open and closed — not just phrase', () => {
const text = render({
green: false,
integrity: { ok: true, violations: [], detail: '' },
rows: [{
id: 'defects', label: 'open pipeline defects', source: 's', open: 2, total: 3, target: 0, detail: '',
items: [
{ id: 'D-01', summary: 'still open', probe: 'phrase', status: 'open', detail: '' },
{ id: 'D-03', summary: 'still open', probe: 'behaviour', status: 'open', detail: '' },
{ id: 'D-06', summary: 'fixed', probe: 'byte', status: 'closed', detail: '' },
],
}],
});
assert.match(text, /\[open · phrase probe\] D-01/);
assert.match(text, /\[open · behaviour probe\] D-03/);
assert.match(text, /closed: D-06 \(byte probe\)/);
});
test('the detail line carries one note per probe kind PRESENT, and none for kinds absent', () => {
const dir = fixture({
'a.md': 'bad\n',
'STATE.md': '## Open decisions\n- [x] done\n',
'agents/a.md': '---\nname: a\n---\n',
'commands/r.md': 'Spawn `a`\n',
});
try {
writeGateFiles(dir, registry({
defects: [
{ id: 'D-01', summary: 'a phrase defect', probe: 'phrase', check: [{ path: 'a.md', pattern: 'bad', expect: 'match' }] },
{ id: 'D-02', summary: 'a behaviour defect', probe: 'behaviour', check: [{ path: 'a.md', pattern: 'nope', expect: 'match' }] },
],
}));
gitInit(dir);
const out = JSON.parse(runGate(dir, ['--json']).stdout);
const detail = rowById(out, 'defects').detail;
assert.match(detail, /phrase probe closes on rewording/);
assert.match(detail, /behaviour probe/);
assert.ok(!/byte probe/.test(detail), 'a kind no entry declares must not be explained');
assert.equal(rowById(out, 'experiments').detail, '', 'an empty row explains nothing');
} finally { cleanup(dir); }
});
// --- a probe that RUNS a test, and the M6 mutant ---------------------------
//
// Measured 2026-09-18 (re-measured on 6cafb4c): a two-line file
// export function runPlanVerification() { return { ok: true }; }
// export function formatCriteriaEvidence() { return ''; }
// dropped into an otherwise UNFIXED tree closed both D-03 and D-04, and the
// gate reported "defects 0 of 7, registry intact". A grep condition can only
// ask whether a string is present, so a behaviour probe built from one is a
// phrase probe pointed at code. A condition that RUNS a named test and reads
// its TAP result cannot be closed that way.
const M6_STUB = [
'export function runPlanVerification() { return { ok: true }; }',
"export function formatCriteriaEvidence() { return ''; }",
'',
].join('\n');
// A fixture pair: a module, and a test that BINDS it. `green one` passes on
// the tree and fails against a stub of the module - which is what makes it a
// behaviour probe rather than a name.
const MODULE = 'export function answer() { return 42; }\n';
const TEST_FILE = [
"import { test } from 'node:test';",
"import { strict as assert } from 'node:assert';",
"import { answer } from '../lib/m.mjs';",
"test('green one', () => { assert.equal(answer(), 42); });",
"test('red one', () => { assert.ok(false, 'red on purpose'); });",
'',
].join('\n');
test('a test condition RUNS the named test and reads its result', () => {
const dir = fixture({ 'tests/t.test.mjs': TEST_FILE, 'lib/m.mjs': MODULE });
try {
const cond = (name, expect) => ({ test: 'tests/t.test.mjs', name, expect, stubs: ['lib/m.mjs'] });
assert.equal(evaluateCondition(dir, cond('green one', 'passes')), true);
assert.equal(evaluateCondition(dir, cond('green one', 'fails')), false);
assert.equal(evaluateCondition(dir, cond('red one', 'passes')), false);
assert.equal(evaluateCondition(dir, cond('red one', 'fails')), true);
} finally { cleanup(dir); }
});
test('a test condition naming a test that does not run is NOT FELLABLE, never closed', () => {
const dir = fixture({ 'tests/t.test.mjs': TEST_FILE, 'lib/m.mjs': MODULE });
try {
for (const cond of [
{ test: 'tests/t.test.mjs', name: 'no such test', expect: 'fails', stubs: ['lib/m.mjs'] },
{ test: 'tests/missing.test.mjs', name: 'green one', expect: 'fails', stubs: ['lib/m.mjs'] },
]) {
const r = evaluateCheck(dir, [cond]);
assert.equal(r.status, 'not-fellable', JSON.stringify(cond));
assert.ok(r.detail.length > 0, 'the gate must say WHY the check could not fire');
}
} finally { cleanup(dir); }
});
test('M6 mutant: a stub exporting both symbols does NOT close D-03 or D-04', () => {
const dir = fixture({ 'lib/cosmetic/stub.mjs': M6_STUB });
try {
const entries = loadRegistry(ROOT).defects.filter((e) => e.id === 'D-03' || e.id === 'D-04');
assert.equal(entries.length, 2, 'D-03 and D-04 are still in the registry');
for (const e of entries) {
assert.notEqual(
evaluateCheck(dir, e.check).status, 'closed',
`${e.id} closed on a tree where nothing is fixed: ${JSON.stringify(e.check)}`,
);
}
} finally { cleanup(dir); }
});
test('the gate states out loud that the wiring is pinned by text, not proven', () => {
const r = spawnSync(process.execPath, [GATE], { encoding: 'utf8', cwd: ROOT });
assert.ok(r.status === 0 || r.status === 1, r.stderr);
assert.match(r.stdout, /wiring: /);
assert.match(r.stdout, /pinned by TEXT/);
assert.match(r.stdout, /plugin-eval/);
});
// --- M7: a probe must bind the test's CONTENT, not its name ---------------
//
// Measured 2026-09-18 (re-measured on e1e7bdf): the cosmetic close came back
// one level up. The probe reads the test file from the SAME tree it measures,
// so a four-line file holding two EMPTY tests with the two named names —
// test('runPlanVerification: a plan whose success criterion FAILS fells the run', () => {});
// test('formatCriteriaEvidence: one row per criterion, with command and exit code', () => {});
// — closed D-03 and D-04 on a tree where `lib/verification/` did not exist at
// all: "defects 0 of 7, registry intact". An empty test is the new two-line
// stub. The only form that cannot be closed by a name is one that requires the
// named test to FELL a mutant: the gate runs it a second time against a tree
// where the module it binds is replaced by a stub exporting the same names and
// doing nothing. A test that passes against both is not a behaviour probe.
const EMPTY_NAMED_TESTS = [
"import test from 'node:test';",
"test('runPlanVerification: a plan whose success criterion FAILS fells the run', () => {});",
"test('formatCriteriaEvidence: one row per criterion, with command and exit code', () => {});",
'',
].join('\n');
const REAL_RUNNER = join(ROOT, 'lib', 'verification', 'criteria-runner.mjs');
const REAL_RUNNER_TEST = join(ROOT, 'tests', 'lib', 'criteria-runner.test.mjs');
test('M7 mutant: two EMPTY tests with the right names do NOT close D-03 or D-04', () => {
// The checkpoint's reproduction: an unfixed tree (no lib/verification/) plus
// the four-line test file.
const dir = fixture({ 'tests/lib/criteria-runner.test.mjs': EMPTY_NAMED_TESTS });
try {
const entries = loadRegistry(ROOT).defects.filter((e) => e.id === 'D-03' || e.id === 'D-04');
assert.equal(entries.length, 2, 'D-03 and D-04 are still in the registry');
for (const e of entries) {
const r = evaluateCheck(dir, e.check);
assert.equal(r.status, 'not-fellable', `${e.id} closed on a tree where nothing is fixed`);
assert.ok(r.detail.length > 0, 'the gate must say WHY the check could not fire');
}
} finally { cleanup(dir); }
});
test('M7 mutant: an empty named test does not close the defect even when the module IS present', () => {
// The harder case: the real module is there, so the stub CAN be built — and
// the empty test passes against it, which is exactly the proof that the test
// binds the name and nothing else.
const dir = fixture({
'tests/lib/criteria-runner.test.mjs': EMPTY_NAMED_TESTS,
'lib/verification/criteria-runner.mjs': readFileSync(REAL_RUNNER, 'utf8'),
});
try {
for (const e of loadRegistry(ROOT).defects.filter((x) => x.id === 'D-03' || x.id === 'D-04')) {
const r = evaluateCheck(dir, e.check);
assert.equal(r.status, 'not-fellable', `${e.id} closed on an empty test`);
assert.match(r.detail, /stub/i, 'the gate must name the mutant the test failed to fell');
}
} finally { cleanup(dir); }
});
test("D-03/D-04 still CLOSE on the real tree — the named tests do fell a stub", () => {
// The positive control. Without it, "not closed" everywhere would read as a
// working probe when the probe is simply broken.
for (const e of loadRegistry(ROOT).defects.filter((x) => x.id === 'D-03' || x.id === 'D-04')) {
const r = evaluateCheck(ROOT, e.check);
assert.equal(r.status, 'closed', `${e.id} is not closed on the real tree: ${r.detail}`);
}
});
test('a test condition must name the module its test binds, or it cannot fire', () => {
const dir = fixture({ 'tests/t.test.mjs': TEST_FILE, 'lib/m.mjs': MODULE });
try {
for (const cond of [
{ test: 'tests/t.test.mjs', name: 'green one', expect: 'passes' },
{ test: 'tests/t.test.mjs', name: 'green one', expect: 'passes', stubs: [] },
{ test: 'tests/t.test.mjs', name: 'green one', expect: 'passes', stubs: ['lib/gone.mjs'] },
]) {
const r = evaluateCheck(dir, [cond]);
assert.equal(r.status, 'not-fellable', JSON.stringify(cond));
assert.ok(r.detail.length > 0, 'the gate must say WHY');
}
} finally { cleanup(dir); }
});
test('the stub mutant leaves the measured tree untouched', () => {
const dir = fixture({ 'tests/t.test.mjs': TEST_FILE, 'lib/m.mjs': MODULE });
try {
const before = readFileSync(join(dir, 'lib', 'm.mjs'), 'utf8');
evaluateCheck(dir, [{ test: 'tests/t.test.mjs', name: 'green one', expect: 'passes', stubs: ['lib/m.mjs'] }]);
assert.equal(readFileSync(join(dir, 'lib', 'm.mjs'), 'utf8'), before, 'the mutation must happen in a copy, never in place');
} finally { cleanup(dir); }
});
test('the frozen file claims no authority it does not have', () => {
// Measured the same day: the tracked frozen file claimed its second
// amendment was operator-authorised. No operator authorised it; it followed
// from a maintenance decision. A file whose whole job is to be an explicit
// decision trail may not overclaim in either direction.
const why = loadFrozen(ROOT).why;
assert.ok(why.length > 0, 'the frozen manifest must say WHY it changed');
assert.ok(
!/operator-authoris|operator-authoriz|by work order|PM checkpoint/i.test(why),
'the frozen file may not claim operator authorisation, and coordination metadata belongs in the local plan',
);
});

View file

@ -13,7 +13,7 @@
// - a stats file with no `effort` field is a loud error, never a silently
// empty group that reads as "no gain".
//
// Pattern: tests/scripts/synthesis-measure.test.mjs (pure core, no fixtures on disk).
// Pure core, no fixtures on disk.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
@ -248,7 +248,7 @@ test('decideVerdict: a null gain is insufficient data, never a decline', () => {
// appends; nothing read it.
//
// The stats record carries counts, not names — names are free prose and
// field-allowlist.mjs denies prose by omission — so the run attests membership
// the record denies prose by omission — so the run attests membership
// with a low-cardinality boolean instead, and the gate refuses to call
// activation OK without it.
// ---------------------------------------------------------------------------

View file

@ -1,101 +0,0 @@
// tests/scripts/synthesis-measure.test.mjs
// NW3 (S12) — deterministic Δ main-context measurement core.
//
// The gate metric (T1 §2) is Δ main-context tokens for an equivalent-quality
// digest. This pins the pure accounting that turns fixture token counts into a
// verdict, under the two framings that decide NW3:
//
// - FAITHFUL (current flow): Phase 5 swarm runs FOREGROUND, so its outputs are
// already RESIDENT in main before Phase 7. Delegating only the synthesis read
// cannot evict them → main holds base+out+dig in BOTH arms → Δ ≈ 0.
// - DISK-POTENTIAL (upper bound): IF outputs were on disk (a separate Phase-5
// change, out of NW3 scope), the delegated arm holds base+dig only → Δ = out/(base+out+dig).
//
// POSITIVE adopt requires Δ ≥ 30% AND quality ≥ inline (T1 §5 thresholds).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import {
estimateTokens,
mainContextTokens,
deltaPct,
decideVerdict,
analyze,
} from '../../scripts/synthesis-measure.mjs';
test('estimateTokens: ~chars/4 heuristic, monotonic, non-negative', () => {
assert.equal(estimateTokens(''), 0);
assert.equal(estimateTokens('abcd'), 1);
assert.equal(estimateTokens('abcde'), 2); // ceil(5/4)
assert.ok(estimateTokens('a'.repeat(400)) === 100);
assert.ok(estimateTokens('x'.repeat(1000)) > estimateTokens('x'.repeat(500)));
});
test('mainContextTokens: inline arm holds base + out + dig', () => {
assert.equal(mainContextTokens({ base: 50000, out: 12000, dig: 1500, arm: 'inline' }), 63500);
});
test('mainContextTokens: delegated_faithful still holds out (Phase-5 resident) + dig', () => {
// The decisive structural fact: foreground swarm delivery already made `out`
// resident; delegating Phase 7 does not evict it.
assert.equal(
mainContextTokens({ base: 50000, out: 12000, dig: 1500, arm: 'delegated_faithful' }),
63500,
);
});
test('mainContextTokens: delegated_disk holds base + dig only (out lives in the sub-agent)', () => {
assert.equal(
mainContextTokens({ base: 50000, out: 12000, dig: 1500, arm: 'delegated_disk' }),
51500,
);
});
test('mainContextTokens: unknown arm throws (no silent default)', () => {
assert.throws(() => mainContextTokens({ base: 1, out: 1, dig: 1, arm: 'nope' }));
});
test('deltaPct: (A-B)/A; equal arms → 0; B smaller → positive fraction', () => {
assert.equal(deltaPct(100, 100), 0);
assert.equal(deltaPct(100, 75), 0.25);
assert.equal(deltaPct(0, 0), 0); // guard divide-by-zero
});
test('decideVerdict: ≥30% AND quality-ok → POSITIVE', () => {
assert.equal(decideVerdict(0.30, true), 'POSITIVE');
assert.equal(decideVerdict(0.45, true), 'POSITIVE');
});
test('decideVerdict: quality loss forces NEGATIVE even at a large Δ', () => {
assert.equal(decideVerdict(0.60, false), 'NEGATIVE');
});
test('decideVerdict: <15% → NEGATIVE; the [15%,30%) band → INCONCLUSIVE', () => {
assert.equal(decideVerdict(0.149, true), 'NEGATIVE');
assert.equal(decideVerdict(0.00, true), 'NEGATIVE');
assert.equal(decideVerdict(0.15, true), 'INCONCLUSIVE');
assert.equal(decideVerdict(0.2999, true), 'INCONCLUSIVE');
});
test('analyze: faithful arm is structurally Δ=0 → NEGATIVE regardless of sizes', () => {
const a = analyze({ baseTokens: 50000, outTokens: 12000, digTokens: 1500, qualityOK: true });
assert.equal(a.faithful.deltaPct, 0);
assert.equal(a.faithful.verdict, 'NEGATIVE');
assert.equal(a.faithful.armA, a.faithful.armB);
});
test('analyze: disk arm Δ = out/(base+out+dig)', () => {
const a = analyze({ baseTokens: 50000, outTokens: 12000, digTokens: 1500, qualityOK: true });
const expected = 12000 / (50000 + 12000 + 1500);
assert.ok(Math.abs(a.disk.deltaPct - expected) < 1e-9);
assert.equal(a.disk.armB, 51500);
});
test('analyze: disk verdict tracks BASE — large fixed baseline can sink the upper bound below 30%', () => {
// Small base → disk Δ clears 30%; large base → it does not. Demonstrates the
// upper bound is itself BASE-sensitive (sweep, do not assert one number).
const small = analyze({ baseTokens: 10000, outTokens: 12000, digTokens: 1500, qualityOK: true });
const large = analyze({ baseTokens: 200000, outTokens: 12000, digTokens: 1500, qualityOK: true });
assert.equal(small.disk.verdict, 'POSITIVE'); // 12000/23500 ≈ 0.51
assert.equal(large.disk.verdict, 'NEGATIVE'); // 12000/213500 ≈ 0.056
});

View file

@ -1,660 +0,0 @@
// tests/scripts/yardstick.test.mjs
// The yardstick prints Voyage's three numbers (use · trust · interrupts) with
// denominators read from their SOURCE, plus a fourth row that asks whether the
// end-state gate itself can be fooled by a signature-preserving mutant.
//
// Like the gate's own test, these tests pin the MECHANICS in both directions
// against throwaway fixtures - every number can be countable and can be not
// countable, the mutant row can read felled and not felled. They deliberately
// do NOT assert that the real data is red today: the yardstick is supposed to
// turn green, and a test that pins today's distance would fail on the day the
// work is done. Nothing here reads the real order queue or the real stats.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, symlinkSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { join, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
DEFAULT_CUTOFF,
MARKER_SINCE,
loadStats,
readOrders,
measureUse,
measureTrust,
measureInterrupts,
emitterCallSites,
buildM9Tree,
measureMutants,
measureAll,
render,
main,
} from '../../scripts/yardstick.mjs';
// New exports are read through the namespace, so a missing one fails its own
// tests instead of failing this whole file at link time.
import * as Y from '../../scripts/yardstick.mjs';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
// --- helpers ---------------------------------------------------------------
function fixture(files) {
const dir = mkdtempSync(join(tmpdir(), 'yardstick-'));
for (const [rel, body] of Object.entries(files)) {
const p = join(dir, rel);
mkdirSync(dirname(p), { recursive: true });
writeFileSync(p, body);
}
return dir;
}
const cleanup = (...dirs) => { for (const d of dirs) rmSync(d, { recursive: true, force: true }); };
const jsonl = (...rows) => `${rows.map((r) => (typeof r === 'string' ? r : JSON.stringify(r))).join('\n')}\n`;
// `marker` writes the order's solution-choice marker the way dispatch does: an
// HTML comment on its own line in the body, `<!-- loesningsvalg: <value>; grunn=... -->`.
// `arbeidsform` writes `<!-- arbeidsform: <value> -->` on its own line, as dispatch does.
function order({ id, date, workClass, slug, marker, arbeidsform, extra = '' }) {
const lines = ['---', 'from: .claude', 'to: some-repo', `order-id: ${id}`, `subject: order ${id}`, `date: ${date}`];
if (workClass !== undefined) lines.push(`work-class: ${workClass}`);
if (slug !== undefined) lines.push(`slug: ${slug}`);
lines.push('---');
if (arbeidsform !== undefined) lines.push(`<!-- arbeidsform: ${arbeidsform} -->`);
if (marker !== undefined) lines.push(`<!-- loesningsvalg: ${marker}; grunn=fixture -->`);
lines.push('', `# ORDER ${id}`, extra, '');
return lines.join('\n');
}
const review = (ts, verdict, slug = 's') => ({ ts, slug, verdict });
// A /trekbrief stats record naming the order it ran (the writer adds order_*).
const briefRun = ({ order_id, slug = 's', ts = '2026-10-04T10:00:00Z' }) => ({ ts, slug, order_id, order_claimed_at: ts, order_reason: order_id ? null : 'none-claimed' });
// The runs index the yardstick joins on, built from stats files `{ name: [records] }`.
function runsFrom(files = {}) {
const dir = fixture(Object.fromEntries(Object.entries(files).map(([n, recs]) => [n, jsonl(...recs)])));
try { return Y.orderRuns(loadStats(dir)); } finally { cleanup(dir); }
}
const runsOf = (...records) => runsFrom(records.length ? { 'trekbrief-stats.jsonl': records } : {});
// measureUse over a fixture queue; counting starts at MARKER_SINCE unless the test says otherwise.
function use(files, { records = [], runs, cutoff = DEFAULT_CUTOFF, countingSince = MARKER_SINCE } = {}) {
const coord = fixture(files);
try {
return measureUse({ queue: readOrders(coord), runs: runs ?? runsOf(...records), cutoff, countingSince });
} finally { cleanup(coord); }
}
// --- the stats loader --------------------------------------------------------
test('loadStats: an invalid JSON line is COUNTED, never silently dropped', () => {
const dir = fixture({
'trekreview-stats.jsonl': jsonl(review('2026-09-01T00:00:00Z', 'ALLOW'), '{not json', '', review('2026-09-02T00:00:00Z', 'BLOCK')),
});
try {
const stats = loadStats(dir);
const f = stats.files.find((x) => x.name === 'trekreview-stats.jsonl');
assert.equal(f.lines, 3, 'blank lines are not lines; the invalid one is');
assert.equal(f.records.length, 2);
assert.deepEqual(f.invalid, [2], 'the invalid line is reported by its line number');
} finally { cleanup(dir); }
});
test('loadStats: a missing data directory is reported, not read as zero', () => {
const stats = loadStats(join(tmpdir(), 'yardstick-no-such-dir-8f3a'));
assert.equal(stats.missing, true);
assert.deepEqual(stats.files, []);
});
// --- 2 · TRUST ---------------------------------------------------------------
test('trust: the ALLOW streak is read in ts order, not file order', () => {
// File order ends on ALLOW, ALLOW; chronologically the last review is a BLOCK.
const dir = fixture({
'trekreview-stats.jsonl': jsonl(
review('2026-09-01T00:00:00Z', 'ALLOW'),
review('2026-09-05T00:00:00Z', 'BLOCK'),
review('2026-09-02T00:00:00Z', 'ALLOW'),
review('2026-09-03T00:00:00Z', 'ALLOW'),
),
});
try {
const row = measureTrust(loadStats(dir));
assert.equal(row.countable, true);
assert.equal(row.denominator, 4);
assert.equal(row.value, 0, 'the chronologically last review is a BLOCK');
assert.equal(row.facts.longestStreak, 3);
assert.deepEqual(row.facts.verdicts, { ALLOW: 3, BLOCK: 1 });
assert.equal(row.facts.last.verdict, 'BLOCK');
} finally { cleanup(dir); }
});
test('trust: both spacings of the verdict field count the same (it is read as JSON, not grepped)', () => {
const dir = fixture({
'trekreview-stats.jsonl': jsonl(
'{"ts": "2026-09-01T00:00:00Z", "slug": "a", "verdict": "BLOCK"}',
'{"ts":"2026-09-02T00:00:00Z","slug":"b","verdict":"ALLOW"}',
),
});
try {
const row = measureTrust(loadStats(dir));
assert.deepEqual(row.facts.verdicts, { BLOCK: 1, ALLOW: 1 });
assert.equal(row.value, 1);
} finally { cleanup(dir); }
});
test('trust: an invalid line is left out of n and said out loud', () => {
const dir = fixture({
'trekreview-stats.jsonl': jsonl(review('2026-09-01T00:00:00Z', 'ALLOW'), '{"ts": oops'),
});
try {
const row = measureTrust(loadStats(dir));
assert.equal(row.denominator, 1);
assert.equal(row.facts.invalid, 1);
assert.match(row.detail, /1 invalid line/);
} finally { cleanup(dir); }
});
test('trust: an empty or missing review file is NOT countable', () => {
const empty = fixture({ 'trekreview-stats.jsonl': '' });
const none = fixture({ 'trekplan-stats.jsonl': jsonl({ ts: '2026-09-01T00:00:00Z', slug: 'x' }) });
try {
for (const dir of [empty, none]) {
const row = measureTrust(loadStats(dir));
assert.equal(row.countable, false, dir);
assert.equal(row.value, null);
assert.ok(row.detail.length > 0, 'the row must say WHY it cannot be counted');
}
} finally { cleanup(empty, none); }
});
// --- 1 · USE -----------------------------------------------------------------
test('use: orders are read from orders/, archive/ and claimed/, and nothing else', () => {
const coord = fixture({
'repo-a/orders/o1.md': order({ id: 'o1', date: '2026-09-22T10:00:00Z' }),
'repo-a/orders/archive/o2.md': order({ id: 'o2', date: '2026-09-01T10:00:00Z' }),
'repo-b/orders/claimed/o3.md': order({ id: 'o3', date: '2026-09-22T10:00:00Z' }),
'repo-b/orders/claimed/o3.claim': 'claimed',
'repo-b/inbox/m1.md': order({ id: 'm1', date: '2026-09-22T10:00:00Z' }),
'repo-b/orders/broken.md': 'no frontmatter at all\n',
});
try {
const q = readOrders(coord);
assert.equal(q.files, 4, 'three orders and one unreadable file, all .md under */orders/');
assert.equal(q.orders.length, 3);
assert.equal(q.unreadable.length, 1);
} finally { cleanup(coord); }
});
test('use: a dot-mailbox is a mailbox - its orders count, and the detail names it', () => {
// Measured 2026-09-22: a hand count over `coord/*/orders` came out lower than
// the yardstick, because the shell's `*` skips dot-directories and the queue
// does not. The detail names them, so the next re-measure can reconcile.
const coord = fixture({
'r/orders/o1.md': order({ id: 'o1', date: '2026-09-22T10:00:00Z' }),
'.pm/orders/archive/o2.md': order({ id: 'o2', date: '2026-09-22T10:00:00Z' }),
});
try {
const q = readOrders(coord);
assert.equal(q.orders.length, 2);
const row = measureUse({ queue: q, runs: runsOf(), cutoff: DEFAULT_CUTOFF, countingSince: MARKER_SINCE });
assert.match(row.detail, /2 order files in 2 mailboxes/);
assert.match(row.detail, /dot-mailboxes .pm 1/);
} finally { cleanup(coord); }
});
test('use: the marker is counted from 2026-09-28, the day pre-order-loesningsvalg.sh began enforcing it', () => {
assert.equal(MARKER_SINCE, '2026-09-28');
});
test('use: no order carrying a loesningsvalg marker means NOT COUNTABLE, not 0 %', () => {
const row = use({
'r/orders/o1.md': order({ id: 'o1', date: '2026-09-29T10:00:00Z' }),
'r/orders/o2.md': order({ id: 'o2', date: '2026-09-30T10:00:00Z' }),
});
assert.equal(row.countable, false);
assert.equal(row.value, null);
assert.equal(row.denominator, 0);
assert.equal(row.facts.scanned, 2);
assert.equal(row.facts.withMarker, 0);
assert.equal(row.facts.notMeasured, 2, 'an unmarked order is not measured - never a no');
assert.match(row.detail, /0 of 2 orders carry a loesningsvalg marker/);
assert.match(row.detail, /2 not measured/);
});
test('use: plan and prove are NEW work, ikke-nytt is not; unmarked and pre-2026-09-28 orders are not measured', () => {
const row = use({
'r/orders/plan.md': order({ id: 'plan', date: '2026-09-28T00:00:00Z', marker: 'plan' }),
'r/orders/archive/prove.md': order({ id: 'prove', date: '2026-10-01T00:00:00Z', marker: 'prove' }),
'r/orders/claimed/ikke.md': order({ id: 'ikke', date: '2026-10-01T00:00:00Z', marker: 'ikke-nytt' }),
'r/orders/none.md': order({ id: 'none', date: '2026-10-01T00:00:00Z' }),
'r/orders/early.md': order({ id: 'early', date: '2026-09-27T23:59:59Z', marker: 'plan' }),
}, { records: [briefRun({ order_id: 'plan' }), briefRun({ order_id: 'ikke', slug: 'other' }), briefRun({ order_id: 'early', slug: 'old' })] });
assert.equal(row.facts.newWork, 2, 'plan and prove');
assert.equal(row.facts.notNew, 1);
assert.equal(row.facts.beforeMarker, 1);
assert.equal(row.facts.unmarked, 1);
assert.equal(row.facts.withVoyageRun, 1, 'plan has a trekbrief record naming it');
assert.equal(row.facts.noRecord, 1, 'prove has none - not measured, never a no');
assert.equal(row.countable, true);
assert.equal(row.value, 1);
assert.equal(row.denominator, 1, 'YES + declared NO; prove is not measured');
assert.equal(row.facts.notMeasured, 3, 'unmarked, before 2026-09-28, and prove without a record');
assert.match(row.detail, /value = 1 of 1 measured/);
});
test('use: work-class in the frontmatter no longer decides anything - only the marker does', () => {
const row = use({
'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', workClass: 'new' }),
'r/orders/b.md': order({ id: 'b', date: '2026-09-29T00:00:00Z', workClass: 'maintenance', marker: 'prove' }),
}, { records: [briefRun({ order_id: 'a' }), briefRun({ order_id: 'b', slug: 'b' })] });
assert.equal(row.facts.newWork, 1, 'b is new by its marker; a has no marker and is not measured');
assert.equal(row.value, 1);
assert.equal(row.denominator, 1);
assert.equal(row.facts.notMeasured, 1);
});
test('use: only a marker on its own line counts - a mention in prose or an unknown value is not measured', () => {
const row = use({
'r/orders/prose.md': order({ id: 'prose', date: '2026-09-29T00:00:00Z', extra: 'Count from the marker `<!-- loesningsvalg: plan -->` in the body.' }),
'r/orders/odd.md': order({ id: 'odd', date: '2026-09-29T00:00:00Z', marker: 'kanskje' }),
'r/orders/both.md': order({ id: 'both', date: '2026-09-29T00:00:00Z', marker: 'ikke-nytt', extra: 'Prose names `<!-- loesningsvalg: plan -->` too.' }),
});
assert.equal(row.denominator, 0);
assert.equal(row.facts.notNew, 1, 'both: the line marker wins over the prose mention');
assert.equal(row.facts.notMeasured, 2, 'prose has no marker; kanskje is no known value');
assert.equal(row.countable, false);
});
test('use: a cutoff later than 2026-09-28 moves the start of the count', () => {
const row = use({
'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/b.md': order({ id: 'b', date: '2026-10-02T00:00:00Z', marker: 'plan' }),
}, { cutoff: '2026-10-01', records: [briefRun({ order_id: 'a' }), briefRun({ order_id: 'b', slug: 'b' })] });
assert.equal(row.facts.newWork, 1);
assert.equal(row.facts.beforeMarker, 1);
assert.equal(row.value, 1, 'b only');
assert.match(row.source, /from 2026-10-01/);
});
// --- use: the order-id join (source C) -------------------------------------------
test('use: a new-work order is YES only when a trekbrief record names its order id', () => {
const row = use({ 'r/orders/o.md': order({ id: 'o', date: '2026-09-29T00:00:00Z', marker: 'plan' }) },
{ records: [briefRun({ order_id: 'o' })] });
assert.equal(row.countable, true);
assert.equal(row.value, 1);
assert.equal(row.denominator, 1);
assert.equal(row.facts.withVoyageRun, 1);
assert.deepEqual(row.facts.matched, ['o']);
});
test('use: no record, or a record naming another id, is NOT MEASURED - never a no', () => {
const row = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }) },
{ records: [briefRun({ order_id: 'zzz' })] });
assert.equal(row.facts.noRecord, 1);
assert.equal(row.facts.withVoyageRun, 0);
assert.equal(row.countable, false);
assert.equal(row.value, null);
assert.equal(row.denominator, 0);
assert.equal(row.facts.notMeasured, 1);
});
test('use: an equal slug alone no longer matches - only the order id does', () => {
const row = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan', slug: 'feat-a' }) },
{ records: [briefRun({ order_id: null, slug: 'feat-a' }), briefRun({ order_id: 'other', slug: 'feat-a' })] });
assert.equal(row.facts.withVoyageRun, 0);
assert.equal(row.facts.noRecord, 1);
});
test('use: an order id in another stats file does not count - only /trekbrief records the order', () => {
const row = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }) },
{ runs: runsFrom({ 'trekplan-stats.jsonl': [{ ts: 't', slug: 's', order_id: 'a' }] }) });
assert.equal(row.facts.withVoyageRun, 0);
assert.equal(row.facts.noRecord, 1);
});
test('use: two records with the same id and slug count once; the same id under two slugs is disputed', () => {
const once = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }) },
{ records: [briefRun({ order_id: 'a' }), briefRun({ order_id: 'a', ts: '2026-10-05T00:00:00Z' })] });
assert.equal(once.value, 1);
assert.equal(once.denominator, 1);
const two = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }) },
{ records: [briefRun({ order_id: 'a', slug: 'x' }), briefRun({ order_id: 'a', slug: 'y' })] });
assert.equal(two.facts.disputed, 1);
assert.equal(two.facts.withVoyageRun, 0);
assert.equal(two.countable, false, 'a disputed order is not measured');
});
test('use: one slug naming two order ids makes both disputed - a brief cannot run two orders', () => {
const row = use({
'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/b.md': order({ id: 'b', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
}, { records: [briefRun({ order_id: 'a', slug: 'one' }), briefRun({ order_id: 'b', slug: 'one' })] });
assert.equal(row.facts.disputed, 2);
assert.equal(row.facts.withVoyageRun, 0);
});
test('use: arbeidsform: ordre is a declared NO even with a record; arbeidsform: voyage with a record is YES', () => {
const row = use({
'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan', arbeidsform: 'ordre' }),
'r/orders/b.md': order({ id: 'b', date: '2026-09-29T00:00:00Z', marker: 'plan', arbeidsform: 'voyage' }),
}, { records: [briefRun({ order_id: 'a', slug: 'a' }), briefRun({ order_id: 'b', slug: 'b' })] });
assert.equal(row.facts.declaredNo, 1);
assert.equal(row.facts.withVoyageRun, 1);
assert.deepEqual(row.facts.matched, ['b']);
assert.equal(row.value, 1);
assert.equal(row.denominator, 2);
});
test('use: a veto marker mentioned mid-line in prose does not veto', () => {
const row = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan', extra: 'Not this: `<!-- arbeidsform: ordre -->` in a sentence.' }) },
{ records: [briefRun({ order_id: 'a' })] });
assert.equal(row.facts.declaredNo, 0);
assert.equal(row.value, 1);
});
test('use: arbeidsform: ordre on an ikke-nytt or unmarked order adds nothing to the denominator', () => {
const row = use({
'r/orders/y.md': order({ id: 'y', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/i.md': order({ id: 'i', date: '2026-09-29T00:00:00Z', marker: 'ikke-nytt', arbeidsform: 'ordre' }),
'r/orders/u.md': order({ id: 'u', date: '2026-09-29T00:00:00Z', arbeidsform: 'ordre' }),
}, { records: [briefRun({ order_id: 'y' })] });
assert.equal(row.facts.declaredNo, 0);
assert.equal(row.denominator, 1);
});
test('use: arbeidsform: voyage without a record is NOT MEASURED - the declaration is never the source', () => {
const row = use({ 'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan', arbeidsform: 'voyage' }) });
assert.equal(row.facts.withVoyageRun, 0);
assert.equal(row.facts.noRecord, 1);
assert.equal(row.countable, false);
});
test('use: orders before the counting start are NOT MEASURED; an order dated exactly on it counts', () => {
const row = use({
'r/orders/early.md': order({ id: 'early', date: '2026-10-01T23:59:59Z', marker: 'plan' }),
'r/orders/on.md': order({ id: 'on', date: '2026-10-02T00:00:00Z', marker: 'plan' }),
}, { countingSince: '2026-10-02', records: [briefRun({ order_id: 'early', slug: 'e' }), briefRun({ order_id: 'on' })] });
assert.equal(row.facts.beforeMarker, 1);
assert.equal(row.value, 1);
assert.equal(row.denominator, 1);
assert.deepEqual(row.facts.matched, ['on']);
});
test('use: with no counting start the row is NOT COUNTABLE and every order is NOT MEASURED', () => {
const row = use({
'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/b.md': order({ id: 'b', date: '2026-09-29T00:00:00Z', marker: 'plan', arbeidsform: 'ordre' }),
}, { countingSince: null, records: [briefRun({ order_id: 'a' })] });
assert.equal(row.countable, false);
assert.equal(row.value, null);
assert.equal(row.facts.notMeasured, row.facts.scanned);
assert.equal(row.facts.countingSince, null);
assert.match(row.detail, /counting has not started/);
assert.match(row.source, /counting not started/);
});
test('use: the window starts at the latest of the cutoff, MARKER_SINCE and the counting start', () => {
const files = { 'r/orders/a.md': order({ id: 'a', date: '2026-10-04T00:00:00Z', marker: 'plan' }) };
assert.match(use(files, { cutoff: '2026-10-05', countingSince: '2026-10-03' }).source, /from 2026-10-05/);
assert.match(use(files, { cutoff: '2026-09-21', countingSince: '2026-10-03' }).source, /from 2026-10-03/);
});
test('use: the exact outcome over YES, declared NO, no record and disputed', () => {
const row = use({
'r/orders/y.md': order({ id: 'y', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/n.md': order({ id: 'n', date: '2026-09-29T00:00:00Z', marker: 'prove', arbeidsform: 'ordre' }),
'r/orders/r.md': order({ id: 'r', date: '2026-09-29T00:00:00Z', marker: 'plan', arbeidsform: 'voyage' }),
'r/orders/d.md': order({ id: 'd', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/u.md': order({ id: 'u', date: '2026-09-29T00:00:00Z' }),
'r/orders/e.md': order({ id: 'e', date: '2026-09-01T00:00:00Z', marker: 'plan' }),
}, { records: [briefRun({ order_id: 'y', slug: 'y' }), briefRun({ order_id: 'd', slug: 'd1' }), briefRun({ order_id: 'd', slug: 'd2' })] });
assert.equal(row.countable, true);
assert.equal(row.value, 1);
assert.equal(row.denominator, 2, 'y (YES) + n (declared NO)');
assert.equal(row.facts.withVoyageRun, 1);
assert.equal(row.facts.declaredNo, 1);
assert.equal(row.facts.noRecord, 1);
assert.equal(row.facts.disputed, 1);
assert.equal(row.facts.notMeasured, 4, 'r (no record) + d (disputed) + u (unmarked) + e (before the window)');
assert.match(row.detail, /value = 1 of 2 measured/);
});
test('use: ordersNamedByARecord counts named orders even before counting starts (the known-positive check)', () => {
const row = use({
'r/orders/a.md': order({ id: 'a', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
'r/orders/b.md': order({ id: 'b', date: '2026-09-29T00:00:00Z', marker: 'plan' }),
}, { countingSince: null, records: [briefRun({ order_id: 'a' })] });
assert.equal(row.countable, false);
assert.equal(row.facts.ordersNamedByARecord, 1);
});
// --- 3 · INTERRUPTS ----------------------------------------------------------
test('interrupts: no user_input record means NOT COUNTABLE, with the line denominator stated', () => {
const dir = fixture({
'trekexecute-stats.jsonl': jsonl({ ts: 't', session_id: 's', success: true }, { ts: 't', event: 'main-merge-gate', payload: {} }),
'trekplan-stats.jsonl': jsonl({ ts: 't', slug: 'x' }),
});
try {
const row = measureInterrupts({ stats: loadStats(dir), callSites: { sites: [], scanned: 3 } });
assert.equal(row.countable, false);
assert.equal(row.facts.lines, 3);
assert.equal(row.facts.userInput, 0);
assert.match(row.detail, /0 user_input records of 3 lines in 2 files/);
assert.match(row.detail, /0 emitter call sites/);
} finally { cleanup(dir); }
});
test('interrupts: user_input WITHOUT a slug or run id is still not countable per delivery', () => {
const dir = fixture({
'trekexecute-stats.jsonl': jsonl({ ts: 't', event: 'user_input', payload: { question: 'q' } }),
});
try {
const row = measureInterrupts({ stats: loadStats(dir), callSites: { sites: [], scanned: 1 } });
assert.equal(row.countable, false);
assert.equal(row.facts.userInput, 1);
assert.equal(row.facts.withDelivery, 0);
} finally { cleanup(dir); }
});
test('interrupts: user_input carrying a slug or run id is countable, per delivery', () => {
const dir = fixture({
'trekexecute-stats.jsonl': jsonl(
{ ts: 't1', event: 'user_input', payload: { slug: 'feat-a' } },
{ ts: 't2', event: 'user_input', payload: { slug: 'feat-a' } },
{ ts: 't3', event: 'user_input', payload: { run_id: 'r-9' } },
),
});
try {
const row = measureInterrupts({ stats: loadStats(dir), callSites: { sites: [], scanned: 1 } });
assert.equal(row.countable, true);
assert.equal(row.value, 3);
assert.deepEqual(row.facts.perDelivery, { 'feat-a': 2, 'r-9': 1 });
} finally { cleanup(dir); }
});
test('emitterCallSites: counts user_input emissions outside lib/stats/ and tests/ only', () => {
const root = fixture({
'commands/a.md': 'node ${CLAUDE_PLUGIN_ROOT}/lib/stats/event-emit.mjs \\\n --event user_input \\\n',
'commands/b.md': 'the emitter knows user_input as an event name - prose, not a call\n',
'lib/util/x.mjs': "emitEvent('user_input', { slug });\n",
'lib/stats/event-emit.mjs': "emit('user_input', {});\n",
'tests/lib/y.test.mjs': "emit('user_input', {});\n",
'hooks/scripts/h.mjs': "spawnSync('node', [EMIT, '--event', 'user_input']);\n",
});
try {
const r = emitterCallSites(root);
assert.deepEqual(r.sites.map((s) => s.file).sort(), ['commands/a.md', 'hooks/scripts/h.mjs', 'lib/util/x.mjs']);
assert.equal(r.scanned, 4, 'commands/a.md, commands/b.md, lib/util/x.mjs, hooks/scripts/h.mjs');
} finally { cleanup(root); }
});
// --- 4 · the signature-preserving mutant (M9) ----------------------------------
const MODULE = 'export function verdict(code) { return { status: code === 0 ? "PASSED" : "FAILED" }; }\n';
const STRONG_TEST = [
"import { test } from 'node:test';",
"import { strict as assert } from 'node:assert';",
"import { verdict } from '../lib/m.mjs';",
"test('verdict fells a failing exit', () => { assert.equal(verdict(1).status, 'FAILED'); });",
'',
].join('\n');
const behaviourRegistry = {
defects: [{
id: 'D-X', probe: 'behaviour',
check: [{ test: 'tests/t.test.mjs', name: 'verdict fells a failing exit', expect: 'fails', stubs: ['lib/m.mjs'] }],
}],
experiments: [],
};
test('buildM9Tree: every export keeps its name and returns {status:"PASSED"}; every named test only checks typeof', async () => {
const root = fixture({ 'lib/m.mjs': MODULE, 'tests/t.test.mjs': STRONG_TEST });
const m9 = buildM9Tree(root, behaviourRegistry);
try {
const mod = readFileSync(join(m9, 'lib', 'm.mjs'), 'utf8');
assert.match(mod, /export function verdict\(\) \{ return \{ status: 'PASSED' \}; \}/);
const t = readFileSync(join(m9, 'tests', 't.test.mjs'), 'utf8');
assert.match(t, /verdict fells a failing exit/);
assert.match(t, /typeof/);
assert.doesNotMatch(t, /FAILED/, 'the mutant test asserts nothing about behaviour');
assert.equal(readFileSync(join(root, 'lib', 'm.mjs'), 'utf8'), MODULE, 'the measured tree is never touched');
} finally { cleanup(root, m9); }
});
test('mutants: a gate that CLOSES the probe on M9 has felled 0 of 1', () => {
const root = fixture({ 'lib/m.mjs': MODULE, 'tests/t.test.mjs': STRONG_TEST });
try {
const row = measureMutants(root, {
registry: behaviourRegistry,
evaluate: () => ({ status: 'closed', detail: '' }),
runTest: () => true,
});
assert.equal(row.countable, true);
assert.equal(row.value, 0);
assert.equal(row.denominator, 1);
assert.match(row.detail, /D-X closed/);
} finally { cleanup(root); }
});
test('mutants: a gate that refuses to close the probe on M9 has felled 1 of 1', () => {
const root = fixture({ 'lib/m.mjs': MODULE, 'tests/t.test.mjs': STRONG_TEST });
try {
const row = measureMutants(root, {
registry: behaviourRegistry,
evaluate: () => ({ status: 'not-fellable', detail: 'still passes against a signature-preserving mutant' }),
runTest: () => true,
});
assert.equal(row.value, 1);
assert.equal(row.denominator, 1);
} finally { cleanup(root); }
});
test('mutants: a mutant whose own tests do not pass is not LIVE, so the row cannot be counted', () => {
// Without this control a broken fixture would read as "felled".
const root = fixture({ 'lib/m.mjs': MODULE, 'tests/t.test.mjs': STRONG_TEST });
try {
const row = measureMutants(root, {
registry: behaviourRegistry,
evaluate: () => ({ status: 'not-fellable', detail: 'x' }),
runTest: () => null,
});
assert.equal(row.countable, false);
assert.equal(row.value, null);
assert.match(row.detail, /not live/);
} finally { cleanup(root); }
});
test('mutants: against the REAL gate and registry, M9 is live and the denominator is 1', () => {
// Pins the measurement's shape, not today's value: M9's typeof-only tests
// really do pass on the M9 tree (so "felled" can only come from the gate).
const row = measureMutants(ROOT);
assert.equal(row.countable, true, row.detail);
assert.equal(row.denominator, 1);
assert.ok(row.value === 0 || row.value === 1);
assert.deepEqual(row.facts.entries.sort(), ['D-03', 'D-04']);
});
// --- the whole yardstick -------------------------------------------------------
function world({ orders = {}, stats = {} } = {}) {
const coord = fixture(Object.keys(orders).length ? orders : { 'r/orders/.keep': '' });
const data = fixture(Object.keys(stats).length ? stats : { '.keep': '' });
return { coord, data };
}
test('measureAll + main: RED with exit 1 while any number is not countable', () => {
const { coord, data } = world({
orders: { 'r/orders/o.md': order({ id: 'o', date: '2026-09-22T00:00:00Z' }) },
stats: { 'trekreview-stats.jsonl': jsonl(review('2026-09-01T00:00:00Z', 'ALLOW')) },
});
try {
const result = measureAll({ root: ROOT, coordDir: coord, dataDir: data, cutoff: DEFAULT_CUTOFF });
assert.equal(result.green, false);
assert.equal(result.countable, 1);
const text = render(result);
assert.match(text, /1 of 3 numbers countable/);
assert.match(text, /signature-preserving mutants felled: \d of 1/);
assert.match(text, /verdict: NOT JUDGED/);
for (const id of ['use', 'trust', 'interrupts', 'mutants']) assert.match(text, new RegExp(`\\| ${id} \\|`));
assert.equal(main(['--root', ROOT, '--coord', coord, '--data', data, '--json']), 1);
} finally { cleanup(coord, data); }
});
test('measureAll: all three countable and the mutant felled is still RED until the verdict can be judged', () => {
const { coord, data } = world({
orders: { 'r/orders/o.md': order({ id: 'o', date: '2026-09-29T00:00:00Z', marker: 'plan' }) },
stats: {
'trekbrief-stats.jsonl': jsonl(briefRun({ order_id: 'o', slug: 'a' })),
'trekreview-stats.jsonl': jsonl(review('2026-09-01T00:00:00Z', 'ALLOW', 'a')),
'trekexecute-stats.jsonl': jsonl({ ts: 't', event: 'user_input', payload: { slug: 'a' } }),
},
});
try {
const result = measureAll({
root: ROOT, coordDir: coord, dataDir: data, cutoff: DEFAULT_CUTOFF, countingSince: MARKER_SINCE,
mutantOptions: { evaluate: () => ({ status: 'not-fellable', detail: '' }) },
});
assert.equal(result.countable, 3);
assert.equal(result.rows.find((r) => r.id === 'mutants').value, 1);
assert.equal(result.verdict.judged, false);
assert.equal(result.green, false);
} finally { cleanup(coord, data); }
});
test('main: an unknown argument or a malformed cutoff is a usage error (exit 2)', () => {
assert.equal(main(['--nope']), 2);
assert.equal(main(['--cutoff', '21.09.2026']), 2);
});
test('measureAll: without countingSince it uses USE_COUNTING_SINCE, whatever its value', () => {
assert.ok('USE_COUNTING_SINCE' in Y, 'scripts/yardstick.mjs exports USE_COUNTING_SINCE');
const { coord, data } = world({
orders: { 'r/orders/o.md': order({ id: 'o', date: '2026-10-29T00:00:00Z', marker: 'plan' }) },
stats: { 'trekbrief-stats.jsonl': jsonl(briefRun({ order_id: 'o' })) },
});
try {
const all = measureAll({ root: ROOT, coordDir: coord, dataDir: data, cutoff: DEFAULT_CUTOFF,
mutantOptions: { evaluate: () => ({ status: 'not-fellable', detail: '' }) } });
const direct = measureUse({ queue: readOrders(coord), runs: Y.orderRuns(loadStats(data)), cutoff: DEFAULT_CUTOFF,
countingSince: Y.USE_COUNTING_SINCE });
assert.deepEqual(all.rows.find((r) => r.id === 'use'), direct);
} finally { cleanup(coord, data); }
});
test('main: the CLI prints its result when run through a symlink', () => {
const { coord, data } = world();
const links = mkdtempSync(join(tmpdir(), 'yardstick-link-'));
try {
const link = join(links, 'yardstick.mjs');
symlinkSync(join(ROOT, 'scripts', 'yardstick.mjs'), link);
const r = spawnSync(process.execPath, [link, '--coord', coord, '--data', data, '--json'], { encoding: 'utf8', timeout: 120000 });
assert.ok(r.stdout.trim().length > 0, `no output through a symlink (exit ${r.status})`);
assert.ok(Array.isArray(JSON.parse(r.stdout).rows));
} finally { cleanup(coord, data, links); }
});

View file

@ -8,7 +8,7 @@
// (a) brief without a marker → /trekplan's check exits non-zero
// (b) marker + intent text edited after → exits non-zero (stale)
// (c) valid marker → exits 0 (known negative)
// (d) the stamp runs before the manual/auto fork and emits brief-approved
// (d) the stamp runs before the manual/auto fork
//
// The module is imported dynamically so a missing module fails each test on its
// own instead of crashing the file into a single failure.
@ -86,12 +86,6 @@ function gate(briefPath) {
return run(commandLine('commands/trekplan.md', '--check'), { '{brief_path}': briefPath });
}
function stampInto(dir, env) {
// {BRIEF_PATH} since M2 (the resolved --approve target); {PROJECT_DIR} kept for the old spelling
return run(commandLine('commands/trekbrief.md', '--stamp'),
{ '{BRIEF_PATH}': join(dir, 'brief.md'), '{PROJECT_DIR}': dir }, env);
}
function codes(res) {
const out = JSON.parse(res.stdout);
return out.errors.map((e) => e.code);
@ -223,7 +217,7 @@ test('trekplan prose: the intent-approval gate sits in "Read the brief" and says
assert.match(section, /\*\*halt\*\*/i);
});
// ---- (d) the stamp: operator answer → marker + brief-approved, on BOTH paths ----
// ---- (d) the stamp: operator answer → marker, on BOTH paths ----
test('(d) trekbrief: the stamp step runs BEFORE the Phase 5 manual/auto fork', () => {
const text = read('commands/trekbrief.md');
@ -239,47 +233,6 @@ test('(d) trekbrief: the stamp step runs BEFORE the Phase 5 manual/auto fork', (
assert.match(before, /--quick/);
});
test('(d) trekbrief: the auto path no longer emits brief-approved on its own (one emission, not two)', () => {
const text = read('commands/trekbrief.md');
const auto = text.indexOf('### Auto path');
const phase6 = text.indexOf('## Phase 6');
assert.ok(auto > 0 && phase6 > auto);
assert.ok(!/--event brief-approved/.test(text.slice(auto, phase6)),
'brief-approved is emitted by the stamp; a second emission on the auto path would double-count');
});
test('(d) the manual-path stamp line writes the marker AND emits a brief-approved record', () => {
const dir = tmpDir();
const data = tmpDir();
try {
writeFileSync(join(dir, 'brief.md'), BRIEF);
const r = stampInto(dir, { CLAUDE_PLUGIN_DATA: data });
assert.equal(r.status, 0, `stamp failed: ${r.stdout} ${r.stderr}`);
const after = readFileSync(join(dir, 'brief.md'), 'utf8');
assert.match(after, /^intent_approved_hash: "sha256:[0-9a-f]{64}"$/m);
assert.match(after, /^intent_approved_at: "\d{4}-\d\d-\d\dT[^"]+"$/m);
const statsFile = join(data, 'trekexecute-stats.jsonl');
assert.ok(existsSync(statsFile), 'brief-approved record must be written');
const recs = readFileSync(statsFile, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
const ev = recs.filter((x) => x.event === 'brief-approved');
assert.equal(ev.length, 1);
assert.equal(ev[0].payload.slug, 'dark-mode-toggle');
assert.match(ev[0].payload.intent_hash, /^sha256:[0-9a-f]{64}$/);
// and the gate now passes on the stamped file
assert.equal(gate(join(dir, 'brief.md')).status, 0);
} finally {
rmSync(dir, { recursive: true, force: true });
rmSync(data, { recursive: true, force: true });
}
});
test('(d) with CLAUDE_PLUGIN_DATA unset the record still lands (fallback = the dir the yardstick reads)', async () => {
const { resolveApprovalDataDir } = await load();
const d = resolveApprovalDataDir({ HOME: '/home/someone' });
assert.equal(d, '/home/someone/.claude/plugins/data/voyage-ktg-plugin-marketplace');
assert.equal(resolveApprovalDataDir({ CLAUDE_PLUGIN_DATA: '/x/y', HOME: '/h' }), '/x/y');
});
test('re-stamping replaces the marker instead of duplicating it', async () => {
const { stampIntentApproval } = await load();
const once = stampIntentApproval(BRIEF, new Date('2026-09-23T08:00:00Z')).text;