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
487f7ae746
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
7a90d348ad
feat(voyage)!: marketplace handoff — rename plugins/ultraplan-local to plugins/voyage [skip-docs]
...
Session 5 of voyage-rebrand (V6). Operator-authorized cross-plugin scope.
- git mv plugins/ultraplan-local plugins/voyage (rename detected, history preserved)
- .claude-plugin/marketplace.json: voyage entry replaces ultraplan-local
- CLAUDE.md: voyage row in plugin list, voyage in design-system consumer list
- README.md: bulk rename ultra*-local commands -> trek* commands; ultraplan-local refs -> voyage; type discriminators (type: trekbrief/trekreview); session-title pattern (voyage:<command>:<slug>); v4.0.0 release-note paragraph
- plugins/voyage/.claude-plugin/plugin.json: homepage/repository URLs point to monorepo voyage path
- plugins/voyage/verify.sh: drop URL whitelist exception (no longer needed)
Closes voyage-rebrand. bash plugins/voyage/verify.sh PASS 7/7. npm test 361/361.
2026-05-05 15:37:52 +02:00