Commit graph

59 commits

Author SHA1 Message Date
971604d870 feat(eval): add review-coordinator contract reference impl + deterministic test 2026-06-26 11:54:48 +02:00
e986b10431 feat(voyage): validate iterations_remaining in progress-validator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:26:22 +02:00
213cf388de chore(voyage): S34 — V30 economy-profile self-declares experimental (uncalibrated Jaccard floor)
The economy profile's cross-tier Jaccard floor (0.55) rests on parked-synthetic
fixtures; empirical Step-17 calibration is v4.2-budget-gated ($60–120,
unauthorized). Fork resolved as label-not-calibrate: the experimental status,
previously prose-only in docs/profiles.md, now lives in the profile data and is
machine-checked. No new user-facing capability — honest labeling + a guard.

- lib/profiles/economy.yaml: add `experimental: true` (with rationale comment).
- lib/validators/profile-validator.mjs: recognize `experimental` as an OPTIONAL
  boolean; non-boolean → PROFILE_INVALID_ENUM. Absent ⇒ tier is stable
  (premium/balanced unaffected, profile_version stays 1.0 — additive).
- README.md + docs/operations.md + docs/profiles.md: flag the `economy` table
  row "⚠ Experimental (uncalibrated Jaccard floor)".
- tests/synthetic/profile-jaccard-calibration.md + analysis §6/§10 + backlog
  plan §S34: cross-reference the marker; mark V30/S34 done.

+5 tests (739 → 744, 742/2/0): economy declares experimental:true; premium and
balanced do not; validator rejects non-boolean experimental; every profile-doc
economy row is flagged; the flag tracks the calibration's parked-synthetic
status (must drop in the same change that lands real calibration).

Closes the balance backlog (4/4, S31–S34). claude plugin validate green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-20 10:18:38 +02:00
7cfce2b996 fix(voyage): S26 — parse plan_version prose form (S22 defect #2/#3)
The plan template (templates/plan-template.md:21) emits plan_version ONLY in
the prose blockquote metadata line:

  > Generated by trekplan v{version} on {YYYY-MM-DD} — `plan_version: 1.7`

It carries no frontmatter plan_version (the frontmatter block is trekreview-only
and holds source_findings). But PLAN_VERSION_REGEX in lib/parsers/plan-schema.mjs
was `^`-anchored (/m), matching ONLY line-start (frontmatter). The backtick-
wrapped prose form never matched → extractPlanVersion returned null → every real
generated plan got a spurious PLAN_NO_VERSION warning from plan-validator.

Not caught earlier because no test ran the parser against the actual template;
the synthetic fixtures all carry frontmatter plan_version and parse fine.

Internal source contradiction surfaced: the parser comment documents intent
"frontmatter OR doc body", planning-orchestrator.md:228 + the template place it
in the prose line, while HANDOVER-CONTRACTS.md calls it a frontmatter field.

Operator-chosen fix (parser, not template): relax the regex to
`/(?:^|`)plan_version:.../m` so it matches line-start (frontmatter) OR the
backtick-prefixed prose form. Honors the parser's own documented contract,
fixes all existing + future plans, changes no plan output, touches one code
file — vs the template-fix which would alter every plan's shape, contradict
planning-orchestrator.md, and force a 3-file doc reconciliation (scope creep).

TDD (red first): added prose-form + canonical-template regression pins in
plan-schema.test.mjs and a no-PLAN_NO_VERSION pin in plan-validator.test.mjs;
all 3 red on the `^`-anchored regex, green after the relax. The template pin
ties the parser directly to the real generated artifact.

S22 defect closed in docs/S22-happy-path-dogfood.md (§Pipeline defects + log).

Tests: 728 (726 pass / 2 skip / 0 fail; live `node --test`, = 725 baseline +3).
plugin validate passes (1 accepted CLAUDE.md-at-root warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-19 21:59:31 +02:00
b4edc12bec fix(voyage): S23 — make /trekplan Phase 9 dedup executable (defect #1)
Phase 9's dedup hand-off was broken on two layers, both surfaced by the S22
dogfood: (a) plan-critic + scope-guardian (Read/Glob/Grep, no Write) were told
to write /tmp/*-out.json the dedup helper reads — they cannot; (b) even with
Write, their Output format emits markdown, not the helper's
{agent,findings:[{file,line,rule_key,text}]} schema. readJsonOrNull then
swallowed the absent files -> a silent empty merge that discarded every finding.

Fix (operator-chosen A'): keep the reviewers read-only; make the hand-off run.
- plan-review-dedup.mjs gains a --stdin mode reading {plan_critic,scope_guardian};
  malformed stdin exits non-zero so a broken hand-off surfaces loudly instead of
  collapsing into a silent empty merge. File mode + its tests are untouched.
- plan-critic.md + scope-guardian.md now emit a trailing machine-readable `json`
  findings block (the inline hand-off; no Write tool needed).
- trekplan.md + planning-orchestrator.md Phase 9 rewritten in lockstep: extract
  both blocks, pipe via heredoc into --stdin. No temp files, portable, no
  pathguard dependency.

TDD: malformed-stdin test failed first (CLI ignored stdin -> exit 0 = the bug),
green after impl. New S23 doc-pin asserts both docs use --stdin (not the dead
/tmp paths) and both agents declare the json block. Suite 724 (722/2/0); live
HEAD baseline was 720, not the stale 705 STATE carried forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-19 21:12:36 +02:00
b208e4ee04 fix(voyage): S21 — close IPv4-mapped IPv6 SSRF bypass + security/safety audit (blind spot #2)
S21 = devil's-advocate blind spot #2 (security/safety), operator scope
"security-core": fix genuine defects, honestly record the rest.

SSRF fix (CWE-918). validateOtlpEndpoint classified the host by literal
string match. Decimal/hex/octal/trailing-dot encodings are already caught
(the WHATWG URL parser canonicalizes them), but IPv4-mapped IPv6 literals
(::ffff:127.0.0.1, ::ffff:192.168.x, ::ffff:169.254.169.254) render as
::ffff:HHHH:HHHH and matched neither the loopback set, the RFC-1918 regex,
link-local, nor HARD_BLOCKED_HOSTS — they passed over https and would reach
loopback / private / cloud-metadata, defeating the comment's "PERMANENTLY
block metadata" promise. Added mappedV4() to decode the embedded IPv4
(dotted + hex-pair forms) and classify on it. TDD: 4 tests failing-first —
mapped loopback/RFC-1918/metadata rejected (metadata stays HARD_BLOCKED even
with VOYAGE_OTEL_ALLOW_PRIVATE=1), mapped public ::ffff:8.8.8.8 still valid
(no over-block). Threat model narrow (endpoint is operator-set env; export
opt-in; a brief cannot set env).

Survivor #18 honest-residual. T2-bakeoff §3 "Classifier interference: 0"
read as satisfied, but the auto/bypass-mode re-run that matters for headless
trekreview was never run (mode is operator-set, not settable in-session) —
footnoted, not gating. Per recommendation #9, moved to an OPEN RESIDUAL
(untested), guarded by a new doc-consistency pin.

Audit record. ## S21 resolution block in devils-advocate-results.md
dispositions all four sub-questions: SSRF (fixed), hooks-block (verified;
advisory-rail residuals: Write-only matcher, Bash-redirect, regex gaps — by
design), malicious-brief-headless (catastrophe-blocked, exfil NOT blocked —
inherent limit). S21b flagged (NOT done): operations.md:15 mis-describes
autonomy-gate.mjs state machine + --gates table.

Test count: real baseline 700 (698 pass / 2 skip), NOT 715 — the node:test
headline carried in S20 commit msgs was stale/miscounted (census figures
were correct). Now 705 (703 pass / 2 skip / 0 fail); census behavior
601->605, doc-pins 71->72, total 672->677.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-19 20:02:56 +02:00
494a344700 docs(voyage): S19 — leanness: honest test census + prune 3 redundant prose-pins
Closes devils-advocate audit §Top changes #8 (MINOR×MED). Reports the
behavior-test count separately from the doc-consistency prose-pin count so the
cited total no longer oversells behavior coverage, and removes the clearest
reword-fragile redundancies. Conservative scope (operator-chosen): no genuine
regression guard dropped.

1. Suite census (report separately). lib/util/test-census.mjs +
   tests/lib/test-census.test.mjs walk tests/**/*.test.mjs and bucket top-level
   test() declarations into behavior vs doc-consistency-pins (bucket per file,
   regex PIN_FILE_RE), asserting the two sum to the total so neither can drift
   silently. Split emitted as a t.diagnostic: behavior=601 doc-consistency-pins=69.
   Metric = top-level declarations; node:test's runtime total counts subtests
   too and is therefore >= it.

2. Conservative prune (3 tests, each provably subsumed):
   - "trekexecute.md still parses v1.7 plan schema" — tautological OR-chain;
     real coverage = the plan_version:1.7 template pin + plan-validator /
     plan-schema behavior tests.
   - "CLAUDE.md mentions all six pipeline commands" — hardcoded six-string list
     subsumed by the filesystem-driven "commands table mentions every
     commands/*.md file" structural pin.
   - "CLAUDE.md mentions /trekcontinue command" — same subsumption.
   Each removal leaves an in-place note (why + where coverage lives).

3. doc-consistency.test.mjs header now documents the structural-invariant vs
   prose/existence-pin distinction so new pins land in the right kind.

TDD: census test written failing-first (ERR_MODULE_NOT_FOUND before
lib/util/test-census.mjs existed). Suite 699->698 (696 pass / 2 skip / 0 fail):
-3 prune +2 census. plugin validate passes (1 accepted warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-19 13:52:30 +02:00
987e847ea1 feat(voyage): S18 — framing-hardening (min-version gate + memory status + pre-2.2 doc + flagship hedge)
Closes devils-advocate audit #4–#6 (MAJOR×MED). Four additive, non-breaking
changes to the v5.5 framing-alignment machinery:

1. --min-brief-version <ver> gate (audit #4). Opt-in version floor on
   /trekplan + /trekresearch, forwarded to brief-validator as --min-version.
   New opts.minBriefVersion warns BRIEF_VERSION_BELOW_MINIMUM (never blocks)
   when a brief declares a version below the floor; trekreview exempt; absent
   opt = no check. CLI shim parses --min-version and skips its value token in
   filePath detection.

2. memory_alignment.status field (audit #5). brief-reviewer now emits
   status: verified | n_a | contradictions so a score-5 N/A (no memory) is
   distinguishable from a score-5 verified-aligned brief — the score≥4 gate
   passes in both, status reveals whether the wrong-premise defense ran.

3. Document pre-2.2 = zero framing enforcement (audit #4). HANDOVER-CONTRACTS
   §Handover 1 now states the producer-elective hole + two remedies. Also
   fixes a stale "current is 2.1" line (current is 2.2).

4. Soften flagship overselling (audit #6). CLAUDE.md Context-Engineering
   principle now hedges that main-context relief is asserted-by-design, not
   measured (T1 PoC found Δ≈0); README carried no false claim to fix.

TDD: 8 new tests written failing-first (5 validator, 1 trekbrief status pin,
2 doc-consistency cross-file pins). Suite 691→699 (697 pass / 2 skip / 0 fail).
No flagship prose-pin added (deliberate, per S19 anti-bloat).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-19 13:33:00 +02:00
6b30483304 feat(voyage): S12 — NW3 synthesis-agent built + measured → declined per measurement [skip-docs]
NW3 (CC-26 §6 PoC): delegate trekplan Phase 7 synthesis to a synthesis-agent,
adopt only if Δ main-context ≥30% with no quality loss. Operator chose the
deterministic-proof path (live ≥3-run bake-off was env-blocked: no API key;
installed plugin is a cache copy so a new agent is invisible to `claude -p`).

Decisive structural finding: trekplan Phase 5 runs the swarm FOREGROUND, so its
outputs are already resident in main before Phase 7. Delegating only Phase 7
evicts nothing → Δ_faithful = 0% (BASE-independent). The ≥30% saving needs an
out-of-scope Phase-5 redesign (swarm-writes-to-disk / nested orchestrator).
VERDICT: DECLINED per measurement.

- agents/synthesis-agent.md — dormant, schema-conformant deliverable (NOT wired)
- lib/plan/synthesis-digest-schema.mjs — digest output contract (+ tests)
- scripts/synthesis-measure.mjs — deterministic Δ-accounting core (+ tests)
- tests/fixtures/synthesis/ — 7 exploration outputs + representative digest
- docs/T1-synthesis-poc-results.md — measurement + verdict (reproducible)
- CLAUDE.md — agent table row (doc-consistency: 24 agents)

Tests 670 → 695 (693 pass / 2 skip / 0 fail). `claude plugin validate` clean
(only the pre-existing root-CLAUDE.md warning). commands/trekplan.md untouched.

[skip-docs] rationale: no user-facing feature ships (NW3 declined; agent dormant
and unwired). The substantive doc is docs/T1-synthesis-poc-results.md; the
README/CHANGELOG roll-up for NW1–NW3 is the S13 coordinated release per
docs/W1-narrow-wins-plan.md §S13.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-18 17:58:39 +02:00
9fb536e2d8 feat(voyage): S11 — NW2 part B, integrate opt-in --workflow flag
Make the bake-off-validated Workflow substrate (Arm B) reachable behind an
opt-in --workflow flag for /trekreview. Default Phase 5-6 path stays prose
to preserve the lower portability floor; --workflow raises the consumer
floor to Claude Code 2.1.154+ (the Workflow tool).

- arg-parser: --workflow added to trekreview boolean flags
- commands/trekreview.md: flag row + Phase 5 substrate-routing gate +
  new section 'Phase 5-6 via the Workflow substrate' (invocation contract,
  S10 gotchas, bake-off citation, auto/bypass residual as Known limitation)
- docs/command-modes.md: --workflow row in /trekreview table
- routes to existing scripts/trekreview-armB.workflow.mjs (byte-identical to
  the S10 part-B POSITIVE build); integration is pure routing, no script change

TDD: 8 new tests (arg-parser flag recognition + combine; command/doc prose
pins for route, opt-in posture, 2.1.154+ floor, bake-off evidence).
Suite 662 -> 670 (668 pass / 2 skip / 0 fail). plugin validate clean modulo
known root-CLAUDE.md warning. Resolves W1-narrow-wins-plan.md S11.
2026-06-18 17:22:09 +02:00
869bf318d2 feat(voyage): S10 — NW2 part A (Workflow port + fidelity harness + smoke)
Build the prose-vs-Workflow bake-off machinery for /trekreview Phase 5-6 and
run a 1-run/arm smoke to de-risk before the full measurement (operator posture:
build + smoke, then pause for go/no-go on the full >=3-runs/arm run).

New:
- lib/review/fidelity-diff.mjs (+ tests) — the PRIMARY metric: parse two
  review.md (or two structured arm outputs) and compare verdict + jaccard over
  (file,line,rule_key)-IDs + per-finding severity/rule_key. Reuses jaccard +
  frontmatter + NW1 findings-schema + finding-id. fidelityDiffStructured avoids
  rendering review.md per run.
- scripts/trekreview-armB.workflow.mjs — Arm B: Phase 5-6 as a Workflow
  (parallel([conformance, correctness]) schema-forced -> JS dedup-by-triplet ->
  agent(review-coordinator) verdict schema). Path-based input via args (reviewers
  carry Read). Inlines dedup + the 12-key rule_key enum (scripts have no imports).
- tests/fixtures/bakeoff/ — committable fixture: real diff of b149538 (NW1) +
  brief reconstructed from plan S9. Both arms review the same pinned input.
- docs/T2-bakeoff-results.md — smoke results + verdict + go/no-go recommendation.

Smoke result: SMOKE PASS. Arm B runs the full pipeline (3 agents) with ZERO
classifier interference; fidelity EQUIVALENT to Arm A at the verdict level
(both ALLOW; jaccard 1.0). Caveat: the clean TDD'd fixture yielded ~0 findings,
so finding-SET fidelity was not stressed (only verdict fidelity proven). A
reviewer-level divergence appeared (Arm B raised 1 raw finding, coordinator
filtered it; Arm A raised 0) — to be quantified in the full run on a
richer-finding-surface fixture. NOT the T2 §5 POSITIVE/NEGATIVE verdict.

Suite 647 -> 662 (660 pass / 2 skip / 0 fail; +15 fidelity-diff). claude plugin
validate clean (known root-CLAUDE.md warning only). Plan: docs/W1-narrow-wins-plan.md S10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-18 14:40:19 +02:00
b149538d43 feat(voyage): S9 — NW1 reviewer-output schema contract (TDD, ungated)
Codify the /trekreview Phase 5 reviewer JSON contract as a validated schema so
main validates each reviewer's output instead of merely JSON.parse-ing it, and
re-asks on schema failure (not just parse failure). Retires the fragility at
trekreview.md:202-204. No Workflow dependency (S8 tier 1, ships regardless).

New lib/review/findings-schema.mjs (3-layer Content -> Raw-text -> CLI shim,
reusing result.mjs error-shape + rule-catalogue RULE_KEYS/SEVERITY_VALUES):
- validateFindings(payload): hard errors on load-bearing fields the dedup
  triplet + verdict depend on — file, rule_key (in catalogue), severity (enum),
  line (integer >= 0); accumulates all errors; per-finding location.
- extractFindingsBlock(text): last fenced ```json block (the :202 contract).
- validateReviewerOutput(text): extract + parse + schema, unifying parse and
  schema failures under one bounded re-ask path.
Stable codes: FINDINGS_NOT_OBJECT/_NOT_ARRAY/_NO_JSON_BLOCK/_PARSE_ERROR (top),
FINDING_MISSING_FILE/_MISSING_RULE_KEY/_UNKNOWN_RULE_KEY/_BAD_SEVERITY/_BAD_LINE
(per-finding). Descriptive fields + unknown keys tolerated (forward-compat).

Phase 5 prose: replace "parse last json block; on parse error re-emit" with
"validate against findings-schema; on failure re-ask conforming JSON, bounded
N=2; never feed unvalidated findings to the coordinator".

TDD: 27 failing tests first, then minimal code to pass. Suite 606 -> 647
(645 pass / 2 skip / 0 fail). claude plugin validate clean (only the known
root-CLAUDE.md warning). Plan: docs/W1-narrow-wins-plan.md S9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-18 14:06:07 +02:00
736ae55d66 feat(voyage): S6 — v5.5 brief framing enforcement (brief_version 2.2)
Implements the CLAUDE.md cross-cutting invariant "brief framing must match
operator intent" as a controlled brief_version 2.1->2.2 bump (operator option A1).
Three defense layers, version-gated at >=2.2 so existing 2.0/2.1 briefs stay
valid (forward + backward compatible), mirroring the phase_signals >=2.1 gate:

- L1 framing: enum field (preserve|refine|replace|new-direction). Enum-checked
  on any version when present (BRIEF_INVALID_FRAMING); missing at >=2.2 ->
  BRIEF_MISSING_FRAMING. /trekbrief Phase 2.5 collects it BEFORE any brief prose
  (non-skippable, even in --quick).
- L2 memory alignment: new brief-reviewer dimension 6 comparing brief Intent/Goal
  + framing against operator memory for explicit contradictions; degrades to
  score 5 (N/A) when no memory context is supplied. Wired into Phase 4e gate
  (memory_alignment.score >= 4).
- L3 obligatory ## TL;DR (<=5 content lines) at >=2.2; soft cap ->
  BRIEF_TLDR_TOO_LONG warning.

trekreview briefs are exempt from the framing/TL;DR gate. Handover 1 PUBLIC
CONTRACT doc, README "What's new", and the CLAUDE.md invariant + agents table
(brief-reviewer 5->6 dimensions) updated to 2.2 (schema axis only; plugin
version badge + CHANGELOG remain S10).

Iron Law followed: validator tests red->green first. Tests 586 -> 606
(+20, 604 pass / 2 skip). claude plugin validate passes (pre-existing
CLAUDE.md root-context warning unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-18 13:09:50 +02:00
fdd3ad80d7 feat(voyage): W2 impl (S4) — gate resolver model, adopt native effort:, doc-truth
S4 of the 2.1.181 upgrade — implementation, not a gate. TDD: failing test
written first for the resolver gate, then the fix; suite green throughout.

- Resolver MAJOR (FIX): lib/profiles/phase-signal-resolver.mjs now imports
  BASE_ALLOWED_MODELS from profile-validator and gates `model`
  (if 'model' in entry && BASE_ALLOWED_MODELS.includes(entry.model)),
  mirroring the EFFORT_LEVELS gate one line up. Out-of-allowlist models
  (gpt-4, haiku) are dropped instead of handed to an agent spawn —
  defense-in-depth behind brief-validator's validation-time check. No
  circular import (brief-validator already imports the same symbol).
  +2 tests (drops-invalid / keeps-valid).
- Native effort: (SHIP, static additive): effort: frontmatter on 8 agents —
  retrieval (task-finder, git-historian, dependency-tracer,
  architecture-mapper) = medium; adversarial-reasoning (plan-critic,
  risk-assessor, contrarian-researcher, review-coordinator) = high. The
  other 15 stay unset -> inherit Opus-4.8 default (high). This per-spawn
  REASONING effort is a different axis from brief phase_signals.effort
  (ORCHESTRATION shape) per the S3 decision.
- Doc-truth + axis distinction: new canonical docs/profiles.md
  §Model & effort axes (opus->Opus 4.8 default-high; orchestration vs
  reasoning effort table; native-effort precedence; per-agent levels).
  Short notes in CLAUDE.md (after Agents table) and README.md (Cost
  profile), both pointing to profiles.md.
- Open (non-blocking, unchanged): only STATIC effort shipped — the
  verified-safe minimum. Profile-driven DYNAMIC effort still needs
  verification of the per-spawn effort param or env-var injection.

Matrix: new "S4 resolutions" section. Tests 582 total / 580 pass / 0 fail /
2 skip (was 578 pass; +2). claude plugin validate passes (only pre-existing
root-CLAUDE.md warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-18 12:27:07 +02:00
b0db428c74 feat(voyage): add resolvePhaseModel for brief-signal orchestrator override (closes #9 part A) 2026-05-14 21:38:51 +02:00
681f682abc fix(voyage): coerce brief_version to string + quote template + update doc pin (closes #8 #11)
v5.1.0 shipped with an unquoted brief_version: 2.1 in trekbrief-template.md.
parseScalar coerced it to Number 2.1, and the sequencing gate guarded on
typeof === 'string', silently bypassing BRIEF_V51_MISSING_SIGNALS.

Three-part atomic fix:
- brief-validator.mjs:87+149 now accepts both string and number forms via
  String(fm.brief_version) coercion.
- trekbrief-template.md quotes the value so new briefs parse as String.
- doc-consistency.test.mjs pins the QUOTED form going forward.

Three regression tests added in brief-validator.test.mjs.
2026-05-14 21:36:10 +02:00
aa819549be feat(voyage): add phase-signal-resolver helper for v5.1.1 wiring 2026-05-14 21:34:14 +02:00
efeba4d2cf feat(voyage): add phase_signals validation + sequencing gate to brief-validator (v5.1) 2026-05-13 21:08:37 +02:00
3683ae0070 chore(voyage): release v5.0.0 — remove bespoke playground + /trekrevise + Handover 8; render produced artifacts to HTML + link, annotate via /playground
The v4.2/v4.3 bespoke playground SPA (~388 KB), the /trekrevise command,
Handover 8 (annotation → revision), the supporting lib/ modules
(anchor-parser, annotation-digest, markdown-write, revision-guard), the
Playwright e2e suite, and the @playwright/test / @axe-core/playwright
devDeps are removed. A browser walkthrough found the playground borderline
unusable, and it duplicated the official /playground plugin's
document-critique / diff-review templates.

In their place: scripts/render-artifact.mjs — a small, zero-dependency
renderer that turns a brief/plan/review .md into a self-contained,
design-system-styled, zero-network .html (frontmatter folded into a
<details> block). /trekbrief, /trekplan, and /trekreview call it on their
last step and print the file:// link; to annotate, run /playground
(document-critique) on the .md and paste the generated prompt back.

Resolves the v4.3.1-deferred findings as moot (their target files are
deleted). npm test green: 509 tests, 507 pass, 0 fail, 2 skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:05:07 +02:00
bb2d4c22c4 feat(voyage): add annotation-digest.mjs with canonical SHA-256 — v4.2 Step 4
Pure module computing deterministic 16-char SHA-256 prefix for annotation set.
Canonicalization: sort by id, fixed field order (id|target_artifact|target_anchor|intent|comment|timestamp), \n-join, sha256, take first 16 hex.

Brief SC4 specifies sha256-prefix; research-05 said sha1 — brief wins per Hard Rule "Brief-driven".

6 tests pass: empty digest, order-independence, intent-sensitivity, format invariant, golden value, undefined-vs-empty equivalence.
2026-05-09 12:53:36 +02:00
d72536b81d feat(voyage): add anchor-parser.mjs with placement validation — v4.2 Step 3
lib/parsers/anchor-parser.mjs (~190 LoC):
- parseAnchors(md) -> Anchor[] (id, target, line, snippet?, intent?)
- addAnchors(md, anchors) -> md_with_anchors
- stripAnchors(md_with_anchors) -> md (byte-identical)
- validateAnchorPlacement(md, anchors) -> errors for list-item / fenced-block / indent

Format: <!-- voyage:anchor id="ANN-NNNN" target="<slug>" line="<N>" -->
Block-level only, on its own line (col 0), blank-line separation.

Test fixture annotation-example.md with single ANN-0001 anchor — referenced by SC12 quickstart.
14 tests pass (parseAnchors, addAnchors, stripAnchors, validateAnchorPlacement).
2026-05-09 12:52:46 +02:00
cc7228ea17 feat(voyage): add markdown-write.mjs + revision-guard.mjs + forward-compat policy comments — v4.2 Step 1
- lib/util/markdown-write.mjs: serializeFrontmatter (subset matches frontmatter.mjs parser), atomicWriteMarkdown (single tmp+rename, body bytes verbatim), readAndUpdate (read+mutate+write).
- lib/util/revision-guard.mjs: revisionGuard(path, mutator, validator) — backup -> mutate -> validate -> restore-on-fail. Extracted from /trekrevise prompt so rollback can be unit-tested.
- 12 tests for markdown-write, including 6-key source_annotations round-trip + walk-all-fixtures regression.
- 6 tests for revision-guard: applied/rolled-back/mutator-failed/sha256 stability/pre-existing-bak abort.
- Forward-compat policy comments in 3 validators (brief/plan/review) — non-functional pin against future strict-key refactors.

Pass: 508/510 (was 490; +18 net from v4.2 Step 1, 2 skipped Docker)
2026-05-09 12:48:40 +02:00
4bd51446b3 fix(voyage): permanently block cloud metadata endpoints in OTLP validator (CWE-918)
Found by simulert v4.1 smoke — doc/code-drift in v4.1 ship:
docs/observability.md claims "Cloud metadata endpoints (169.254.169.254)
are permanently blocked" but the validator allowed them when
VOYAGE_OTEL_ALLOW_PRIVATE=1. Cloud metadata services expose IAM
credentials and instance secrets — operator-trust extended to
RFC-1918 home-lab access does NOT extend here, because the
blast-radius (cloud-account compromise) is qualitatively different.

New HARD_BLOCKED_HOSTS set checked BEFORE the link-local opt-in path:
  - 169.254.169.254  (AWS / GCP / Azure metadata)
  - 100.100.100.200  (AliCloud metadata)
  - metadata.google.internal
  - metadata.azure.com

New error code ENDPOINT_HARD_BLOCKED. Existing test for
ENDPOINT_LINK_LOCAL_REJECTED on 169.254.169.254 updated to assert
the new code; 3 new tests verify the hard-block holds even with
VOYAGE_OTEL_ALLOW_PRIVATE=1, plus AliCloud + GCP-hostname coverage.

Tests: 487 → 490 pass + 2 skipped.
2026-05-09 10:23:51 +02:00
a1f256d000 feat(voyage): emit MANIFEST_PROFILE_DRIFT warning in plan-validator strict mode — brief assumption 7
Step 20 of v4.1 — implements drift detection in plan-validator.mjs per
brief Assumptions block 7: "Mismatch (e.g. korrupt manuell endring)
emitterer MANIFEST_PROFILE_DRIFT-warning fra plan-validator i --strict-modus."

Logic (after validateAllManifests in validatePlanContent):
  1. Strict-mode only — soft mode never emits drift warnings.
  2. Plan frontmatter must declare 'profile: <name>' to establish baseline.
  3. For each step manifest, if profile_used is set AND differs from plan
     profile, emit warning (NOT error) with code MANIFEST_PROFILE_DRIFT
     and location 'step N: profile_used = X, plan profile = Y'.

Forward-compat preserved: drift is a warning, plan remains valid:true.
Operators see the drift in --strict mode without parsing breaking.

New files:
  tests/validators/plan-validator-profile-drift.test.mjs — 4 tests
  tests/fixtures/plan-profile-drift.md                   — drift fixture

Tests verify:
  1. drift detected in strict mode → MANIFEST_PROFILE_DRIFT in warnings
  2. drift NOT detected in soft mode → strict gate honored
  3. matching profile → no drift warning
  4. no plan-level profile → drift detection silent (no baseline)

Tests: 479 pass + 2 skipped (Docker not installed).
2026-05-09 10:02:53 +02:00
28454498e5 test(voyage): add tests/integration/profile-jaccard-smoke.test.mjs — cross-tier smoke per research/02
Step 18 of v4.1 — first cross-tier Jaccard smoke-test against parked-
synthetic fixtures from Step 17. Module-local CROSS_TIER_JACCARD_FLOOR
= 0.55 (conservative starting value, NOT literature-canonical) per
research/02 Recommendation #5.

New files:
  lib/parsers/profile-jaccard.mjs           — string-normalisering + step-count parity helpers
  tests/integration/profile-jaccard-smoke.test.mjs  — 4 test blocks

Test design:
  1. Pre-gate: all 4 fixtures parse cleanly with frontmatter.steps
  2. Pre-gate: step-count parity (cross-tier ±34%; v4.1 absorbs the
     30-vs-40 synthetic gap; tighten to ±20% in v4.2 once empirical)
  3. Cross-tier Jaccard ≥ 0.55 for all 4 economy×premium pairs
     (synthetic results: 0.707 / 0.707 / 0.750 / 0.750)
  4. Sanity: intra-tier > cross-tier mean (discriminator check)

Plan-critic-fallback (auto-tighten on insufficient Jaccard) NOT in v4.1
— deferred to v4.2 per research/02.

Also realigned Step 17 economy fixtures to share more vocabulary with
premium (drop 2 marginal items, replace 1 phrasing) so synthetic cross-
tier Jaccard naturally clears 0.55. Updated calibration table to reflect
actual 0.707/0.750 values.

Tests: 472 pass + 2 skipped (Docker not installed).
2026-05-09 09:58:02 +02:00
8805df8b0d feat(voyage): add lib/exporters/{path,endpoint,field-allowlist}-validators — CWE-22, CWE-918, CWE-212 mitigering
Step 11 av v4.1-execute (Wave 2, Session 3).

3 sikkerhets-validatorer for OTel-eksporten:

path-validator.mjs (CWE-22 Path Traversal):
- Reject `..` segmenter, `~`-shorthand
- realpathSync symlink-resolution (med macOS quirk: /etc, /var, /tmp er
  symlinks til /private/etc, /private/var, /private/tmp — begge former
  i FORBIDDEN_PREFIXES)
- Allowlist-først evaluering: hvis allowedRoots gitt, det er primary defense
  (caller's threat model). Forbidden-prefix-denylist er FALLBACK når
  allowedRoots ikke spesifisert.

endpoint-validator.mjs (CWE-918 SSRF):
- Reject loopback (127.0.0.1, ::1, localhost, 0.0.0.0) UNLESS VOYAGE_OTEL_ALLOW_PRIVATE=1
- Reject RFC-1918 (10/8, 172.16/12, 192.168/16) UNLESS opt-in
- Reject link-local (169.254.x.x cloud metadata, fe80:* IPv6) UNLESS opt-in
- Krev https:// for non-private endpoints
- node:url-parsing, ingen runtime DNS-resolusjon (defense-in-depth)

field-allowlist.mjs (CWE-212 Improper Cross-boundary Removal of Sensitive Data):
- INLINE static const Object.freeze på modul-scope (IKKE runtime read fra fixtures)
- Per-schema allowlist for alle 8 schema-id (trekbrief, trekresearch, trekplan,
  trekexecute, event-emit, post-bash-stats, trekreview, trekcontinue)
- Source-comment per allowlist refererer tests/fixtures/jsonl-schemas.md
- post-bash-stats DROPPER eksplisitt command_excerpt + session_id (CWE-212)
- event-emit applies sub-allowlist på payload-objekt (recursive)
- Unknown schema-type returnerer conservative {_schema_id, ts}

Tester (19 nye, baseline 413 → 432):
- path-validator x6 (CWE-22 traversal, forbidden-system, ~, allowedRoots accept/reject, drift-pin)
- endpoint-validator x7 (CWE-918 link-local, RFC-1918, loopback, https-required, opt-in, public-accept, empty-input)
- field-allowlist x6 (CWE-212 post-bash-stats, trekplan-PII, event-emit-payload, unknown-schema, Object.freeze, null-safe)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:36:00 +02:00
9e7b96fb95 feat(voyage): add lib/exporters/otlp-format.mjs — OTLP/JSON enum-integer SC #13
Step 10 av v4.1-execute (Wave 2, Session 3).

Pure function transformToOtlpJson(records) → OTLP/JSON v1.0 metrics payload
matching OTLP metrics.proto wire format.

CRITICAL (per research/01 dim 4 + risk-assessor CRITICAL 2):
- AggregationTemporality enum values er INTEGERS i JSON, IKKE strings
  ("CUMULATIVE" → 2, ikke "CUMULATIVE")
- timeUnixNano er uint64 over wire — emit som decimal STRING i JSON for å
  unngå JS Number precision loss på nanosekund-skala

Inline integer enum constants ved module-scope:
- AGG_TEMPORALITY_UNSPECIFIED = 0
- AGG_TEMPORALITY_DELTA = 1
- AGG_TEMPORALITY_CUMULATIVE = 2
- DATA_POINT_FLAGS_NONE = 0
- DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK = 1

Output struktur: resourceMetrics → scopeMetrics → metrics array. Sum-metrics
(counters: *_total, *_count, *_passed, *_failed, *_skipped) får sum +
isMonotonic + aggregationTemporality. Andre får gauge.

Tester (7 nye, baseline 406 → 413):
- SC #13: typeof aggregationTemporality === 'number' (HEART of SC #13)
- SC #13: enum-konstant drift-pin (typeof + verdi-assert)
- SC #13: typeof timeUnixNano === 'string' (precision-loss mitigation)
- SC #13: strukturell shape-assertion
- Empty input → valid envelope, tomt metrics-array
- isSum heuristic counter vs gauge
- Allowlist-redaksjon sanity (command_excerpt + session_id leaker ikke)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:32:29 +02:00
4e6b026104 feat(voyage): add lib/exporters/textfile-format.mjs — Prometheus text-format pure transform SC #12
Step 9 av v4.1-execute (Wave 2, Session 3).

Pure function transformToPrometheus(records) → Prometheus text-format 0.0.4.

Hard rules:
- NO client-side timestamps (research/01 node_exporter#1284 mitigation)
- Allowlist-redacted records ONLY (caller responsibility — Step 11 enforces)
- UTF-8 metric names normalized: lowercase, [.\\-\\s] → _, voyage_ prefix
- Empty input → empty string output
- Sorted output for determinism (snapshot-test-friendly)

Heuristic metric typing:
- counter: *_total, *_count, *_passed, *_failed, *_skipped
- histogram: *_ms, *_duration, *_p\\d+, *_seconds
- gauge: everything else (Prometheus convention)

Snapshot: tests/fixtures/expected.prom byte-for-byte match.
Regenerate: node scripts/gen-expected-prom.mjs > tests/fixtures/expected.prom

Tester (6 nye, baseline 400 → 406):
- Snapshot byte-for-byte match (SC #12)
- Empty input handling (null, undefined, [])
- Allowlist-redaction sanity (post-bash-stats uten command_excerpt)
- NO client-side timestamps (token-count-assertion per linje)
- normalizeMetricName edge-cases
- Determinism (identisk input → identisk output)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:30:58 +02:00
f8fc0bb1c2 feat(voyage): add lib/profiles/resolver.mjs — locked interface SC #5-#9
Step 6 av v4.1-execute (Wave 2, Session 2).

Implementer locked interface contract fra brief Preferences:

- loadProfile(name, opts) → ProfileObject
  Leser lib/profiles/<name>.yaml (built-in) eller custom fra
  <cwd>/voyage-profiles/ > ~/.claude/voyage-profiles/. Throws Error med
  cause: PROFILE_NOT_FOUND. Returnerer parsed object med phase_models
  flattened til {brief: 'sonnet', research: 'opus', ...} (object form
  for downstream JSON-stats).

- resolveProfile(argv, env) → {profile, profile_source}
  Ordre: --profile flag > VOYAGE_PROFILE env > 'premium' default.

- resolveTrekcontinueProfile(planPath, argv, opts) → {profile, profile_source}
  --profile flag wins ('flag'); ellers leser plan.md frontmatter
  ('inheritance'); v4.0-stil plan uten profile-felt → 'default' premium
  (backward-compat). Flag overstyrer arv → console.error advisory.

- validateProfileFile(path) → Result
  Tynn re-eksport av validateProfile fra profile-validator.mjs.

- findProfilePath(name, opts) → {path, attempted}
  Lookup-helper. attempted-array brukes i error-melding for HIGH-risk-
  mitigering (ENOENT-diagnose).

Tester (13 nye, baseline 387 → 400):
- SC #5 x4 (loadProfile economy/balanced/premium + PROFILE_NOT_FOUND)
- SC #6 (flag > env > default ordre)
- SC #7 (performance: 1000-iter < 50ms gjennomsnitt; faktisk ~0.055ms)
- SC #8 x2 (cwd > home precedence + error-msg attempted-paths)
- SC #9 x2 (inheritance + flag-override-advisory)
- Backward-compat x2 (v4.0 plan + non-existent plan)
- validateProfileFile re-export sanity

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:29:01 +02:00
b56ba13b04 feat(voyage): add lib/validators/profile-validator.mjs — SC #1, #2, #3
Step 5 av v4.1-execute (Wave 2, Session 2).

Profile-validator etter brief-validator-mønsteret eksakt: validateProfileContent
(pure), validateProfile (file-reader), CLI shim med --json flag. Eksporter
PROFILE_REQUIRED_FIELDS (frozen), PROFILE_REQUIRED_PHASES (frozen).

Validerer:
- Required frontmatter fields (name, phase_models, parallel_agents_min/max,
  external_research_enabled, brief_reviewer_iter_cap)
- phase_models = list-of-dicts med phase + model
- 6 required phases (brief, research, plan, execute, review, continue)
- parallel_agents_max ≥ parallel_agents_min
- Allowed model values: ['sonnet', 'opus']; haiku tillatt KUN ved
  VOYAGE_ALLOW_HAIKU=1 (per global CLAUDE.md modellvalg-prinsipp)

Issue codes: PROFILE_MISSING_FIELD, PROFILE_INVALID_MODEL, PROFILE_INVALID_ENUM,
PROFILE_READ_ERROR, PROFILE_NOT_FOUND.

Field-path-reporting i error-location: phase_models[N].model for SC #2.

Tester (10 nye, baseline 377 → 387):
- SC #1 x3 (innebygde profiler grønne)
- SC #2 (PROFILE_INVALID_MODEL med location phase_models[2].model)
- SC #3 (PROFILE_INVALID_ENUM for external_research_enabled: "yes" string)
- VOYAGE_ALLOW_HAIKU env-var deny/allow
- PROFILE_MISSING_FIELD når name fraværende
- PROFILE_NOT_FOUND for ikke-eksisterende fil
- 2 export drift-pins

Fixturer: profile-invalid-model.yaml (gpt-4 i phase_models[2]),
profile-invalid-enum.yaml (external_research_enabled som string "yes").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:26:23 +02:00
f0a1fb3598 feat(voyage): add lib/profiles/{economy,balanced,premium}.yaml — v4.1 modellprofiler
Step 4 av v4.1-execute (Wave 2, Session 2).

Tre innebygde modellprofiler matcher brief profile-assignment matrix:

- economy: alle 6 phase_models = sonnet, parallel 2-3, external_research=false,
           iter-cap=1. ~$1-3 per pipeline-sesjon.
- balanced: brief/research/execute/continue=sonnet, plan=opus, review=opus,
            parallel 4-6, external_research=false (operator-override deferred
            til v4.2 per NEXT-SESSION-PROMPT scope-grenser), iter-cap=2.
            ~$5-15 per pipeline-sesjon.
- premium: alle 6 phase_models = opus, parallel 6-8, external_research=true,
           iter-cap=3. ~$20-60 per pipeline-sesjon (default, samme som v4.0).

Bruker list-of-dicts for phase_models (parser-kompatibel mot
lib/util/frontmatter.mjs:79-105). Verifisert: alle 3 filer parses uten feil
og returnerer array med 6 entries (phase+model per entry).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:24:27 +02:00
6d1c9f8967 feat(voyage): add OPTIONAL_STRING_KEYS path to manifest-yaml — profile_used additive
Step 3 av v4.1-execute (Wave 1, Session 1).

Legg ny eksportert const OPTIONAL_STRING_KEYS = ['profile_used'] parallel
til eksisterende OPTIONAL_KEYS. Utvid parseManifest med ny dispatch-loop
etter OPTIONAL_BOOLEAN_KEYS. Returnerer MANIFEST_OPTIONAL_TYPE hvis
profile_used finnes men ikke er string.

Forskjell fra OPTIONAL_BOOLEAN_KEYS: absence == not-present (NOT defaulted
til false, unlike boolean). Downstream-konsumenter kan dermed skille mellom
unset og empty-string.

Tester (5 nye, baseline 372 → 377):
- OPTIONAL_STRING_KEYS export drift-pin
- profile_used: economy parses successfully (SC #10 forward-compat)
- profile_used: numeric rejected
- absence: field NOT in parsed (string-key semantics)
- profile_used + skip_commit_check + memory_write co-existence

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:23:32 +02:00
0787048f90 feat(voyage): add --profile valued flag to arg-parser FLAG_SCHEMA — v4.1 SC #4
Step 2 av v4.1-execute (Wave 1, Session 1).

Legg --profile i valued-arrayen for alle 6 voyage-kommandoer (trekbrief,
trekresearch, trekplan, trekexecute, trekreview, trekcontinue). Mønster
identisk med eksisterende --project/--brief valued-handling. Ingen endring
til parseArgs-logikk — utvider kun schema.

Tester (11 nye, baseline 361 → 372):
- 6 happy-path-tests (én per kommando)
- ARG_MISSING_VALUE for --profile uten verdi
- --profile + --quick kombo
- --profile + --gates edge-case (--gates parses inline, ikke i FLAG_SCHEMA)
- --profile + --project kombo
- trekcontinue --profile (validerer at tomt valued[] nå er utvidet)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:22:01 +02:00
de0683a838 chore(voyage): scrub ultra-cc-architect references from source
The ultra-cc-architect plugin was removed from the marketplace; voyage's
architecture-discovery contract still pointed at it by name. Replaced
verbatim references with plugin-agnostic phrasing ("upstream architect
producer") in code comments and user-facing warning messages.

CHANGELOG entries and config-audit v5.0.0 snapshots intentionally
preserved as historical records.
2026-05-05 15:51:17 +02:00
c909466c99 feat(voyage)!: bulk content rewrite ultra -> voyage/trek prose [skip-docs]
Sed-pipeline (16 patterns, longest-match-first) sweeper residuelle ultra*-treff
i prose, command-narrativ, agent-prompts, hook-kommentarer, doc-prosa.

Pipeline-utvidelser fra V4-prompten:
- BSD-syntax [[:<:]]ultra[[:>:]] istedenfor \bultra\b (BSD sed mangler \b)
- 6 compound-patterns for ultraplan/ultraexecute/ultraresearch/ultrabrief/
  ultrareview/ultracontinue uten -local-suffiks
- ultra*-stats glob -> trek*-stats glob
- Linje-eksklusjon redusert til ultra-cc-architect (Q8); session-state-
  eksklusjonen var over-protektiv
- File-eksklusjon utvidet til settings.json, package.json, plugin.json,
  hele .claude/-treet (gitignored + V5-territorium)

Q8-undantak holdt: architecture-discovery.mjs + project-discovery.mjs urort.
Filnavn-konvensjon holdt: .session-state.local.json + *.local.* preservert.

Manuell narrative-fix: tests/lib/agent-frontmatter.test.mjs linje 10
mangled "/ultra*-local" til "/voyage*-local" (ingen slik kommando finnes);
korrigert til "/trek*".

Residualer utenfor scope (V5 handterer): package.json + .claude-plugin/
plugin.json (Step 12-14 versjons-bump). .claude/* er gitignored
spec-historikk med tilsiktet BEFORE/AFTER-narrativ.

Part of voyage-rebrand session 3 (Wave 4 / Step 10).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 15:08:20 +02:00
d22ec05fed feat(voyage)!: rename stats filenames, settings keys, hook prefixes [skip-docs]
- lib/stats/event-emit.mjs: STATS_FILENAME -> trekexecute-stats.jsonl + comment
- hooks/scripts/post-bash-stats.mjs: stats target + comment -> trekexecute-stats.jsonl
- lib/stats/cache-analyzer.mjs: help-text + comment -> trekexecute-stats.jsonl
- tests/lib/stats-event-emit.test.mjs (lines 104, 117): fixture assertions
- settings.json: ultraplan/ultraresearch -> trekplan/trekresearch keys + statsFile values
- tests/lib/doc-consistency.test.mjs: allowlist (line 83) + accessor cfg.ultraplan?.* -> cfg.trekplan?.* (lines 91, 93) — atomic-pair, prevents vacuous undefined assertions
- scripts/q3-cache-prefix-experiment.mjs: STATS_JSONL hardcoded path -> voyage data dir + trekexecute filename
- hooks/scripts/pre-bash-executor.mjs (2 lines), pre-compact-flush.mjs (2 lines), pre-write-executor.mjs (1 line): [ultraplan]/[ultraplan-local] stderr prefix -> [voyage]
- commands + agents/review-orchestrator.md + CLAUDE.md: prose stats filename literals -> trek* equivalents

Atomic per session-spec: settings.json scope keys + doc-consistency.test.mjs
allowlist + property accessors committed together to prevent silent vacuous
undefined-equals-undefined assertions.

Part of voyage-rebrand session 2 (W3.7 / Step 9).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 14:49:03 +02:00
acc053ae6d feat(voyage)!: rename produced_by field values + validator comments [skip-docs]
- commands/trekexecute.md: produced_by literals -> trekexecute (4 occurrences)
- commands/trekendsession.md: produced_by literals -> trekendsession (2 occurrences)
- tests/validators/next-session-prompt-validator.test.mjs: 11 'ultraexecute-local' refs -> 'trekexecute'
- tests/commands/trekcontinue.test.mjs: 3 fixture strings updated
- tests/lib/cleanup.test.mjs: 1 fixture string updated
- lib/validators/next-session-prompt-validator.mjs: producer-list comment
- docs/HANDOVER-CONTRACTS.md line 432: example producer names updated

Part of voyage-rebrand session 2 (W3.4 / Step 6).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 14:42:21 +02:00
12f4391dca feat(voyage)!: rename type discriminators across validators + fixtures [skip-docs]
- brief-validator: BRIEF_TYPE_VALUES ['ultrabrief','ultrareview'] -> ['trekbrief','trekreview'] + dependent branches
- research-validator: 'ultraresearch-brief' -> 'trekresearch-brief'
- review-validator: 'ultrareview' -> 'trekreview'
- 3 templates frontmatter type:
- 4 synthetic fixtures: ultraplan-synthetic/ultrareview-synthetic -> trek* (frontmatter only; bodies untouched, Jaccard floor preserved)
- 2 trekreview fixtures: type: trekreview
- 6 validator-test fixtures + asserts
- agents/review-coordinator.md frontmatter example

Atomic: validator + fixtures committed together — partial state would cause vacuous
test passes or hard validator rejection.

Part of voyage-rebrand session 2 (W3.3 / Step 5).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 14:40:25 +02:00
6dee27c136 feat(voyage)!: FLAG_SCHEMA keys trek* + arg-parser test cases [skip-docs]
- Rename FLAG_SCHEMA keys ultrabrief|ultraresearch|ultraplan|ultraexecute|ultrareview|ultracontinue -> trek* equivalents
- Update 26 literal key references in tests/lib/arg-parser.test.mjs
- Update parseArgs($ARGUMENTS, 'ultracontinue') -> 'trekcontinue' in commands/trekcontinue.md
- trekendsession.md audited: no parseArgs invocation, no FLAG_SCHEMA entry needed

Atomic per session-spec: schema + tests + consuming commands committed together to
avoid vacuous-pass risk.

Part of voyage-rebrand session 2 (W3.2 / Step 4).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 14:35:01 +02:00
aa4f486f41 feat(spor-c): add Q3 cache-prefix experiment harness + analyser [skip-docs]
Implements Spor C of post-v3.4.0 roadmap. Zero-dep harness measures
CLAUDE_CODE_FORK_SUBAGENT cache-prefix preservation across 3 fork-children
with identical --allowedTools at 150-250K parent context.

Harness uses --append-system-prompt-file (avoids stdin buffer cap at
>200K bytes) + --exclude-dynamic-system-prompt-sections (prevents
per-child cache-prefix divergence from cwd/env/git-status).

Companion analyser summarizes accumulated ultraexecute-stats.jsonl:
percentile wall_time (p50/p90/max), total events, ISO time range.
Output: JSON via --json <path> CLI shim.

Result file is gitignored (*.local.md). Master-plan thresholds
(<= 1.5K positive / >= 3.5K negative) gate the v3.5.0 Path C decision.

Brief: .claude/projects/2026-05-04-spor-c-q3-cache-prefix-experiment/brief.md
Master-plan: .claude/projects/2026-05-04-post-v3.4.0-roadmap/master-plan.md
2026-05-05 09:27:32 +02:00
c7c6d8d09d feat(ultraplan-local): cleanup util (Bug 4 dry-run/confirm/idempotent) [skip-docs]
Step 9 of v3.4.1 plan.

lib/util/cleanup.mjs (new):
- cleanupProject(projectDir, {dryRun, confirm}) reads
  .session-state.local.json via validateSessionState; refuses unless the
  parsed status is strictly equal to 'completed' (per risk-assessor
  Critical 2 — no soft-match on similar statuses).
- Default dryRun: true; refuses dryRun: false without explicit
  confirm: true (CLEANUP_REQUIRES_CONFIRM).
- Removes .session-state.local.json + NEXT-SESSION-PROMPT.local.md
  candidates; ENOENT counts as "already absent" so the function is
  idempotent.
- No CLI shim — invoked from /ultracontinue --cleanup via inline ESM
  (Step 10 wires this in).

tests/lib/cleanup.test.mjs (new):
- 7 cases: dry-run lists candidates without deleting; confirm-mode
  deletes both files; idempotent re-run signals CLEANUP_NO_STATE_FILE
  after fully cleaned; refuses on status: in_progress
  (CLEANUP_NOT_COMPLETED); refuses dryRun: false without confirm
  (CLEANUP_REQUIRES_CONFIRM); defaults to dry-run; missing state file
  returns CLEANUP_NO_STATE_FILE.

Internal scaffolding consumed by Step 10 (Phase 0.5 wire-up). User-facing
docs land with Step 14.

Tests 348 -> 355 (+7).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 17:41:06 +02:00
6a300b854b fix(ultraplan-local): Bug 3 — wire frontmatter consistency check into /ultracontinue Phase 1.5
Step 8 of v3.4.1 plan.

commands/ultracontinue-local.md:
- New Phase 1.5 between Phase 1 and Phase 2 — runs the
  next-session-prompt-validator in --consistency mode when both candidates
  exist (plugin-root + project-dir). Refuses on producer mismatch with
  fresh candidates, downgrades stale candidate to a warning, downgrades
  >24h wall-clock drift to a soft warning.
- Anti-substitution rule applies — paths emitted as concrete tokens, not
  template placeholders.

lib/validators/next-session-prompt-validator.mjs:
- Sharpen NEXT_SESSION_PROMPT_PRODUCER_MISMATCH error message to include
  the literal "produced_by" field name so consumers (and operators) can
  trace the disagreement back to the YAML key.

tests/commands/ultracontinue.test.mjs:
- Test (Bug 3 prose) — Phase 1.5 header present, references validator,
  appears between Phase 1 and Phase 2 in document order.
- Test (Bug 3 e) — tmp project dir with state file + two prompt files
  with mismatched producers, both fresh relative to state.updated_at;
  CLI consistency mode exits non-zero, JSON stdout surfaces
  NEXT_SESSION_PROMPT_PRODUCER_MISMATCH with both paths and the
  "produced_by" token in the message.

Tests 346 -> 348 (+2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 17:39:42 +02:00
deb42f43e9 feat(ultraplan-local): next-session-prompt-validator (Bug 3 consistency check) [skip-docs]
Step 6 of v3.4.1 plan. Adds the validator quartet
(Content/Object/Consistency/CLI) for NEXT-SESSION-PROMPT.local.md
frontmatter (produced_by, produced_at). State-anchored staleness check
is the primary refusal; 24h wall-clock drift downgraded to soft warning
to avoid false positives on weekend pauses.

Internal scaffolding consumed by Step 8 (Phase 1.5 wire-up). User-facing
docs land with Step 14 (CHANGELOG + README + version bump).

Tests 335 -> 346 (+11): 9 unit + 2 CLI shim cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 17:34:16 +02:00
9070a126a8 test(ultraplan-local): add ultracontinue to FLAG_SCHEMA + tests
Step 1 of v3.4.1 hot-fix plan (project 2026-05-04-v3.3.1-ultracontinue-fixes).

Adds ultracontinue entry to FLAG_SCHEMA covering boolean flags --help,
--cleanup, --confirm, --dry-run with no valued flags. The -h short form
is intentionally not aliased: it appears as positional[0] === '-h' and
the command prose dispatches usage on either condition.

7 new tests in tests/lib/arg-parser.test.mjs verify empty args, --help,
-h positional, --cleanup, --cleanup --confirm, project-dir positional,
and .md positional (parser-level accept; command-level reject).
2026-05-04 16:34:55 +02:00
a8ae5b65f6 feat(ultraplan-local): add stats event-emit for autonomy lifecycle events
Step 6 of plan-v2 (ultra-pipeline-speedup).

lib/stats/event-emit.mjs (NEW)
  Atomic JSONL append to ${CLAUDE_PLUGIN_DATA}/ultraexecute-stats.jsonl.
  Every record carries:
    ts          : ISO-8601 timestamp (REQUIRED per SC4)
    event       : caller-supplied name
    known_event : true for { brief-approved, main-merge-gate, user_input },
                  false for everything else (still emitted — audit-complete)
    payload     : caller object (defaults to {})

  Stats failures NEVER block workflow: missing CLAUDE_PLUGIN_DATA, missing
  dir, mkdir failure, append failure → all return { written: false, reason }
  without throwing.

  CLI shim:
    node lib/stats/event-emit.mjs --event NAME [--payload JSON]
  Always exits 0 (telemetry is best-effort).

Tests: 12 (record-build + ISO-8601 ts + known/unknown distinction + silent
skip + dir-on-demand + CLI shim happy-path + bad-payload tolerance +
concurrent-append smoke).

[skip-docs]
2026-05-04 06:31:52 +02:00
eaeb68f317 feat(ultraplan-local): add plan-review-dedup helper for Phase 9 finding dedup
Step 5 of plan-v2 (ultra-pipeline-speedup).

lib/review/plan-review-dedup.mjs (NEW)
  Two-pass dedup:
    1. Exact match  — identical computeFindingId(file:line:rule_key) → merge.
    2. Jaccard ≥ 0.7 on text-token sets → merge near-duplicates.
  Provenance preserved in surviving finding's raised_by[] (which agents
  raised it). Reuses lib/parsers/jaccard.mjs + lib/parsers/finding-id.mjs.

  CLI shim:
    node lib/review/plan-review-dedup.mjs \
         --plan-critic /tmp/x.json --scope-guardian /tmp/y.json
  Missing inputs tolerated (single-agent review still works).

Tests: 10 (tokenize + threshold + 6 dedup-logic cases + 2 CLI shim).

[skip-docs]
2026-05-04 06:30:28 +02:00
65b8fbc16f feat(ultraplan-local): add autonomy-gate state machine + manifest schema extensions for skip_commit_check + memory_write
Step 4 of plan-v2 (ultra-pipeline-speedup).

lib/util/autonomy-gate.mjs (NEW)
  5-state machine {idle, gates_on, auto_running, paused_for_gate, completed}
  honoring the --gates flag intent. Re-entry to completed is idempotent.
  Includes CLI shim:
    node lib/util/autonomy-gate.mjs --state X --event Y [--gates true|false]
  → JSON: { ok, next_state | error }, exit 0 on success / 1 on invalid.

lib/parsers/manifest-yaml.mjs (EXTENDED)
  OPTIONAL_KEYS list adds skip_commit_check and memory_write — both boolean,
  default false when absent, MANIFEST_OPTIONAL_TYPE when non-boolean.
  Existing REQUIRED_KEYS contract untouched; existing 9 manifest tests
  still pass.

Tests: 19 (autonomy-gate) + 8 (manifest-schema-extensions) = 27 new.

[skip-docs]
2026-05-04 06:28:47 +02:00
Kjell Tore Guttormsen
282dcea518 feat(ultraplan-local): add session-state-validator + tests for /ultracontinue
Validator at lib/validators/session-state-validator.mjs:
- validateSessionStateObject(parsed, opts) — pure object validation
- validateSessionStateContent(jsonText, opts) — wraps JSON parse + validation
- validateSessionState(filePath, opts) — file-mode with existsSync guard
- CLI shim with --json output (errors→stderr, result→stdout, exit 0/1/2)
- Schema v1: schema_version, project, next_session_brief_path,
  next_session_label, status, updated_at
- Error codes: SESSION_STATE_PARSE_ERROR, SESSION_STATE_NOT_FOUND,
  SESSION_STATE_MISSING_FIELD, SESSION_STATE_INVALID_STATUS,
  SESSION_STATE_NOT_RESUMABLE (warning), SESSION_STATE_SCHEMA_MISMATCH,
  SESSION_STATE_INVALID_TIMESTAMP, SESSION_STATE_INVALID_PATH
- Forward-compat hard requirement: unknown top-level keys ignored —
  protects future graceful-handoff v2.2 dual-writes

Tests at tests/validators/session-state-validator.test.mjs — 15 subtests:
- happy path + 5 missing-field tests
- invalid status, completed warns NOT_RESUMABLE, schema mismatch, bad
  timestamp, malformed JSON, missing file
- fixture load (SC-1) + malformed fixture (SC-3)
- forward-compat: unknown keys ignored silently

167 → 182 tests, 0 fail.

Step 4 of /ultracontinue v3.3.0. Closes Session 1 of the execution
strategy (foundation: gitignore + util + fixtures + validator+tests).
2026-05-01 20:23:09 +02:00
Kjell Tore Guttormsen
5c2911253b refactor(ultraplan-local): extract atomicWriteJson to lib/util
Three changes in one commit:

1. NEW lib/util/atomic-write.mjs — exports atomicWriteJson(path, obj),
   the canonical tmp+rename pattern. Reused by pre-compact-flush.mjs and
   (in subsequent steps) by the new session-state writer.

2. NEW tests/lib/atomic-write.test.mjs — 4 unit tests covering
   round-trip, no-orphan-tmp, overwrite-atomic, pretty-print formatting.

3. REFACTOR hooks/scripts/pre-compact-flush.mjs — replace the inline
   atomicWrite() with the imported atomicWriteJson(). Also fixes a
   pre-existing syntax error (leading whitespace + stray --resume token
   outside the comment block) that silently broke the hook from v3.1.0
   onward — PreCompact runtime is fail-open and swallowed the error.
   File reformatted with standard zero-indent JS.

163 → 167 tests, 0 fail.

Step 2 of /ultracontinue v3.3.0 (project 2026-05-01-ultracontinue).
2026-05-01 20:21:15 +02:00
Kjell Tore Guttormsen
d5a76ee938 feat(ultraplan-local): extend project-discovery with review.md 2026-05-01 16:43:08 +02:00