docs(voyage): pin Handover 8 + templates + PIPELINE_COMMANDS update — v4.2 Step 12
This commit is contained in:
parent
97b6f5406e
commit
6d57314937
5 changed files with 308 additions and 2 deletions
|
|
@ -16,6 +16,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated:
|
|||
| `progress.json` | `schema_version` (top-level) | `"1"` |
|
||||
| `review.md` | `review_version` (frontmatter) | `1.0` |
|
||||
| `.session-state.local.json` | `schema_version` (top-level) | `1` (number) |
|
||||
| `brief.md` / `plan.md` / `review.md` (annotated) | `revision` (frontmatter) | `0` (implicit), `1+` after `/trekrevise` |
|
||||
|
||||
## Breaking-change protocol
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated:
|
|||
| 5. progress.json (resume) | `lib/validators/progress-validator.mjs` |
|
||||
| 6. review → plan | `lib/validators/review-validator.mjs` |
|
||||
| 7. session-state (multi-session resume) | `lib/validators/session-state-validator.mjs` |
|
||||
| 8. annotation → revision | `lib/parsers/anchor-parser.mjs` + `lib/parsers/annotation-digest.mjs` (parsing) and the existing brief / plan / review validators (forward-compat — additive fields tolerated) |
|
||||
|
||||
Every validator exposes a CLI: `node lib/validators/<name>.mjs --json <path>` returns `{valid, errors[], warnings[], parsed}`. Errors and warnings have stable `code` fields for downstream tooling.
|
||||
|
||||
|
|
@ -438,6 +440,96 @@ The `next-session-prompt-validator` (`lib/validators/next-session-prompt-validat
|
|||
|
||||
---
|
||||
|
||||
## Handover 8 — annotation → revision
|
||||
|
||||
**Handover 8 closes the operator-feedback loop.** Where Handovers 1–4 flow forward (brief → research → plan → execute), Handover 5 makes execute resumable, Handover 6 routes review findings back into planning, and Handover 7 makes multi-session work survivable across fresh chats, **Handover 8 lets a human operator annotate an artifact (brief, plan, or review) inside the playground and feed those annotations back into a revision cycle without losing the original artifact's byte-for-byte content**. The pipeline becomes round-trippable: a single artifact can be annotated, revised, and re-rendered repeatedly, each revision recorded with a deterministic digest in frontmatter.
|
||||
|
||||
**Producer:**
|
||||
- The operator (manual step) — open the artifact in `playground/voyage-playground.html`, drag-select or hover-to-anchor, fill comment + intent in the modal, click "Eksporter batch" to copy the `/trekrevise` invocation to clipboard.
|
||||
- `/trekrevise --project <dir>` (Phase 3 — apply) — consumes the pasted batch, writes anchor comments back into the target artifact, increments `revision:`, appends entries to `source_annotations:`, and computes a fresh `annotation_digest`.
|
||||
|
||||
**Consumer:**
|
||||
- Subsequent `/trekplan --project <dir>` if the revised brief or plan needs further re-planning.
|
||||
- Subsequent `/trekexecute --project <dir>` if the revised plan is ready for execution.
|
||||
- All existing validators (brief, plan, review) — they tolerate the additive frontmatter fields without version bumps.
|
||||
|
||||
**Path conventions:**
|
||||
- The annotated artifact is the same canonical file in `{project_dir}/` (`brief.md`, `plan.md`, `review.md`). Revisions are *in-place* — no `plan-revN.md` shadow files. Audit trail lives in git commits + the `revision:` counter + `source_annotations:` list inside frontmatter.
|
||||
- The playground itself lives at `playground/voyage-playground.html` (single self-contained file with vendored `markdown-it` + `highlight.js` under `playground/lib/`).
|
||||
|
||||
**Frontmatter schema (additive — applies to brief.md, plan.md, review.md):**
|
||||
|
||||
| Field | Type | Required | Allowed values | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `revision` | number | optional | `0`, `1`, `2`, … | Absent = `0` (forward-compat). Incremented by `/trekrevise` on each apply |
|
||||
| `source_annotations` | list | optional | block-style YAML list of dicts | Absent = empty. Audit trail of every annotation that has been folded into this artifact |
|
||||
| `annotation_digest` | string | optional | first 16 hex chars of canonical SHA-256 over the sorted `source_annotations` array | Absent = no annotations applied yet. Deterministic — re-applying the same set yields the same digest |
|
||||
| `revision_reason` | string | optional | free-form text | **Required only** when the revision is non-additive (e.g. removed scope, replaced an SC). Operators are encouraged to fill it for any revision |
|
||||
|
||||
Each entry in `source_annotations` is a YAML dict:
|
||||
|
||||
```yaml
|
||||
source_annotations:
|
||||
- id: ANN-0001
|
||||
target_artifact: plan.md
|
||||
target_anchor: step-3
|
||||
intent: change # change | add | remove | clarify | risk
|
||||
comment: "consider rolling back if cache_creation jumps >10%"
|
||||
line: 412
|
||||
```
|
||||
|
||||
**Anchor format (block-level HTML comments inside the body):**
|
||||
|
||||
```html
|
||||
<!-- voyage:anchor id="ANN-0001" target="plan.md#step-3" line="412" -->
|
||||
```
|
||||
|
||||
Anchors are placed **only at block boundaries** — not in list items, not mid-paragraph, not at line-start collision points where a markdown parser might fold them into surrounding content. The playground enforces this discipline via `validateAnchorPlacement()` in `lib/parsers/anchor-parser.mjs`. Anchors survive markdown rendering as comments (no visible artifact) and round-trip through `parseAnchors → addAnchors → stripAnchors` byte-identically (SC2 contract).
|
||||
|
||||
**Body invariants:**
|
||||
- The artifact's existing required sections remain untouched. `/trekrevise` writes anchor comments only at block boundaries declared by the operator's annotations.
|
||||
- After each apply: byte-identical content outside anchor blocks. Stripping all anchors with `stripAnchors()` MUST yield the artifact's original body modulo the operator's deliberate prose edits.
|
||||
|
||||
**Validation strategy:**
|
||||
|
||||
| Layer | When | What |
|
||||
|---|---|---|
|
||||
| `revision` shape | every read | Number (or absent → treat as `0`). Negative or non-integer rejected by validator |
|
||||
| `source_annotations` shape | every read | Array of dicts; each dict must include `id`, `target_artifact`, `target_anchor`, `intent`. Other fields tolerated (drift-WARN) |
|
||||
| `annotation_digest` shape | every read | 16-char lowercase hex; presence requires `source_annotations` non-empty |
|
||||
| Anchor placement | `/trekrevise` Phase 2 (validate) | `validateAnchorPlacement()` rejects in-list / mid-paragraph / line-start anchors before write |
|
||||
| Round-trip integrity | `/trekrevise` Phase 4 (post-write) | `stripAnchors()` of the just-written file MUST equal the pre-write body |
|
||||
|
||||
The parsers (`lib/parsers/anchor-parser.mjs`, `lib/parsers/annotation-digest.mjs`) are pure functions — no I/O, fully unit-tested. The existing brief / plan / review validators in `lib/validators/` already tolerate the new optional fields (forward-compat — see Step 12 manifest pins).
|
||||
|
||||
**Forward-compat — additive principle:** Artifacts without any of the four new fields validate as `revision: 0` with empty annotations. This means every brief / plan / review written before v4.2 remains valid without migration. Producers (operators using `/trekrevise`) opt into the schema by writing the fields; consumers tolerate their presence or absence equally.
|
||||
|
||||
**Idempotence:** Re-applying the same annotation batch on the same artifact (same anchors, same intents, same comments) yields the same `annotation_digest` and no body diff outside anchor refresh. This is enforced by `computeAnnotationDigest()` canonicalizing the sorted `source_annotations` array before hashing — operators can replay a batch safely, and the test suite pins this with `tests/parsers/annotation-digest.test.mjs`.
|
||||
|
||||
**Versioning:** No `*_version` bump for v4.2 — the four new fields are additive. A future schema break (e.g. removing `target_anchor` in favor of structured pointer) would bump `brief_version` / `plan_version` / `review_version` per the breaking-change protocol.
|
||||
|
||||
**Failure modes:**
|
||||
- `ANNOTATION_PLACEMENT_INVALID` → `/trekrevise` Phase 2 halts; operator re-anchors via playground
|
||||
- `ANNOTATION_DIGEST_DRIFT` → digest computed at apply-time differs from operator's expected digest in the pasted batch; halt with mismatch report (suggests the batch was hand-edited after export)
|
||||
- `ANNOTATION_ROUNDTRIP_FAIL` → post-write `stripAnchors()` does not yield the original body; rollback restores the byte-identical pre-write file from `*.local.bak`
|
||||
- Parser failures from `parseAnchors()` produce `{valid: false, errors: [...]}` and `/trekrevise` halts before any write. The atomic-write pattern (tmp-file + rename) guarantees the canonical file is never partially updated.
|
||||
|
||||
### § Lifecycle
|
||||
|
||||
The annotation cycle has three stages — no persistent state file (unlike Handover 7), only the artifact frontmatter and git history record what happened:
|
||||
|
||||
| Stage | Owner | Action |
|
||||
|---|---|---|
|
||||
| Annotate | Operator + playground UI | Open artifact in `playground/voyage-playground.html`, anchor + comment, click "Eksporter batch" → clipboard contains a `/trekrevise --project <dir> --apply '{...JSON...}'` command |
|
||||
| Apply | `/trekrevise` | Phase 1 parse the pasted batch, Phase 2 validate placement, Phase 3 atomically write anchors + frontmatter (`revision: N+1`, append to `source_annotations`, recompute `annotation_digest`), Phase 4 round-trip integrity check, Phase 5 commit |
|
||||
| Re-render | Playground (next open) | The freshly-revised artifact loads with anchors visible as inline comment markers; operator can iterate or call `/trekplan` / `/trekexecute` on the revised file |
|
||||
|
||||
**Single-iteration MVP (v4.2 scope):** Each operator annotation batch produces one `revision:` increment. Multi-iteration loops (e.g. revise → re-review → revise again without operator intervention) are deferred indefinitely — the brief's SC4 wording is single-revision, and operator validation that multi-iteration is desired has not been collected (see plan Alternatives table). The single-iteration MVP keeps the audit trail unambiguous: one `revision:` bump per `/trekrevise` invocation.
|
||||
|
||||
**Stale-anchor principle:** Unlike `.session-state.local.json`, anchors are durable and intentionally retained — they document *why* a revision happened. There is no `--cleanup` for anchors. Removing them is a manual operator decision, executed via the playground "Strip all anchors" affordance or hand-editing the source. `/trekrevise` does not remove anchors automatically.
|
||||
|
||||
---
|
||||
|
||||
## Stability summary
|
||||
|
||||
| Handover | Validation strength | Owner | Risk |
|
||||
|
|
@ -449,5 +541,6 @@ The `next-session-prompt-validator` (`lib/validators/next-session-prompt-validat
|
|||
| 5. progress.json | shape + resume readiness | this plugin | medium — drift during compaction handled by pre-compact-flush hook (CC v2.1.105+) |
|
||||
| 6. review → plan | strict at write, soft at read | this plugin | low — additive feedback loop; consumer falls back gracefully when source_findings is absent |
|
||||
| 7. session-state (multi-session resume) | required-fields + status enum + drift-WARN extras | this plugin | low — readers tolerate unknown keys; writers are owned by trekexecute Phase 8 + helper command |
|
||||
| 8. annotation → revision | parser-strict at write (placement + round-trip), validator-soft at read (additive fields) | this plugin | low — additive frontmatter; existing artifacts validate as `revision: 0` without migration |
|
||||
|
||||
When extending the plugin or adding a new pipeline stage, follow the same pattern: produce an artifact with a versioned frontmatter (or `schema_version` for JSON), write a validator under `lib/validators/`, add fixtures under `tests/fixtures/`, and add an entry to this document.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,22 @@ source_findings:
|
|||
---
|
||||
-->
|
||||
|
||||
<!--
|
||||
Optional annotation fields (Handover 8 — added in v4.2). Written by
|
||||
`/trekrevise` when an operator annotates this plan via the playground.
|
||||
All fields are additive — absence implies `revision: 0`. The plan_version
|
||||
is NOT bumped; the validator tolerates these fields on every plan.md.
|
||||
|
||||
# revision: 0 # optional — annotation revision counter (incremented by /trekrevise)
|
||||
# source_annotations: # optional — list of applied annotations from /trekrevise
|
||||
# - id: ANN-0001
|
||||
# target_artifact: plan.md
|
||||
# target_anchor: step-3
|
||||
# intent: change # change | add | remove | clarify | risk
|
||||
# annotation_digest: <16-char sha256 prefix> # optional, deterministic over sorted source_annotations
|
||||
# revision_reason: "..." # required only if revision is non-additive
|
||||
-->
|
||||
|
||||
# {Task Title}
|
||||
|
||||
> **Plan quality: {grade}** ({score}/100) — {APPROVE | APPROVE_WITH_NOTES | REVISE | REPLAN}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
<!--
|
||||
Optional annotation fields (Handover 8 — added in v4.2). Written by
|
||||
`/trekrevise` when an operator annotates this brief via the playground.
|
||||
All fields are additive — absence implies `revision: 0`. The brief_version
|
||||
is NOT bumped; the validator tolerates these fields on every brief.md.
|
||||
|
||||
# revision: 0 # optional — annotation revision counter (incremented by /trekrevise)
|
||||
# source_annotations: # optional — list of applied annotations from /trekrevise
|
||||
# - id: ANN-0001
|
||||
# target_artifact: brief.md
|
||||
# target_anchor: intent
|
||||
# intent: change # change | add | remove | clarify | risk
|
||||
# annotation_digest: <16-char sha256 prefix> # optional, deterministic over sorted source_annotations
|
||||
# revision_reason: "..." # required only if revision is non-additive
|
||||
-->
|
||||
|
||||
---
|
||||
type: trekbrief
|
||||
brief_version: 2.0
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
<!--
|
||||
Optional annotation fields (Handover 8 — added in v4.2). Written by
|
||||
`/trekrevise` when an operator annotates this review via the playground.
|
||||
All fields are additive — absence implies `revision: 0`. The review_version
|
||||
is NOT bumped; the validator tolerates these fields on every review.md.
|
||||
|
||||
# revision: 0 # optional — annotation revision counter (incremented by /trekrevise)
|
||||
# source_annotations: # optional — list of applied annotations from /trekrevise
|
||||
# - id: ANN-0001
|
||||
# target_artifact: review.md
|
||||
# target_anchor: finding-0123456789abcdef0123456789abcdef01234567
|
||||
# intent: clarify # change | add | remove | clarify | risk
|
||||
# annotation_digest: <16-char sha256 prefix> # optional, deterministic over sorted source_annotations
|
||||
# revision_reason: "..." # required only if revision is non-additive
|
||||
-->
|
||||
|
||||
---
|
||||
type: trekreview
|
||||
review_version: "1.0"
|
||||
|
|
|
|||
|
|
@ -94,8 +94,10 @@ test('settings.json no longer carries vestigial exploration block', () => {
|
|||
'agentTeam block was vestigial — should be deleted in v3.1.0 Spor 0');
|
||||
});
|
||||
|
||||
test('CLAUDE.md mentions all six pipeline commands', () => {
|
||||
// Step 21 of v4.1 — added /trekcontinue to coverage (was 5/6 before).
|
||||
test('CLAUDE.md mentions all seven pipeline commands', () => {
|
||||
// v4.1 Step 21 — added /trekcontinue to coverage (was 5/6 before).
|
||||
// v4.2 Step 12 — added /trekrevise (Handover 8 producer), bringing the
|
||||
// canonical pipeline to seven commands.
|
||||
const md = read('CLAUDE.md');
|
||||
for (const c of [
|
||||
'/trekbrief',
|
||||
|
|
@ -103,6 +105,7 @@ test('CLAUDE.md mentions all six pipeline commands', () => {
|
|||
'/trekplan',
|
||||
'/trekexecute',
|
||||
'/trekreview',
|
||||
'/trekrevise',
|
||||
'/trekcontinue',
|
||||
]) {
|
||||
assert.ok(md.includes(c), `CLAUDE.md missing reference to ${c}`);
|
||||
|
|
@ -258,6 +261,7 @@ const PIPELINE_COMMANDS = [
|
|||
'trekplan.md',
|
||||
'trekexecute.md',
|
||||
'trekreview.md',
|
||||
'trekrevise.md',
|
||||
'trekcontinue.md',
|
||||
];
|
||||
|
||||
|
|
@ -398,3 +402,164 @@ test('commands/trekplan.md Phase 8 seals Opus-4.7 schema-drift defense', () => {
|
|||
'Phase 8 should explicitly enumerate FORBIDDEN headings',
|
||||
);
|
||||
});
|
||||
|
||||
// --- v4.2 Step 12 — Handover 8 + annotation pipeline pins ---
|
||||
//
|
||||
// CLAUDE.md / README.md / CHANGELOG / annotation-quickstart pins are deferred
|
||||
// to Step 13 (post-write of those files). Step 12 only pins HANDOVER-CONTRACTS,
|
||||
// templates, scaffold-files, and the parseAnchors round-trip on the example
|
||||
// fixture.
|
||||
|
||||
import { existsSync, statSync } from 'node:fs';
|
||||
|
||||
test('HANDOVER-CONTRACTS.md contains Handover 8 section (annotation → revision)', () => {
|
||||
const text = read('docs/HANDOVER-CONTRACTS.md');
|
||||
assert.ok(
|
||||
text.includes('## Handover 8'),
|
||||
'docs/HANDOVER-CONTRACTS.md should document Handover 8 (annotation → revision) — added in v4.2',
|
||||
);
|
||||
});
|
||||
|
||||
test('HANDOVER-CONTRACTS.md Handover 8 names annotation_digest and source_annotations', () => {
|
||||
const text = read('docs/HANDOVER-CONTRACTS.md');
|
||||
const h8Start = text.indexOf('## Handover 8');
|
||||
assert.ok(h8Start >= 0, 'Handover 8 heading missing');
|
||||
const h8End = text.indexOf('## Stability summary', h8Start);
|
||||
assert.ok(h8End > h8Start, 'Stability summary heading missing — could not bound Handover 8');
|
||||
const h8 = text.slice(h8Start, h8End);
|
||||
assert.ok(
|
||||
h8.includes('annotation_digest'),
|
||||
'Handover 8 section should document the annotation_digest frontmatter field',
|
||||
);
|
||||
assert.ok(
|
||||
h8.includes('source_annotations'),
|
||||
'Handover 8 section should document the source_annotations frontmatter field',
|
||||
);
|
||||
assert.ok(
|
||||
h8.includes('revision'),
|
||||
'Handover 8 section should document the revision counter field',
|
||||
);
|
||||
});
|
||||
|
||||
test('templates/plan-template.md documents annotation revision fields', () => {
|
||||
const tpl = read('templates/plan-template.md');
|
||||
assert.ok(
|
||||
tpl.includes('revision:'),
|
||||
'plan-template.md must document optional revision counter (Handover 8)',
|
||||
);
|
||||
assert.ok(
|
||||
tpl.includes('source_annotations:'),
|
||||
'plan-template.md must document optional source_annotations list (Handover 8)',
|
||||
);
|
||||
assert.ok(
|
||||
tpl.includes('annotation_digest'),
|
||||
'plan-template.md must document optional annotation_digest field (Handover 8)',
|
||||
);
|
||||
});
|
||||
|
||||
test('templates/trekbrief-template.md documents annotation revision fields', () => {
|
||||
const tpl = read('templates/trekbrief-template.md');
|
||||
assert.ok(
|
||||
tpl.includes('revision:'),
|
||||
'trekbrief-template.md must document optional revision counter (Handover 8)',
|
||||
);
|
||||
assert.ok(
|
||||
tpl.includes('source_annotations:'),
|
||||
'trekbrief-template.md must document optional source_annotations list (Handover 8)',
|
||||
);
|
||||
assert.ok(
|
||||
tpl.includes('annotation_digest'),
|
||||
'trekbrief-template.md must document optional annotation_digest field (Handover 8)',
|
||||
);
|
||||
});
|
||||
|
||||
test('templates/trekreview-template.md documents annotation revision fields', () => {
|
||||
const tpl = read('templates/trekreview-template.md');
|
||||
assert.ok(
|
||||
tpl.includes('revision:'),
|
||||
'trekreview-template.md must document optional revision counter (Handover 8)',
|
||||
);
|
||||
assert.ok(
|
||||
tpl.includes('source_annotations:'),
|
||||
'trekreview-template.md must document optional source_annotations list (Handover 8)',
|
||||
);
|
||||
assert.ok(
|
||||
tpl.includes('annotation_digest'),
|
||||
'trekreview-template.md must document optional annotation_digest field (Handover 8)',
|
||||
);
|
||||
});
|
||||
|
||||
test('playground/ directory exists at voyage root (Handover 8 producer surface)', () => {
|
||||
const playgroundDir = join(ROOT, 'playground');
|
||||
assert.ok(existsSync(playgroundDir), 'playground/ directory missing');
|
||||
assert.ok(statSync(playgroundDir).isDirectory(), 'playground/ is not a directory');
|
||||
// Self-contained HTML must exist
|
||||
assert.ok(
|
||||
existsSync(join(playgroundDir, 'voyage-playground.html')),
|
||||
'playground/voyage-playground.html missing — operator-facing entry point',
|
||||
);
|
||||
});
|
||||
|
||||
test('playground/ files do NOT import or reference `marked` (risk-assessor H1)', () => {
|
||||
// Walk playground/ recursively. Exclude vendor/playground-design-system
|
||||
// (consumed via the shared design system; not part of voyage's playground
|
||||
// markdown renderer). Exclude any *MANIFEST.json files. Assert no file
|
||||
// contains the standalone identifier `marked` (case-sensitive, word-boundary).
|
||||
// markdown-it is the locked renderer per research-03 + alternatives table.
|
||||
const playgroundDir = join(ROOT, 'playground');
|
||||
assert.ok(existsSync(playgroundDir), 'playground/ directory missing — cannot verify marked-ban');
|
||||
const offenders = [];
|
||||
function walk(dir) {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const p = join(dir, entry);
|
||||
const s = statSync(p);
|
||||
if (s.isDirectory()) {
|
||||
// Skip vendor design-system trees (shared infra, not voyage's renderer)
|
||||
if (entry === 'playground-design-system') continue;
|
||||
walk(p);
|
||||
} else if (s.isFile()) {
|
||||
// Skip vendor manifest JSONs
|
||||
if (entry.endsWith('MANIFEST.json')) continue;
|
||||
if (entry === 'VENDOR-MANIFEST.json') continue;
|
||||
const txt = readFileSync(p, 'utf-8');
|
||||
if (/\bmarked\b/.test(txt)) {
|
||||
offenders.push(p.slice(ROOT.length + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(playgroundDir);
|
||||
assert.deepStrictEqual(
|
||||
offenders,
|
||||
[],
|
||||
`playground/ files contain banned identifier "marked": ${offenders.join(', ')}. ` +
|
||||
`Use markdown-it instead — see plan Alternatives table (Issue #3515 disqualifies marked).`,
|
||||
);
|
||||
});
|
||||
|
||||
test('scripts/render-artifact.mjs exists (SC1/SC11 self-render gate)', () => {
|
||||
assert.ok(
|
||||
existsSync(join(ROOT, 'scripts/render-artifact.mjs')),
|
||||
'scripts/render-artifact.mjs missing — required by SC1 (offline render) and SC11 (pipeline-self-eat)',
|
||||
);
|
||||
});
|
||||
|
||||
test('lib/util/revision-guard.mjs exists (plan-critic M4 — atomic-write rollback guard)', () => {
|
||||
assert.ok(
|
||||
existsSync(join(ROOT, 'lib/util/revision-guard.mjs')),
|
||||
'lib/util/revision-guard.mjs missing — required for /trekrevise rollback hygiene',
|
||||
);
|
||||
});
|
||||
|
||||
test('tests/fixtures/annotation/annotation-example.md parses cleanly via parseAnchors (ESM)', async () => {
|
||||
// Plan-critic m4 — fix the SC12 require/import mixup. Use ESM dynamic import,
|
||||
// not require(). The parser is pure — no I/O, no side effects.
|
||||
const { parseAnchors } = await import('../../lib/parsers/anchor-parser.mjs');
|
||||
const fixturePath = join(ROOT, 'tests/fixtures/annotation/annotation-example.md');
|
||||
assert.ok(existsSync(fixturePath), 'tests/fixtures/annotation/annotation-example.md missing');
|
||||
const result = parseAnchors(readFileSync(fixturePath, 'utf-8'));
|
||||
assert.ok(
|
||||
result.valid,
|
||||
`parseAnchors failed on annotation-example.md fixture: ${JSON.stringify(result.errors || [])}`,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue