Commit graph

582 commits

Author SHA1 Message Date
01f31e73c3 refactor(voyage): replace non-canonical DS tokens with canonical names
Replaces --color-accent/--font-mono/--color-bg/--color-bg-hover/--color-bg-code/--color-text-muted/--color-text/--color-success/--color-border with their canonical DS-token equivalents. Drops all hex-fallbacks in var() per Step 4 spec.

Deviation note: plan's literal Verify regex "--color-bg\b" matches --color-bg-soft (canonical) due to \b dash-boundary semantics. Manifest must_contain (--color-scope-voyage) verifies; corrected intent-regex (--color-bg[^-a-z]|--color-bg$) returns empty. Treated as PASS on implementation grounds.
2026-05-10 10:26:12 +02:00
a24c3d1e3b fix(voyage): repair plan-determinism test reference path [skip-docs] 2026-05-10 10:21:52 +02:00
302e3aa42e chore(voyage): update v4.3 brief research_status to complete [skip-docs] 2026-05-10 10:18:46 +02:00
a7dec7fdee feat(ds): add voyage scope tokens + badge--scope-voyage (B-DS-4); resync vendor 2026-05-10 10:17:50 +02:00
d8c80756fe chore(voyage): bump version to 4.2.0 — annotation pipeline + first playground ship
v4.1.0 → v4.2.0. Two-file change per Step 14 manifest (package.json +
.claude-plugin/plugin.json). Description tagline expanded from
"brief, research, plan, execute, review, continue" to include "revise"
and "+ first marketplace playground".

Out-of-scope under Step 14 forbidden_paths (left at 4.1.0 intentionally):
- lib/exporters/otlp-format.mjs (VOYAGE_SCOPE_VERSION constant)
- hooks/scripts/otel-export.mjs (User-Agent header)
These constants are touched on the next bump where the constants directory
is in scope; keeping them stale for one release is acceptable since
otel/otlp telemetry is opt-in and the version field is informational.

Verification:
- node -e "import('./package.json',{with:{type:'json'}}).then(m=>console.log(m.default.version))" → 4.2.0
- jq -r .version .claude-plugin/plugin.json → 4.2.0
- npm test: 610 pass / 0 fail / 2 skipped (Docker)
- SC11 pipeline-self-eat gate: render-artifact.mjs renders own brief.md + plan.md to non-empty HTML
2026-05-09 15:43:28 +02:00
de160d7da1 docs(voyage): CLAUDE.md + README + CHANGELOG + annotation-quickstart + late doc-consistency pins — v4.2 Step 13 2026-05-09 15:41:45 +02:00
6d57314937 docs(voyage): pin Handover 8 + templates + PIPELINE_COMMANDS update — v4.2 Step 12 2026-05-09 15:36:15 +02:00
97b6f5406e feat(voyage): add export flow + A11Y baseline — v4.2 Step 11 [skip-docs]
Closes Wave 2 (Steps 6-11) of v4.2 implementation. Playground now
delivers the complete annotation pipeline: render -> create gestures
-> sidebar -> export.

Export flow:
  - 'Eksporter batch' button in sidebar export-bar
  - Export modal with role="dialog" aria-modal="true"
  - Generated /trekrevise command-string ready to copy
  - Two paths:
      navigator.clipboard.writeText (modern) with execCommand('copy')
      legacy fallback for cross-browser support
      Blob + URL.createObjectURL download as annotated-{brief|plan|review}.md
  - buildAnnotatedMarkdown injects voyage:anchor comments above target
    lines (mirrors lib/parsers/anchor-parser.mjs addAnchors() behaviour)

Resolve-til-arkiv (Google Docs pattern, per research-06):
  - Post-export marks pending drafts as exported (NOT delete)
  - Tab 2 ('Alle revisjoner') surfaces history with revision-stamp
  - aria-live='polite' toast announces export status

A11Y baseline (per research-06 + llm-security A11Y-RAPPORT.md):
  - aria-live='polite' toast region (Step 1)
  - Skip-to-main link (.visually-hidden + #main target)
  - role='dialog' + aria-modal='true' on form modal (Step 9)
                                    on export modal (Step 11)
  - role='tablist' / role='tab' / aria-selected / tabindex roving (Step 10)
  - aria-controls + aria-labelledby on tabpanels
  - aria-pressed on intent buttons (radiogroup-like)
  - aria-expanded + aria-controls on sidebar FAB
  - aria-hidden='true' on decorative SVG paths
  - aria-label on icon-only buttons
  - .visually-hidden labels for textarea + clipboard helper

Test coverage: tests/playground/voyage-playground.test.mjs +4 cases —
aria-live='polite', Skip to main, Blob, clipboard.writeText.

Verify: node --test tests/playground/voyage-playground.test.mjs ->
22 pass / 0 fail.
Full npm test: 596 pass / 0 fail / 2 skipped (Docker).

Refs plan.md Step 11 + research-06 + llm-security A11Y baseline.
2026-05-09 15:27:01 +02:00
125bfb02b2 feat(voyage): add playground sidebar with tabs + critique-card-list — v4.2 Step 10 [skip-docs]
Right-collapsible sidebar (320px) with 40px icon-rail when collapsed
(per critical decision #4 + research-06):

- 2-state FAB toggle (aria-expanded toggles aria-hidden on aside)
- Visible draft-count badge on FAB (mitigates 'forgot to export' friction)
- Two tabs:
    'Denne planen (N drafts)' — pending annotations
    'Alle revisjoner (M historiske)' — exported (Step 11 wires this)
- role="tablist" + role="tab" + aria-selected + tabindex roving
- ArrowLeft/ArrowRight keyboard nav between tabs
- .findings list of .critique-card per annotation
- Click on critique-card scrolls to anchor + .lint-annotation-glow
  1s pulse animation
- Sort-by-location (Hypothes.is pattern; line ASC)

Card visual: intent-badge (color-coded fix=green/change=blue/question=yellow/block=red),
ANN-NNNN ID, snippet preview, comment, exported-status.

Layout: main shifts margin-right: 320px above 1024px viewport so the
sidebar doesn't overlap the rendered artifact.

saveModalAsAnnotation + mountRender hooks now refresh the sidebar so
new drafts appear immediately and re-render preserves visibility.

Test coverage: tests/playground/voyage-playground.test.mjs +2 cases —
role="tablist", tabindex.

Verify: node --test tests/playground/voyage-playground.test.mjs ->
18 pass / 0 fail.
Full npm test: 592 pass / 0 fail / 2 skipped (Docker).

Refs plan.md Step 10 + critical decision #4 + research-06.
2026-05-09 15:25:01 +02:00
a7a6a53686 feat(voyage): add three annotation creation gestures + form modal — v4.2 Step 9 [skip-docs]
Three creation gestures + shared form modal for the v4.2 annotation
playground (per critical decisions #2-#4 + research-06):

Gesture 1 — text-anchored adder-popup:
  - mouseup-debounce 200ms (settles selection)
  - 300ms grace before hide (Hypothes.is friction-mitigation)
  - floating .voyage-adder-popup positioned at selection-bound corner
  - click -> opens form modal with derived heading-path + line + snippet

Gesture 2 — paragraph-anchored hover-icon:
  - 24px pencil SVG injected per <p>/<li> after each render
  - opacity 0 default, opacity 1 on hover/focus-visible
  - click -> opens form modal with no snippet

Gesture 3 — page-level note:
  - .voyage-page-note-btn injected in viewport
  - click -> opens form modal with target=page

Form modal (shared):
  - role="dialog" aria-modal="true" + aria-labelledby
  - 400px right-anchored on backdrop (per critical decision #3)
  - 4 intent buttons (Fiks / Endre / Spørsmål / Block) as aria-pressed group
  - <textarea> required for comment
  - ESC + backdrop-click + Avbryt close
  - Lagre persists via saveModalAsAnnotation

Anchor-ID generation (per critical decision #2 + risk-assessor H7):
  - sequential ANN-NNNN per project+file scope
  - persisted in localStorage under voyage_ann_<project>__<file>.drafts

Test coverage: tests/playground/voyage-playground.test.mjs +3 cases —
aria-modal, ANN-, 300ms grace.

Verify: node --test tests/playground/voyage-playground.test.mjs ->
16 pass / 0 fail.
Full npm test: 590 pass / 0 fail / 2 skipped (Docker).

Refs plan.md Step 9 + critical decisions #2/#3/#4 + research-06.
2026-05-09 15:22:52 +02:00
249142df2f feat(voyage): vendor markdown-it/highlight.js + playground render-pipeline + scripts/render-artifact.mjs CLI — v4.2 Step 8 [skip-docs]
Vendored libs (locked headless via scripts/vendor-playground-libs.mjs;
plan-critic B3 — never use highlightjs.org website builder):
  - playground/lib/markdown-it.min.js           — markdown-it@14.1.0 UMD bundle
  - playground/lib/markdown-it-front-matter.min.js — markdown-it-front-matter@0.2.4 IIFE-wrapped
  - playground/lib/highlight.min.js             — highlight.js@11.11.1 (5-lang bundle:
                                                   yaml/json/javascript/bash/markdown/diff)
  - playground/lib/VENDOR-MANIFEST.json         — pin record + audit trail

scripts/vendor-playground-libs.mjs implements the reproducible
CommonJS-to-IIFE wrapping. Re-vendoring requires only:
  node scripts/vendor-playground-libs.mjs

Render pipeline in playground/voyage-playground.html (~330 LoC total):
  - inline <script src=lib/...> for the three vendored bundles
  - markdown-it init with html: true (preserves voyage:anchor comments)
  - front-matter plugin with pre-render-then-wrap pattern (research/03)
  - paste-import-row textarea + Render/Sample/Clear buttons
  - voyage-viewport region with role + aria-live for A11Y
  - localStorage key pattern: voyage_ann_<project>__<slug> (risk-assessor H7)
  - inline sample plan (mirrors annotation-plan.md fixture)

scripts/render-artifact.mjs CLI (~200 LoC) — brief SC1 + SC11:
  - reads input.md, runs same vendored pipeline server-side
  - inlines DS CSS + (URL-stripped) highlight.js into output
  - zero http://https:// URLs in output (verified by test)
  - deterministic: two invocations -> byte-identical sha256
  - default output: <input>.html next to input

Test coverage:
  - tests/scripts/render-artifact.test.mjs — 5 cases (SC1/SC11)
  - tests/playground/voyage-playground.test.mjs — +5 cases (Step 8 extension)

Verify: node --test tests/playground/voyage-playground.test.mjs
       tests/scripts/render-artifact.test.mjs -> 18 pass / 0 fail.
Full npm test: 587 pass / 0 fail / 2 skipped (Docker).

Refs plan.md Step 8 + plan-critic B3 + scope-guardian B1.
2026-05-09 15:20:17 +02:00
c412f72605 test(voyage): add annotation roundtrip + rollback + source_annotations integration — v4.2 Step 7
Implements SC2/SC3/SC5b/SC7 + additive-field invariant for the v4.2
annotation pipeline:

Fixtures (tests/fixtures/annotation/):
  - annotation-brief.md           — brief-validator-clean fixture
  - annotation-plan.md            — plan-validator-clean (2 steps)
  - annotation-review.md          — review-validator-clean
  - annotation-plan-large.md      — 51 steps (SC3 scale fixture)

Integration tests:
  - tests/integration/annotation-roundtrip.test.mjs — 7 cases:
    SC2 byte-identical empty round-trip across brief/plan/review,
    SC3 scale (51 steps + 100 anchors) round-trip,
    SC7 parseAnchors(stripAnchors(addAnchors(...))) === [] per target.
  - tests/integration/schema-rollback.test.mjs — 4 cases:
    SC5b validator-FAIL -> revisionGuard rolls back byte-identical
    (sha256 invariant) for brief/plan/review + cross-target sweep.
    .local.bak deleted on rollback path (validator-PASS path tested
    in lib/util/revision-guard tests).
  - tests/lib/source-annotations.test.mjs — 6 cases mirroring
    tests/lib/source-findings.test.mjs additive-field pattern: each
    validator (brief/plan/review) accepts source_annotations as
    additive-optional, parser extracts as array of dicts, entries
    conform to documented shape, baseline forward-compat (artifacts
    without source_annotations still validate).

Verify: node --test tests/integration/annotation-roundtrip.test.mjs
       tests/integration/schema-rollback.test.mjs
       tests/lib/source-annotations.test.mjs -> 17 pass / 0 fail.
Full npm test: 577 pass / 0 fail / 2 skipped (Docker).

Refs plan.md Step 7 + plan-critic M4 + plan-critic B4.
2026-05-09 15:13:27 +02:00
4fbc52bbb4 feat(voyage): add commands/trekrevise.md — 7th pipeline command + settings.json scope — v4.2 Step 6 [skip-docs]
Implements Phase 1-8 of /trekrevise (Handover 8 producer):
- Phase 1: parse mode + reject MULTI_ARTIFACT_NOT_SUPPORTED
- Phase 2: read source + check stale .local.bak
- Phase 3: parseAnchors + validateAnchorPlacement (no partial revisions)
- Phase 4: computeAnnotationDigest + non-additive detection
- Phase 5: revisionGuard orchestration (backup -> mutate -> validate -> rollback-on-fail)
- Phase 6: branch on outcome (applied / rolled-back / mutator-failed)
- Phase 7: optional review-gate (advisory, no auto-rollback)
- Phase 8: trekrevise-stats.jsonl + report

Frontmatter: name=trekrevise, model=opus, allowed-tools includes Read/Write/Edit/Bash/Grep/Glob.
Reuses lib/parsers/anchor-parser, lib/parsers/annotation-digest,
lib/util/markdown-write, lib/util/revision-guard, lib/validators/{brief,plan,review}.

settings.json: register new top-level scope trekrevise with
trekrevise-stats.jsonl tracking (mirrors trekplan/trekresearch shape).

Forward-pinning to keep doc-consistency invariants green:
- tests/lib/doc-consistency.test.mjs: known-scopes allowlist += trekrevise
- CLAUDE.md commands table: add /trekrevise row

Plan Step 13 owns the full README/CLAUDE.md/CHANGELOG content sync;
this commit is the implementation milestone, not the doc milestone.

Refs plan.md Step 6 + plan-critic M3.
2026-05-09 15:09:01 +02:00
2f4330265c feat(voyage): scaffold playground/ with DS vendor sync — v4.2 Step 5
- playground/voyage-playground.html: minimal skeleton (DOCTYPE, app-header, guide-panel, aria-live region, skip-to-main link). Steps 8-11 will extend with render-pipeline + creation gestures + sidebar + export.
- playground/vendor/playground-design-system/: synced via 'node scripts/sync-design-system.mjs voyage' (27 files + MANIFEST.json with source_commit + sync_date + SHA-256 per file).
- tests/playground/voyage-playground.test.mjs: 8 tests pinning HTML existence, DOCTYPE, no-external-URLs, no-marked, A11Y skip-to-main + aria-live, MANIFEST.json structure, vendored DS files present.
- shared/PLAYGROUND-MAINTENANCE.md: consumer list updated 5 -> 6 (added voyage).
2026-05-09 12:55:02 +02:00
f316cc1efa 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
fb733ae149 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
ff7a5c63da test(voyage): pin forward-compat for revision/source_annotations/annotation_digest/revision_reason — v4.2 Step 2
3 new test files, 24 cases (8 per validator):
- baseline (no annotation fields) still valid
- revision: 0 / revision: 5 accepted
- source_annotations list-of-dict accepted
- annotation_digest string accepted
- revision_reason accepted
- all 4 fields together accepted
- unrecognized future field tolerated (forward-compat policy)

Pin against future strict-key refactors. No production code change — pure regression pin.
2026-05-09 12:50:22 +02:00
dcf0c7ad02 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
8dc3090080 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
f4331d5d9c chore(voyage): bump version to 4.1.0 — modellprofiler + OTel-exporter ship
Step 23 of v4.1 — final version bump.

  package.json:               4.0.0 → 4.1.0
  .claude-plugin/plugin.json: 4.0.0 → 4.1.0

Verified ESM-friendly version-read:
  node -e "import('./package.json', {with: {type: 'json'}}).then(m =>
    console.log(m.default.version))" → 4.1.0

Grep verified no remaining "4.0.0" strings outside historical references
(CHANGELOG.md v4.0.0 section, MIGRATION.md v3→v4 migration prose,
.claude/ultraplan-sessions/ historical session notes — all expected).

Tests: 487 pass + 2 skipped (Docker not installed).

v4.1 SHIP-READY. Manual smoke (SC #14, #15, #17, #18) is the final
release-gate; documented in NEXT-SESSION-PROMPT.local.md.
2026-05-09 10:10:32 +02:00
f2f8246e01 docs(voyage): document v4.1 profiles + observability + doc-consistency-pinning
Step 22 of v4.1 — write top-level docs for the v4.1 feature surface.

Files updated:
  CLAUDE.md       — Commands tables: add --profile to all 6 modes
                    + new ## Profile system + ## Observability sections
  README.md       — per-command Modes tables: add --profile row
                    + new top-level ## Profile system + ## Observability
                    + cross-link from ## Cost profile
  CHANGELOG.md    — new "## v4.1.0 — 2026-05-09" entry per Keep-a-Changelog 1.1.0
                    (Added / Changed / Fixed / Notes)
  docs/profiles.md — NEW: 168-line decision tree, lookup precedence,
                    custom-profile authoring, drift detection,
                    cost-estimation table with disclaimer
  tests/lib/doc-consistency.test.mjs — extend with 5 new pinning tests:
                    CLAUDE.md --profile + phase_models canonical name,
                    README.md --profile coverage (≥ 6 mentions),
                    CHANGELOG.md v4.1.0 entry, docs/profiles.md substantive

ROADMAP.md is gitignored per marketplace policy (sesjonsfiler) — local
edit applied for v4.1 DONE marker, not committed.

Plan-critic Blocker 2 split is honored: Step 21 pinned commands-only;
Step 22 writes the docs and pins them. doc-consistency.test.mjs is
green AFTER Step 22 (would have failed if Step 22 ran in same wave).

Tests: 487 pass + 2 skipped (Docker not installed).
2026-05-09 10:09:44 +02:00
e440ca858c test(voyage): extend doc-consistency.test.mjs — pin --profile + phase_models on 6 commands SC #20
Step 21 of v4.1 — extend-in-place per Plan-critic Blocker 2 split:
commands-only assertions land here; CLAUDE.md / README.md pinning is
deferred to Step 22 (post-write).

Changes:
  1. CLAUDE.md command coverage loop now spans all SIX pipeline commands
     (added /trekcontinue — was 5 of 6 pre-v4.1 per HIGH risk-assessor).
  2. New: every pipeline command-file (trekbrief/research/plan/execute/
     review/continue.md) must document the --profile flag.
  3. New: forbidden-alias check — no command-file may use the legacy
     names model_per_phase / phase_to_model / profile_phase_models.
     Canonical name is "phase_models" (locked in brief).
  4. New: at least one command-file must mention "phase_models" by name
     so the regression detects total removal of the canonical-name
     reference.

Tests: 482 pass + 2 skipped (Docker not installed).
2026-05-09 10:03:43 +02:00
e98eba88c9 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
93c6b82f62 test(voyage): extend plan-determinism.test.mjs — SC #10 forward-compat block
Step 19 of v4.1 — extend-in-place per brief Preferences. Three new test
blocks asserting forward-compat:

  1. Legacy fixtures (plan-run-A.md, plan-run-B.md) — without profile_used
     in frontmatter — still parse cleanly after manifest-yaml.mjs added
     OPTIONAL_STRING_KEYS.
  2. New fixtures (profile-plan-run-{economy,premium}-*.md) — with
     profile_used in frontmatter — parse cleanly with correct profile
     value extracted.
  3. Real v4.1 plan (.claude/projects/2026-05-08-voyage-v4.1-modellprofiler/plan.md)
     validates strict, emits no PLAN_VERSION_MISMATCH warning.

Tests: 475 pass + 2 skipped (Docker not installed).
2026-05-09 10:00:08 +02:00
fd67978d1c 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
90425073b2 test(voyage): empirical jaccard calibration — parked-synthetic placeholders + threshold pin
Step 17 of v4.1 — escalate-handler invoked. Live LLM-budget ($60-120 for
4 plan-runs á /trekplan --profile {economy,premium} on
examples/01-add-verbose-flag/brief.md) was not authorized for the
v4.1-execute-4b session.

Per Step 17 escalate-fallback (and NEXT-SESSION-PROMPT.local.md
fallback-strategy): document economy-Plan as parked, use balanced as
low-threshold profile, defer empirical calibration to v4.2.

Files:
  tests/synthetic/profile-plan-run-economy-1.md   — 30 steps, parked-synthetic
  tests/synthetic/profile-plan-run-economy-2.md   — 30 steps, parked-synthetic
  tests/synthetic/profile-plan-run-premium-1.md   — 40 steps, parked-synthetic
  tests/synthetic/profile-plan-run-premium-2.md   — 40 steps, parked-synthetic
  tests/synthetic/profile-jaccard-calibration.md  — threshold 0.55 pinned per
                                                    research/02 conservative starting value

Replacement procedure documented in calibration.md "How to replace"
section. Trigger conditions for empirical re-run:
  1. Cross-tier smoke-test (Step 18) flips red on a real run
  2. v4.2 LLM-budget approval
  3. New profile tier added
2026-05-09 09:54:45 +02:00
8bbe60c2f5 test(voyage): add tests/integration/observability-compose.test.mjs — SC #16 skip-if-no-docker pattern
Step 16 of v4.1 — first test in tests/integration/, establishes the
skip-on-missing-tool pattern voyage will reuse for environment-dependent
integration tests. Two tests:
  1. compose config parses and contains expected services
  2. compose config pins required image versions

Both skip cleanly when 'docker info' fails (no Docker installed). On a
machine with Docker, both tests run docker compose config and assert the
4 services + 3 version pins are present.

Tests: 468 pass + 2 skipped (Docker not installed in dev env).
2026-05-09 09:52:23 +02:00
7e60b28c8d docs(voyage): add docs/observability.md — operator quickstart for v4.1 OTel export
Step 15 of v4.1 — operator-facing observability docs (151 lines, target ≥80).
Sections:
  - Overview (JSONL is default, OTel is opt-in)
  - Activating OTel export (VOYAGE_EXPORT_MODE)
  - Output formats (Prometheus textfile vs OTLP/HTTP)
  - Environment variables matrix
  - Docker Compose quickstart (cross-link to examples/observability/)
  - Stats schema (cross-link to tests/fixtures/jsonl-schemas.md)
  - Security (CWE-22, CWE-918, CWE-212 mitigations + min-versions per CVE)
  - Limitations (Stop-hook normal-exit only, no retry, NFR best-effort)
  - Cost-estimering disclaimer (per brief Risk-tabell)
2026-05-09 09:51:44 +02:00
169d5a45ca fix(voyage): correct env-var names in observability/README.md
Step 14 follow-up — VOYAGE_OTEL_ENDPOINT (not VOYAGE_OTLP_ENDPOINT) per
hooks/scripts/otel-export.mjs and lib/exporters/endpoint-validator.mjs.
Adds VOYAGE_OTEL_ALLOW_PRIVATE=1 for localhost since 127.0.0.1 is
loopback and rejected by default.
2026-05-09 09:50:48 +02:00
48543f63c2 feat(voyage): add examples/observability/ Docker Compose stack — version-pinned per research/01
Step 14 of v4.1 — local-development observability stack with version-pinned
container images:
  - prom/prometheus:v3.0.1
  - prom/node-exporter:v1.10.2 (textfile collector enabled)
  - grafana/grafana:11.4.0
  - otel/opentelemetry-collector-contrib:0.115.0

Two complementary export paths from voyage hooks/scripts/otel-export.mjs:
  - VOYAGE_EXPORT_MODE=textfile → node-exporter textfile collector
  - VOYAGE_EXPORT_MODE=otlp     → otel-collector OTLP/HTTP receiver (:4318)
Both feed Prometheus → Grafana.

Files:
  examples/observability/docker-compose.yml
  examples/observability/otel-collector-config.yaml
  examples/observability/prometheus.yml
  examples/observability/grafana-datasource.yml
  examples/observability/README.md

Verified manifest expected_paths (5 files). docker compose config validation
runs in Step 16 with proper skip-pattern when docker is unavailable.
2026-05-09 09:50:13 +02:00
a39f7ec2e2 feat(voyage): wire Stop event to otel-export.mjs in hooks.json
Step 13 of v4.1 — adds Stop hook entry pointing to
hooks/scripts/otel-export.mjs (added in Step 12 / commit c5fb745).
Mounts the orchestrator on Claude Code's Stop event so OTel/Prometheus
export runs at session-end when VOYAGE_EXPORT_MODE is set.

HIGH-risk-mitigering: tests/hooks/hooks-json-stop-wired.test.mjs
asserter at Stop-key finnes, refererer otel-export.mjs, bruker
\${CLAUDE_PLUGIN_ROOT}-substitusjon, og har type:command.

Tests: 464 → 468 (4 new). All green.
2026-05-09 09:48:44 +02:00
c5fb7456d5 feat(voyage): add hooks/scripts/otel-export.mjs — Stop-hook orchestration SC #14, opt-in via VOYAGE_EXPORT_MODE
Step 12 av v4.1-execute (Wave 3, Session 5).

Stop-event hook (CC v2.1.105+) som leser ${CLAUDE_PLUGIN_DATA}/trek*-stats.jsonl,
applies field-allowlist (Step 11), og eksporterer enten Prometheus textfile eller
OTLP/HTTP. Strict opt-in via VOYAGE_EXPORT_MODE env-var (default off).

Modes:
- off (default): silent exit, ingen arbeid
- textfile: skriv voyage.prom til VOYAGE_TEXTFILE_DIR eller CLAUDE_PLUGIN_DATA
- otlp: POST OTLP/JSON til VOYAGE_OTEL_ENDPOINT (https kreves for non-private)

Hard invariants:
- Outer try/catch + process.exit(0) — stats failures MÅ IKKE blokkere Stop
- Tail-latency NFR: textfile <5ms p99, otlp <1500ms (AbortController)
- Allowlist redaction FØR eksport (CWE-212)
- Path/endpoint validation FØR I/O (CWE-22, CWE-918)
- Stderr prefix [voyage]
- EXDEV mitigation: tmp i samme dir som target (IKKE atomicWriteJson)

Heterogen trekexecute-stats disambiguering by record-shape:
- 'event'-felt → 'event-emit'-allowlist
- 'command_excerpt'/'session_id'-felt → 'post-bash-stats'-allowlist
- ellers → 'trekexecute' Phase 9-allowlist

Tester (7 nye, baseline 457 → 464):
- SC #14 off-mode silent exit
- SC #14 unset == off
- SC #14 textfile happy path (voyage.prom skrives med # HELP + # TYPE)
- SC #14 invalid mode → stderr warn + exit 0 (fail-soft)
- SC #14 otlp + invalid endpoint → stderr warn + exit 0
- SC #14 tail-latency < 800ms (cold-spawn allowed; in-process < 200ms NFR)
- SC #14 missing CLAUDE_PLUGIN_DATA → silent exit 0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:44:13 +02:00
ef379bedf7 feat(voyage): add 5 additive profile fields to JSONL stats — SC #11
Step 8 av v4.1-execute (Wave 3, Session 4).

5 nye additive felter er nå dokumentert i hver kommandos prose-stats-blokk
(via Profile-seksjonen fra Step 7 — felles overflate per kommando):
- profile           — string ('economy' | 'balanced' | 'premium' | <custom>)
- phase_models      — object form {brief: 'sonnet', ..., continue: 'opus'}
- parallel_agents   — number (snapshot av maksverdi som faktisk ble brukt)
- external_research_enabled — boolean
- profile_source    — 'flag' | 'env' | 'default' | 'inheritance'

Patcher trekresearch.md med eksplisitt profile_source-mention + alle 5 felter
(de andre 5 commands hadde dette allerede via Step 7 Profile-seksjon).

SC #11 contract-test design (per brief):
(a) Fixture-records valideres som JSONL-contracts → tests/fixtures/stats-with-profile.jsonl
    (5 simulerte stats-rader, én per kommando-overflate)
(b) Command-prose contains field-names → kompenserer for plan-critic Major 4
    false-confidence (faktisk runtime-emission er LLM-prose-driven, ikke
    testbart i node:test alene).

Tester (12 nye, baseline 445 → 457):
- Fixture parses som JSONL (5 records)
- Hver record har profile + profile_source
- profile_source-verdier i {flag, env, default, inheritance}
- Fikstur dekker alle 4 profile_source-verdier
- 6 commands × prose contains profile + profile_source
- trekplan.md prose contains phase_models + parallel_agents
- trekresearch.md prose contains external_research_enabled

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:40:21 +02:00
71fcf6065a feat(voyage): document --profile flag in all 6 commands — SC #4 + arv-policy
Step 7 av v4.1-execute (Wave 3, Session 4).

Legg ny "## Profile (v4.1)"-seksjon i hver kommando-fil rett før "## Hard rules":
- trekbrief.md: --profile + VOYAGE_PROFILE + premium default
- trekresearch.md: + economy/balanced auto-disable external_research_enabled
- trekplan.md: + plan.md frontmatter recording for inheritance
- trekexecute.md: + 4-step resolution (flag > env > inheritance > default)
- trekreview.md: + opus-default for review-deepening
- trekcontinue.md: spesiell — INHERITANCE er default (ikke premium), --profile
  overstyr emitter stderr-advarsel

Tester (13 nye, baseline 432 → 445):
- 6 commands × 2 (--profile + VOYAGE_PROFILE)
- trekcontinue.md "inheritance"-keyword

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:38:36 +02:00
9e01ce30b5 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
08ecdc918d 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
2349d1d431 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
f419121682 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
be9ad6ec07 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
5b4a86dca9 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
ad2dc5759a 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
55384e5b39 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
0bdfc02e75 docs(voyage): jsonl schema audit — field-allowlist input for v4.1 otel exporter
Step 1 av v4.1-execute (Wave 1, Session 1).

Audit alle 6 trek*-stats.jsonl-skjemaer + lib/stats/event-emit.mjs autonomy
events + hooks/scripts/post-bash-stats.mjs PostToolUse Bash records. Produser
markdown-tabell {schema_id, fields[], writer_path, line_ref, v4.1 additive,
PII} som load-bearing input til Step 11 (field-allowlist) og Step 8 (stats
plumbing).

Spesielle merker:
- command_excerpt fra post-bash-stats.mjs flagget CWE-212 (improper cross-
  boundary removal of sensitive data) — eksporten MÅ hard-ekskludere uten
  eksplisitt VOYAGE_EXPORT_INCLUDE_COMMAND_EXCERPT=1 (deferred til v4.2)
- v4.1 additive fields enumerert per skjema: profile, phase_models,
  parallel_agents, external_research_enabled, profile_source
- EXPORT_ALLOWLIST + EXPORT_DENYLIST utdrag i bunnen som forhåndsdefinisjon
  av Step 11 inline static consts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:20:54 +02:00
ce9b06dd16 fix(voyage): escape ! prefix in trekexecute Phase 8 doc-block
Slash-command-parseren matcher !`...` selv inne i ```bash markdown-fences,
som gjorde at Phase 8 NEXT-SESSION-PROMPT-template eksekverte ved skill-load
med literale {project_dir}/{next_session_brief_path}/{next_session_label}/
{status}-strenger som argv. Det ga ENOENT på .session-state.local.json.tmp
og blokkerte hele /trekexecute skill-loadet.

Fjern !`...`-wrapperen og merk blokken eksplisitt som runtime-template.
Pattern matcher nå konvensjonen brukt andre steder i samme fil
(linje 202-208) der ```bash brukes for orkestrator-instruksjon uten
auto-eksekvering.

Wave 0 av v4.1-execute — pre-requisite for å låse opp /trekexecute
skill-invokasjon mot .claude/projects/2026-05-08-voyage-v4.1-modellprofiler/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 09:17:44 +02:00
041e3cc6b3 feat(ms-ai-architect): playground v1.14.0 — root-cause refaktor mot 10+ visuelle bugs
DS-konvensjon-adopsjon på 14 renderere over 6 sesjoner. Etter v1.13.0/.1
patchet 10+ symptomatiske visuelle bugs (191 linjer lokal CSS, 21
fix-kommentarer), grep v1.14.0 root-cause via DS v0.4.0 + per-renderer
refaktor.

Sesjon 2 — DS v0.4.0:
- B-DS-1: kanban-card word-break (break-all → break-word)
- B-DS-2: expansion title-main/sub display:block (var inline)
- B-DS-3: matrix-bubble cursor + hover/focus

Sesjon 3 — risk-renderere til DS-summary-grid + ros-layout
(renderDpia, renderSecurity, renderRos)

Sesjon 4 — 6 compliance/govern-renderere bytter .report-meta-wrapper
mot DS-konvensjon (renderAiActPyramid, renderRequirements,
renderConformity, renderTransparency, renderFria, renderReview)

Sesjon 5 — phase-renderere til expansion-list per fase
(renderMigrate, renderPoc — slett .phase-detail-CSS)

Sesjon 5b — lavt-scope renderer-fixes:
- renderCost: ekstraher .monthly fra p50/p90-objekter
  (key-stats viste \"[object Object]\")
- renderCompare: distinctive-token-matching erstatter firstWord-heuristikk
- renderUtredning: droppet misvisende role=\"tab\"

Sesjon 6 — ship: kommentar-kompaksjon (145 → 122 linjer), 24 screenshots
regenerert til v1.14.0/, dokumentasjon (3 nivåer), versjonsbump,
mellomfiler slettet.

Lokal style-blokk: 191 → 122 effektive linjer (~36% reduksjon)
DS bumpet til v0.4.0 (delt mellom plugins, andre re-syncer på eget tempo)
17 renderere PASS visuell QA mot demo-data i begge themes
219 plugin-validering, 272 E2E playground, 7 migrations PASS

Refs V1.14.0-PLAN + V1.14.0-AUDIT (slettet ved ship per plan).
2026-05-08 21:20:08 +02:00
0033404e7a refactor(ms-ai-architect): playground v1.14.0 sesjon 5b — verifikasjon av lavt-scope-renderere
- renderCost: FIX — KEY_STATS_CONFIG['cost-distribution'] og inferVerdict('cost-distribution') viste "[object Object]" / returnerte alltid 'go' fordi parser-output har p50/p90 = {monthly, yearly}-objekter, ikke tall. Begge ekstraherer nå .monthly med fallback for flate fixtures.
- renderLicense: PASS — ingen kode-endring. Capability-matrix-status korrekt utledet (met/partial/missing) via parseCapabilityMatrix. Visuell QA gjenstår i sesjon 6.
- renderCompare: FIX — firstWord-heuristikk feilet når begge subjekter delte førsteord (f.eks. "Azure AI Foundry" vs "Azure ML + AKS" ga begge fw='azure', kollapset vinn-attribusjon). Erstattet med distinctive-token-matching: full-subject-substring først, deretter ord som er unike for ett subjekt. Diff-cell coloring oppdatert til samme matchSubject()-helper.
- renderUtredning: MINOR — droppet misvisende role="tab"/role="tablist" siden vi rendrer anchor-jump-TOC (alle paneler synlige), ikke ekte tab-toggle. Beholdt aria-current="true" for visuell aktiv-markør (DS-CSS hekter på den). Ekte tab-toggle defer til v1.15.0.

validate-plugin.sh: 219 PASS uendret
run-e2e.sh --playground: 272 PASS uendret
test-playground-migrations.sh: 7 PASS uendret

Refs V1.14.0-AUDIT.local.md sub-batch E (sesjon 5b).
2026-05-08 20:55:45 +02:00
30ddeb2d9f refactor(ms-ai-architect): playground v1.14.0 sesjon 5 — phase-rapporter til expansion-list
- renderMigrate: <section class="phase-detail"> per fase erstattet med
  <div class="expansion">-list (DS-supplement). Default-collapsed, klikkbar
  header (Fase N: navn + duration), body = milepaeler + suksesskriterier.
  Behold cycle-ribbon + mat-ladder + phases-summary-tabell + risks-tabell.
- renderPoc: speil renderMigrate. Traffic-light flyttet inn i expansion-body
  (ul.traffic-list per fase med status fra fasens stepState).
- renderSummary: KEY_STATS_CONFIG['verdict'] patchet — parseTable returnerer
  rader med header-baserte nokler (Metric/Verdi/Mal) ikke canonical
  {label,value,unit}. Ny logikk bruker metrics_headers + heuristikk-match for
  label/value/unit-kolonner, med fallback til canonical felt.
  Backward-kompatibelt.
- renderAdr: verifisert PASS — ingen endring (.adr-meta + critique-cards
  rendrer pent uten ekstra arbeid).
- ACTIONS['phase-expand']: ny handler registrert som alias for
  requirement-expand (samme toggle-monster, eget action-navn for senere
  divergens).
- Lokal CSS: hele .phase-detail-blokken (~10 linjer) slettet. Defensive-
  kommentar oppsummert til 5-linjers historie-notat.
- Style-blokk effektive linjer: 147 (var 178 etter sesjon 4).

Smoke-tester:
- validate-plugin.sh: 219 PASS
- run-e2e.sh --playground: 272 PASS (202 statisk + 70 parser)
- test-playground-migrations.sh: 7 PASS

Refs V1.14.0-AUDIT.local.md sub-batch D.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 20:36:25 +02:00
5c5c7b40a9 refactor(ms-ai-architect): playground v1.14.0 sesjon 4 — compliance/govern til DS-konvensjon
- renderAiActPyramid: 2x <aside class="card"> (rolle/begrunnelse + obligations)
  med <dl class="adr-meta"> og <ol class="stack-sm"> erstatter .report-meta-wrapper
- renderRequirements: outer .report-meta fjernet, bruker <div class="stack-sm">
- renderConformity: timeline standalone i <section class="aiact-timeline-section">
- renderTransparency/renderFria/renderReview: verifisert (DS allerede riktig)
- Slettet .report-meta-CSS-blokk (~14 linjer) + .aiact-timeline + .suppressed-panel +
  .kanban-board + .report-meta fra defensiv layout
- La til .adr-meta-grid + .aiact-timeline-section konsolidert med findings-section
- Style-blokk: 188 -> 178 effektive linjer

Refs V1.14.0-AUDIT.local.md sub-batch A.
2026-05-08 20:27:02 +02:00
d117bea219 refactor(ms-ai-architect): playground v1.14.0 sesjon 3 — risk-rapporter til DS-konvensjon
- renderDpia: matrix wrappet i .card med h2
- renderSecurity: ros-layout (matrix+radar), small-multiples-section, top-risks som <ol> i .card
- renderRos: speil renderSecurity (5x5) + summary-grid for top-risks+recommendation
- renderFindingsBlock: fjern .report-meta-band-aid, bruk findings-section + findings__items--standalone
- Legg til .ros-layout, .summary-grid, .findings-section, .small-multiples-section i lokal CSS
- Fjern .top-risks fra defensive layout-block
- test-playground-v3.sh: bytt .findings__list → .findings__items i DS-klasse-asserts
- Style-blokk: 182 → 188 linjer (mål ≤195 nådd)

Refs V1.14.0-AUDIT.local.md sub-batch B + helper-section.
2026-05-08 20:13:00 +02:00
76a64bde48 feat(playground-design-system): v0.4.0 — root-cause fix for kanban/expansion/matrix-bubble [skip-docs]
Bugfixes (B-DS-1, B-DS-2, B-DS-3 fra V1.14.0-AUDIT):
- .kanban-card__name (tier3-supplement): word-break: break-all → break-word
  + overflow-wrap: anywhere. Knekket midt i ord ("Tekn isk dokumen tasjon").
- .expansion__title-main, .expansion__title-sub (tier3-supplement): legg
  til display: block. Begge er <span> som flyter inline by default —
  resultat: "dokumentertKilde: Art. 9" på samme linje.
- .matrix__bubble (components.css): legg til cursor: pointer, hover-scale
  og focus-visible. Antas rendret som <button> i konsumenter — gir
  visuell + keyboard-fokus-feedback.

Re-syncet til plugins/ms-ai-architect/playground/vendor/ via
sync-design-system.mjs. Slettet 3 lokal-overrides i playground HTML
(matrix-bubble, expansion-title, kanban-card-name). Style-blokk:
191 → 182 linjer.

Smoke-tester: validate-plugin 219 PASS, e2e --playground 272 PASS,
statisk struktur 202 PASS.

Andre plugins (llm-security, voyage, okr, config-audit) påvirkes IKKE
— beholder gammel vendored DS inntil de selv re-syncer.

Sesjon 2 av 6 i v1.14.0 root-cause-multi-sesjons-løp.
ms-ai-architect plugin-versjon ikke bumpet (sesjon 6 ship-er v1.14.0).
[skip-docs]: docs oppdateres i sesjon 6 ved v1.14.0 plugin-ship.

Refs V1.14.0-AUDIT.local.md sub-batch 1 + 4.
2026-05-08 20:03:20 +02:00