Skip to content

Reuse setup between examples by linking to a section (ADR 0016) - #104

Merged
aslakhellesoy merged 21 commits into
mainfrom
reuse-is-a-link
Sep 14, 2026
Merged

aslakhellesoy merged 21 commits into
mainfrom
reuse-is-a-link

Conversation

@aslakhellesoy

Copy link
Copy Markdown
Contributor

Implements ADR 0016 — Varar's answer to Cucumber's Background:.

A block whose entire content is a Markdown link to an oath section — paragraph, blockquote or list item — splices that section's steps in at its own position, sharing the example's state:

[An overdue loan](./shared/an-overdue-loan.md#an-overdue-loan)

When she asks to borrow *Beloved* on June 10, 2026, the library refuses.

It is an ordinary link, so it renders and navigates on GitHub. Targets: #fragment (same oath), ./other.md#anchor (another oath), ./other.md (that whole oath). Anything else — https://…, a .ts file — stays prose, so existing oaths keep their meaning.

Because it splices at its own position it also does what Background: cannot: a shared act mid-example, and shared assertions at the end. References nest to any depth (a cycle is reported with its chain); depth is left to prose and review rather than enforced by the parser.

A section that is referenced stops being a standalone example. It runs where it is referenced, once per referencing example. That is whole-project knowledge, so plan() now takes the workspace as a required argument — a caller that hasn't built one would otherwise run consumed sections as standalone examples, which is green and wrong.

How to review

The commits are ordered to be read in sequence, and each one is green on its own:

Commit What to look at
db65278b…dce7412b ADR 0016 as it evolved — context, decisions, rejected alternatives. Read 966cc3be (the last commit) for what actually shipped.
c5216be5 Prerequisite, and a bug fix on its own: doc.path meant a basename in six ports, a relative path in two, an absolute path in two more. A basename can't anchor a relative link or tell two same-named oaths apart.
4ed817fe Independent of this feature: one LSP keystroke used to re-tree-sitter every step file and plan every oath twice, with no debounce. Now debounced, hash-cached, and drift reuses the index's plans.
397a3894, 80e15ed3 The feature in TypeScript — the reference implementation. packages/core/src/reference.ts and the splice in plan.ts are the heart of it.
2ba138f9…12875900 The other six ports, one per commit. Mechanical against the TS reference.
37d43c56 Docs (draft-gated until release).

Conformance

Two new bundles pin the feature across all seven ports:

  • 20-reference-splice — a spliced step, its docPath, and its execution.
  • 21-reference-consumed — the other half: the defining file of a referenced section plans no example. This is what catches a port that resolves references but forgets the workspace and runs shared sections twice.

It caught a real one on its first run: .NET's MergedExample.ScopeStack was init-only, so the name-replacement rule updated the name but left the referenced section's heading chain on the example.

parity.json gains the reference capability; make check is green (all seven ports, parity, commit lint, adapter smoke contracts).

Known gaps (recorded in the ADR)

  • Run-result v2 is not done. docPath reaches the plan and the plan artifact but not the persisted .varar/<oath>.json. A mismatch inside a referenced section therefore isn't placed correctly in editors — the run still fails with the right message in every runner. Next piece of work; it's a cross-port payload change with its own golden.
  • Ambiguous anchors go undetected — a consequence of resolving sections through the scope stack rather than adding a headings field to Doc, which kept every doc.json golden untouched.
  • No LSP support on a reference block (go-to-definition, hover).

Deviations from the ADR, and why

  1. Recognised in plan(), not the structurer — equally pure there, and it left every port's doc.json golden untouched.
  2. Sections resolve through the scope stack — "from this heading until the next of the same or higher level" is already computed. Cost: ambiguous anchors (above).
  3. PlannedStep gained paramTexts — not in the plan, and necessary: consumers sliced the running oath's source by a step's param spans, which is wrong for a step spliced in from another file.

Note

c5216be5 also carries an unrelated pnpm-lock.yaml change: pnpm install pruned a stale @pnpm/exe entry. The lock now matches the manifests, but it isn't described by that commit message.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX

aslakhellesoy and others added 16 commits September 11, 2026 10:28
Proposes a reference block: a paragraph, blockquote or list item whose
entire content is a single link to an oath section inlines that section's
steps at that point. Covers shared arrange, mid-example act and shared
assertions, within a file or across files.

Draft status — the open questions (whole-project inbound index, drift
liveness, report shape) are unresolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
… rule

Reverses the one-level ceiling. A referenced section may contain reference
blocks to any depth; cycle detection comes back with it. Deep chains stay
bad practice, communicated through the reuse docs and the agent/authoring
instructions rather than enforced by the parser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Every port already globs the whole project once per run (the seam that
prunes varar.lock.json), and the LSP already reindexes every oath on every
change — so the inbound index has a home everywhere it is needed. Records
the decision, the rejected file-level opt-out, and the costs: vitest's
zero-test files and its wider watch invalidation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Adds a per-adapter compatibility table and decisions for the three real
incompatibilities: doc.path means a basename in some ports and cannot
resolve a relative link (prerequisite fix), vitest fails an oath that
becomes a zero-test file (transform it to an empty describe.skip —
verified), and the index must be a required plan() argument backed by a
conformance bundle and an adapter smoke case.

Also records what one LSP keystroke costs today (full reindex, no
debounce, every oath planned twice) and the four changes that make it
cheaper than today even with references: debounce, hash-keyed parse and
scan caches, invalidation along the reference graph, and reusing the
index's plans for drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
… every port

doc.path meant three different things depending on who called: a basename
(pytest, unittest, RSpec, minitest, cargo test, go test), a workspace-
relative path (.NET, JUnit, the vitest runtime) or an absolute path (the
vitest static planner, the CLI linter). A basename cannot tell two
same-named oaths in different directories apart, and it cannot anchor a
relative path.

Every adapter now hands plan()/parse() the same identity varar.lock.json
and .varar/<oathPath>.json already use. TypeScript gains a shared
toOathPath() helper in @varar/config; the other ports reuse the rel-posix
helper each already had next to the call.

Prerequisite for ADR 0016 (reference blocks), and a correctness fix on its
own.

Ports-deferred: java, dotnet — both already passed the relative path

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
…t per keystroke

A keystroke used to re-run the tree-sitter scan on every step file, re-parse
and re-plan every oath, and then parse and plan every oath a second time for
the drift pass — with no debouncing, so a burst of typing queued one full
workspace reindex per character.

Three changes, all invisible except in latency:

- buildWorkspaceIndex takes an optional IndexCache, keyed by (path, content
  hash). Step scans, parsed docs and plans survive across reindexes, so an
  edit re-scans no step files and re-plans only what changed. A plan is also
  keyed by the registry's fingerprint, so a step-file edit still invalidates
  every plan, as it must.
- The index exposes each oath's doc and plan, and the drift pass reads them
  instead of parsing and planning everything again.
- Reindexing is debounced 75 ms. The edited buffer is still written through
  immediately, and every request handler (hover, definition, completion,
  semantic tokens, and the var/* rename and snippet requests) awaits any
  pending reindex first — so no feature can answer from an index older than
  the edit it is answering about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
A block whose entire content is a Markdown link to an oath section — a
paragraph, a blockquote or a list item — is a reference block: it splices
that section's steps in at its own position, sharing the example's state.
It is an ordinary link, so it renders and navigates on GitHub.

    [An overdue loan](./shared/an-overdue-loan.md#an-overdue-loan)

    When she asks to borrow *Beloved* on June 10, 2026, the library refuses.

Because it is positional it also does what Cucumber's Background cannot:
a shared act mid-example, or shared assertions at the end. The target may
be a #fragment in the same oath or a relative .md path, with or without a
fragment; references nest to any depth, and a cycle is reported with its
chain rather than recursed into. A link-only block pointing anywhere else
(https:, a .ts file) stays prose, so existing oaths keep their meaning.

A section that is referenced stops being a standalone example — it runs
where it is referenced, once per referencing example. That is
whole-project knowledge, so plan() now takes the workspace as a required
argument: a caller that has not built one would otherwise run consumed
sections as standalone examples, which is green and wrong.

An oath whose sections are all consumed registers one bookkeeping test
rather than nothing: vitest fails a module that declares no test at all,
and the oath still needs its drift baseline and its .varar record written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Adds paramTexts to PlannedStep — the notation each parameter matched,
sliced at plan time from the document the step was written in. Consumers
sliced the running oath's source by the step's spans instead, which is
wrong for a step a reference block spliced in from another file: the spans
belong to that file, and the host source yields whatever text happens to
sit at those offsets.

The plan conformance artifact projects docPath, and the bundle harness
parses every .md in a bundle directory so a bundle can hold the oath its
example.md links to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Ports reference blocks (ADR 0016) to Python: a block whose entire content
is a Markdown link to an oath section splices that section's steps in at
its own position, in the same file or across files, nesting to any depth
with cycles reported rather than recursed into.

plan() takes the workspace as a required argument, and both adapters build
it from their existing discovery pass — pytest in pytest_configure, where
baseline pruning already insists on the config globs rather than the
filtered view, and unittest in generate_tests.

PlannedStep also gains param_texts, sliced from the document the step was
written in: slicing the running oath's source is wrong for a step spliced
in from another file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Ports reference blocks (ADR 0016) to Ruby: a block whose entire content is
a Markdown link to an oath section splices that section's steps in at its
own position, in the same file or across files, nesting to any depth with
cycles reported rather than recursed into.

Plan.plan takes the workspace as a required argument, and both adapters
build it from the discovery pass they already run for baseline pruning.
PlannedStep gains param_texts, sliced from the document the step was
written in, and doc_path for a step spliced in from another oath.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Ports reference blocks (ADR 0016) to Go: a block whose entire content is a
Markdown link to an oath section splices that section's steps in at its own
position, in the same file or across files, nesting to any depth with
cycles reported rather than recursed into.

core.Plan takes the workspace as a required argument, and gotest builds it
from the discovery pass it already runs for baseline pruning. PlannedStep
gains ParamTexts, sliced from the document the step was written in, and
DocPath for a step spliced in from another oath.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Ports reference blocks (ADR 0016) to Rust: a block whose entire content is
a Markdown link to an oath section splices that section's steps in at its
own position, in the same file or across files, nesting to any depth with
cycles reported rather than recursed into.

plan() takes the workspace as a required argument, and the cargo-test
adapter builds it from the discovery pass it already runs for baseline
pruning, sharing it across trials behind an Arc. PlannedStep gains
param_texts, sliced from the document the step was written in, and
doc_path for a step spliced in from another oath.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Ports reference blocks (ADR 0016) to .NET: a block whose entire content is
a Markdown link to an oath section splices that section's steps in at its
own position, in the same file or across files, nesting to any depth with
cycles reported rather than recursed into.

Plan.Run takes the workspace as a required argument, and the VSTest
adapter builds it from the discovery pass it already runs for baseline
pruning. PlannedStep gains ParamTexts, sliced from the document the step
was written in, and DocPath for a step spliced in from another oath.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Ports reference blocks (ADR 0016) to Java and Kotlin — the last of the
seven ports: a block whose entire content is a Markdown link to an oath
section splices that section's steps in at its own position, in the same
file or across files, nesting to any depth with cycles reported rather
than recursed into.

Plan.plan takes the workspace as a required argument, and both adapters
build it from the discovery pass they already run for baseline pruning.
PlannedStep gains paramTexts, sliced from the document the step was
written in, and docPath for a step spliced in from another oath.

With every port green, the corpus lands too: two bundles pin the feature
across all seven. 20-reference-splice pins a spliced step (docPath and
all) and its execution; 21-reference-consumed pins the other half — the
defining file of a referenced section plans NO example, which is what
catches a port that resolves references but forgets the workspace and
runs shared sections twice. parity.json gains the reference capability.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Adds the how-to (Share setup between examples) and the explanation (Reuse
is a link), both draft-gated until the release that carries the feature.
The examples reference gains reference blocks as a fourth block role, with
the three error codes; the Cucumber migration page's Background row stops
saying "no equivalent"; the agent-instructions block gains the depth
guidance, since an agent is the author most likely to build a chain no
human would.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
Records the three deliberate deviations (recognition in plan() rather than
the structurer, sections resolved through the scope stack, and the added
paramTexts), the .NET divergence the new corpus bundle caught on its first
run, and what is still open — chiefly that docPath does not yet reach the
persisted run-result payload, so a failure inside a referenced section is
not placed correctly in editors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSCLupVART3c5PjiffmahX
@aslakhellesoy

Copy link
Copy Markdown
Contributor Author

Follow-ups from this PR are filed, so the known gaps don't have to hold up the merge:

@aslakhellesoy

Copy link
Copy Markdown
Contributor Author

Stacked #112 on this branch: it closes #105 (run results carrying per-step document identity), the one gap with user-visible consequences. Review this PR first — #112's diff is only the eight commits on top.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate findings remain across planning, source-path handling, adapters, and LSP behavior.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Implements ADR 0016, enabling Markdown link-based reuse of oath sections across language ports and tooling.

Changes:

  • Adds recursive reference resolution, splicing, cycle detection, and consumed-section handling.
  • Updates runners, adapters, Vitest, LSP indexing, and source metadata.
  • Adds conformance bundles, examples, and documentation.
File summaries
File Summary
typescript/pnpm-lock.yaml Lockfile update
typescript/packages/website/src/content/docs/reference/examples.mdx Reference examples
typescript/packages/website/src/content/docs/how-to/share-setup-between-examples.md Sharing guidance
typescript/packages/website/src/content/docs/how-to/agent-instructions.md Agent guidance
typescript/packages/website/src/content/docs/explanation/varar-for-cucumber-users.md Cucumber comparison
typescript/packages/website/src/content/docs/explanation/reuse.md Reuse documentation
typescript/packages/vitest/tests/static-examples.test.ts Static example coverage
typescript/packages/vitest/tests/plugin.test.ts Plugin coverage
typescript/packages/vitest/src/workspace.ts Workspace handling
typescript/packages/vitest/src/static-examples.ts Static examples
typescript/packages/vitest/src/runtime.ts Runtime integration
typescript/packages/vitest/src/reporter.ts Reporter integration
typescript/packages/vitest/src/plugin.ts Vitest plugin integration
typescript/packages/varar/tests/conformance.test.ts Conformance tests
typescript/packages/runner/tests/run.test.ts Runner tests
typescript/packages/runner/src/run.ts Runner integration
typescript/packages/lsp/src/store.ts LSP workspace store
typescript/packages/lsp/src/server.ts LSP server integration
typescript/packages/language/tests/index-cache.test.ts Index cache tests
typescript/packages/language/src/index.ts Language indexing
typescript/packages/core/tests/index.test.ts Core index tests
typescript/packages/core/tests/failure-step-span.test.ts Failure span tests
typescript/packages/core/tests/execute-state.test.ts Execution state tests
typescript/packages/core/tests/execute-roles.test.ts Execution role tests
typescript/packages/core/tests/e2e.test.ts End-to-end coverage
typescript/packages/core/tests/conformance.test.ts Core conformance tests
typescript/packages/core/src/reference.ts Reference resolution
typescript/packages/core/src/index.ts Core exports
typescript/packages/core/src/diagnostics.ts Diagnostics
typescript/packages/core/src/conformance.ts Conformance support
typescript/packages/config/tests/oath-path.test.ts Oath path tests
typescript/packages/config/src/oath-path.ts Oath path handling
typescript/packages/config/src/index.ts Configuration exports
typescript/packages/cli/src/lint.ts CLI lint integration
rust/varar/tests/conformance.rs Rust conformance tests
rust/runner/tests/runner.rs Rust runner tests
rust/runner/src/run.rs Rust runner integration
rust/core/tests/failure_step_span_test.rs Failure span tests
rust/core/tests/execute_test.rs Execution tests
rust/core/tests/drift_test.rs Drift tests
rust/core/src/lib.rs Rust core exports
rust/core/src/diagnostics.rs Rust diagnostics
rust/core/src/conformance.rs Rust conformance support
rust/cargotest/tests/adapter.rs Adapter tests
rust/cargotest/src/lib.rs Adapter integration
ruby/packages/varar/spec/conformance/trace_conformance_spec.rb Trace conformance
ruby/packages/varar/spec/conformance/plan_conformance_spec.rb Plan conformance
ruby/packages/runner/spec/varar/runner_spec.rb Runner specifications
ruby/packages/runner/lib/varar/runner/run.rb Ruby runner integration
ruby/packages/rspec/lib/varar/rspec.rb RSpec integration
ruby/packages/minitest/lib/varar/minitest.rb Minitest integration
ruby/packages/core/spec/varar/core/plan_spec.rb Plan specifications
ruby/packages/core/spec/varar/core/failure_spec.rb Failure specifications
ruby/packages/core/spec/varar/core/execute_spec.rb Execution specifications
ruby/packages/core/spec/varar/core/drift_spec.rb Drift specifications
ruby/packages/core/lib/varar/core/reference.rb Ruby reference resolution
ruby/packages/core/lib/varar/core/diagnostics.rb Ruby diagnostics
ruby/packages/core/lib/varar/core/conformance.rb Ruby conformance support
python/packages/varar/tests/test_conformance.py Python conformance tests
python/packages/unittest/src/varar_unittest/__init__.py unittest integration
python/packages/runner/tests/test_run.py Runner tests
python/packages/runner/src/varar_runner/run.py Python runner integration
python/packages/pytest/src/varar_pytest/plugin.py Pytest integration
python/packages/core/tests/test_failure_step_span.py Failure span tests
python/packages/core/tests/test_conformance.py Core conformance tests
python/packages/core/src/varar_core/reference.py Python reference resolution
python/packages/core/src/varar_core/diagnostics.py Python diagnostics
python/packages/core/src/varar_core/conformance.py Python conformance support
java/varar/src/test/java/dev/varar/ConformanceTest.java Java conformance tests
java/runner/src/test/java/dev/varar/runner/RunTest.java Runner tests
java/runner/src/test/java/dev/varar/runner/RenderTest.java Rendering tests
java/runner/src/main/java/dev/varar/runner/Run.java Java runner integration
java/kotlin/src/test/kotlin/dev/varar/kotlin/ParameterTypeTest.kt Kotlin parameter tests
java/kotlin/src/test/kotlin/dev/varar/kotlin/ExecuteIntegrationTest.kt Kotlin execution tests
java/kotlin/src/test/kotlin/dev/varar/kotlin/ConformanceTest.kt Kotlin conformance tests
java/kotest/src/main/kotlin/dev/varar/kotest/OathSpec.kt Kotest integration
java/junit/src/main/java/dev/varar/junit/OathFileSelectorResolver.java JUnit integration
java/core/src/test/java/dev/varar/core/FailureStepSpanTest.java Failure span tests
java/core/src/test/java/dev/varar/core/ExecuteTest.java Execution tests
java/core/src/test/java/dev/varar/core/DriftTest.java Drift tests
java/core/src/main/java/dev/varar/core/Diagnostics.java Java diagnostics
java/core/src/main/java/dev/varar/core/Conformance.java Java conformance support
go/varar/adapt_test.go Go adapter tests
go/runner/runner_test.go Go runner tests
go/runner/run.go Go runner integration
go/gotest/gotest.go Go test integration
go/core/reference.go Go reference resolution
go/core/plan_test.go Go plan tests
go/core/drift_test.go Go drift tests
go/core/diagnostics.go Go diagnostics
go/core/conformance.go Go conformance support
go/conformance/conformance_test.go Go conformance tests
go/conformance/b21/library.steps.go Consumed-section fixture
go/conformance/b20/library.steps.go Splice fixture
examples/typescript-vitest/varar/shared/an-overdue-loan.md Shared example
examples/typescript-vitest/varar/reuse.md Reuse example
examples/typescript-vitest/varar.lock.json Example lockfile
examples/rust-cargotest/tests/unit.rs Rust example tests
dotnet/Varar.Tests/TraceConformanceTests.cs .NET trace conformance
dotnet/Varar.Tests/PlanConformanceTests.cs .NET plan conformance
dotnet/Varar.Tests/ConformanceFixtures.cs .NET fixtures
dotnet/Varar.TestAdapter/VararAdapter.cs .NET adapter integration
dotnet/Varar.Runner/Runner.cs .NET runner integration
dotnet/Varar.Core/Diagnostics.cs .NET diagnostics
dotnet/Varar.Core/Conformance.cs .NET conformance support
dotnet/Varar.Core.Tests/RunnerTests.cs .NET runner tests
dotnet/Varar.Core.Tests/PlanTests.cs .NET plan tests
dotnet/Varar.Core.Tests/FailureTests.cs .NET failure tests
conformance/parity.json Capability registration
conformance/bundles/21-reference-consumed/shared.md Consumed-section fixture
conformance/bundles/21-reference-consumed/LibrarySteps.java Java fixture
conformance/bundles/21-reference-consumed/library.steps.ts TypeScript fixture
conformance/bundles/21-reference-consumed/library.steps.rs Rust fixture
conformance/bundles/21-reference-consumed/library.steps.rb Ruby fixture
conformance/bundles/21-reference-consumed/library.steps.py Python fixture
conformance/bundles/21-reference-consumed/library.steps.kt Kotlin fixture
conformance/bundles/21-reference-consumed/library.steps.go Go fixture
conformance/bundles/21-reference-consumed/library.steps.cs .NET fixture
conformance/bundles/21-reference-consumed/golden/trace.json Trace golden
conformance/bundles/21-reference-consumed/golden/registry.json Registry golden
conformance/bundles/21-reference-consumed/golden/plan.json Plan golden
conformance/bundles/21-reference-consumed/golden/doc.json Document golden
conformance/bundles/21-reference-consumed/example.md Consumed-section example
conformance/bundles/20-reference-splice/shared.md Splice fixture
conformance/bundles/20-reference-splice/LibrarySteps.java Java fixture
conformance/bundles/20-reference-splice/library.steps.ts TypeScript fixture
conformance/bundles/20-reference-splice/library.steps.rs Rust fixture
conformance/bundles/20-reference-splice/library.steps.rb Ruby fixture
conformance/bundles/20-reference-splice/library.steps.py Python fixture
conformance/bundles/20-reference-splice/library.steps.kt Kotlin fixture
conformance/bundles/20-reference-splice/library.steps.go Go fixture
conformance/bundles/20-reference-splice/library.steps.cs .NET fixture
conformance/bundles/20-reference-splice/golden/trace.json Trace golden
conformance/bundles/20-reference-splice/golden/registry.json Registry golden
conformance/bundles/20-reference-splice/golden/plan.json Plan golden
conformance/bundles/20-reference-splice/golden/doc.json Document golden
conformance/bundles/20-reference-splice/example.md Splice example
conformance/adapter/smoke.sh Adapter smoke checks
Review details

Files not reviewed (1)

  • typescript/pnpm-lock.yaml: Generated file

Suppressed comments (74)

dotnet/Varar.Core/Plan.cs:315

  • structurer stores an immediately following table or fence in the same example body, but this first-body-only check still classifies [Setup](...) plus that attachment as a reference and drops the attachment. That violates the link-only rule and silently loses deferred parameter tables or error fences. Require exactly one body block here, and use the same predicate in the workspace's References scan.
    dotnet/Varar.Core/Plan.cs:16
  • Although this records the source document for a spliced step, Execute.cs still reports failures against the containing plan's path. Failures in referenced sections therefore point at the host oath instead of the file containing step.MatchSpan; use step.DocPath ?? plan.Doc.Path when constructing the failure location.
    dotnet/Varar.Core/Plan.cs:303
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with no DocPath because target.Path == from.Path; the outer reference branch appends those steps without tagging them. A host → shared#outer → shared#inner chain consequently loses the shared source identity. Preserve an existing DocPath when tagging recursive results.
    dotnet/Varar.Core/Plan.cs:112
  • StartMerged(resolved[i]) copies offsets from the referenced document, while FinishMerged converts them using the host oath's source. A cross-file reference consequently gives PlannedExample.Span a host line/column unrelated to the shared step, which misplaces test/editor locations just as in the new splice golden. The merged example needs to retain a host span or its source identity instead of mixing documents.
    dotnet/Varar.Core/Plan.cs:284
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying the expected-failure flag, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make result nonempty. Reject/report these constructs instead.
    dotnet/Varar.Core/Reference.cs:158
  • This inbound-reference scan applies the same first-body-only rule as plan(), so a link paragraph followed by a table or fence marks the section as consumed even though it is not a reference block. Keep this index predicate identical to the planner's full-body link-only check; otherwise standalone planning is suppressed for invalid references.
    dotnet/Varar.Core/Reference.cs:133
  • The workspace-relative oath-path contract preserves ../ for documents outside the root, but this join drops .. once segments is empty. A link from varar/a.md to ../../shared/b.md therefore becomes shared/b.md instead of ../shared/b.md, which can resolve to the wrong oath or a false reference-not-found. Preserve unresolved leading .. segments.
    dotnet/Varar.Core/Reference.cs:55
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    dotnet/Varar.TestAdapter/VararAdapter.cs:102
  • Consumed oaths have no plan.Examples, so no test case reaches results.Record for the defining oath. FlushAll only writes paths with recorded examples, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath with its source and emit an empty Examples array, as the Vitest adapter does.
    go/core/diagnostics.go:64
  • Although the caller constructs the complete chain, referenceCycle drops it and creates a diagnostic containing only code, severity, and span. ADR 0016 requires the whole cycle chain to be reported, so Go users cannot identify which references form the cycle. Add the chain to the diagnostic payload and its renderers.
    go/core/plan.go:322
  • structurer stores an immediately following table or fence in the same example body, but this first-body-only check still classifies [Setup](...) plus that attachment as a reference and drops the attachment. That violates the link-only rule and silently loses deferred parameter tables or error fences. Require exactly one body block here, and use the same predicate in the workspace's References scan.
    go/core/plan.go:314
  • A spliced step's MatchSpan and ParamSpans still point into the referenced document, but this only changes DocPath. Consumers use those spans with the host oath's source (for example to place static tests and diagnostics), so referenced steps can be sliced or located at unrelated offsets. Retag the step's source spans consistently, or otherwise make every span consumer use DocPath rather than the host source.
    go/core/plan.go:51
  • The planner now records the parameter notation from the document where each step was written, but execute.go still slices the host plan.Doc.Source with step.ParamSpans. For a referenced step those offsets belong to the shared document, so a sensor mismatch displays unrelated host-oath text as its expected value. Consume step.ParamTexts in the executor.
    go/core/plan.go:54
  • Although this records the source document for a spliced step, execute.go still reports failures against the containing plan's path. Failures in referenced sections therefore point at the host oath instead of the file containing step.MatchSpan; use step.DocPath when constructing the failure location.
    go/core/plan.go:312
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with an empty DocPath because target.Path == from.Path; the outer reference branch appends those steps without tagging them. A host → shared#outer → shared#inner chain consequently loses the shared source identity. Preserve an existing DocPath when tagging recursive results.
    go/core/plan.go:127
  • startMerged(spliced) copies offsets from the referenced document, while finishMerged converts them using the host oath's source. A cross-file reference consequently gives PlannedExample.span a host line/column unrelated to the shared step, which misplaces test/editor locations just as in the new splice golden. The merged example needs to retain a host span or its source identity instead of mixing documents.
    go/core/plan.go:292
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying ExpectedOutcome, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make out nonempty. Reject/report these constructs instead.
    go/core/reference.go:142
  • This inbound-reference scan applies the same first-body-only rule as plan(), so a link paragraph followed by a table or fence marks the section as consumed even though it is not a reference block. Keep this index predicate identical to the planner's full-body link-only check; otherwise standalone planning is suppressed for invalid references.
    go/core/reference.go:121
  • The workspace-relative oath-path contract preserves ../ for documents outside the root, but this join drops .. once segments is empty. A link from varar/a.md to ../../shared/b.md therefore becomes shared/b.md instead of ../shared/b.md, which can resolve to the wrong oath or a false reference-not-found. Preserve unresolved leading .. segments.
    go/core/reference.go:47
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    go/gotest/gotest.go:93
  • Consumed oaths now produce an empty plan.Examples, so this collector never creates a result entry for the defining oath. FlushAll iterates only paths recorded by a test case, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath with its source and emit an empty Examples array, as the Vitest adapter does.
    java/core/src/main/java/dev/varar/core/Plan.java:356
  • A spliced step's matchSpan and paramSpans still point into the referenced document, but this only changes docPath. Consumers use those spans with the host oath's source (for example to place static tests and diagnostics), so referenced steps can be sliced or located at unrelated offsets. Retag the step's source spans consistently, or otherwise make every span consumer use docPath rather than the host source.
    java/core/src/main/java/dev/varar/core/Plan.java:372
  • structurer stores an immediately following table or fence in the same example body, but this first-body-only check still classifies [Setup](...) plus that attachment as a reference and drops the attachment. That violates the link-only rule and silently loses deferred parameter tables or error fences. Require exactly one body block here, and use the same predicate in the workspace's references() scan.
    java/core/src/main/java/dev/varar/core/Plan.java:89
  • The planner now records the parameter notation from the document where each step was written, but Execute.java still slices the host plan.doc().source() with step.paramSpans(). For a referenced step those offsets belong to the shared document, so a sensor mismatch displays unrelated host-oath text as its expected value. Consume step.paramTexts() in the executor.
    java/core/src/main/java/dev/varar/core/Plan.java:91
  • Although this records the source document for a spliced step, Execute.java still builds the synthetic failure frame from the containing oath path. Failures in referenced sections therefore point at the host oath instead of the file containing step.matchSpan(); use step.docPath() when reporting the step.
    java/core/src/main/java/dev/varar/core/Plan.java:356
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with no docPath because target.path() equals from.path(); the outer reference branch appends those steps without tagging them. A host → shared#outer → shared#inner chain consequently loses the shared source identity. Preserve an existing step path when tagging recursive results.
    java/core/src/main/java/dev/varar/core/Plan.java:162
  • startMerged(spliced) copies offsets from the referenced document, while finishMerged converts them using the host oath's source. A cross-file reference consequently gives PlannedExample.span a host line/column unrelated to the shared step, which misplaces test/editor locations just as in the new splice golden. The merged example needs to retain a host span or its source identity instead of mixing documents.
    java/core/src/main/java/dev/varar/core/Plan.java:318
  • The cycle check has the chain in scope, but this diagnostic keeps only the code and span. ADR 0016 requires the whole chain (for example library.md#stocked → billing.md#fees → library.md#stocked) to be reported; Java callers therefore lose the only information that identifies which references form the cycle. Preserve the chain in the diagnostic and its renderers.
    java/core/src/main/java/dev/varar/core/Plan.java:342
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying the expected-failure flag, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make out nonempty. Reject/report these constructs instead.
    java/core/src/main/java/dev/varar/core/Reference.java:116
  • The workspace-relative oath-path contract preserves ../ for documents outside the root, but this join drops .. once segments is empty. A link from varar/a.md to ../../shared/b.md therefore becomes shared/b.md instead of ../shared/b.md, which can resolve to the wrong oath or a false reference-not-found. Preserve unresolved leading .. segments.
    java/core/src/main/java/dev/varar/core/Reference.java:133
  • This inbound-reference scan applies the same first-body-only rule as plan(), so a link paragraph followed by a table or fence marks the section as consumed even though it is not a reference block. Keep this index predicate identical to the planner's full-body link-only check; otherwise standalone planning is suppressed for invalid references.
    java/core/src/main/java/dev/varar/core/Reference.java:54
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    java/junit/src/main/java/dev/varar/junit/OathFileSelectorResolver.java:366
  • Consumed oaths now produce an empty plan.examples(), so this descriptor never records a result for the defining oath. after() calls Results.flush, which currently returns without writing when nothing was recorded, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath and emit an empty examples array, as the Vitest adapter does.
    java/kotest/src/main/kotlin/dev/varar/kotest/OathSpec.kt:78
  • Consumed oaths now produce an empty plan.examples, so this spec never records a result for the defining oath. afterSpec flushes only recorded paths, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath and emit an empty examples array, as the Vitest adapter does.
    python/packages/core/src/varar_core/plan.py:551
  • structurer stores an immediately following table or fence in the same example body, but this first-body-only check still classifies [Setup](...) plus that attachment as a reference and drops the attachment. That violates the link-only rule and silently loses deferred parameter tables or error fences. Require exactly one body block here, and use the same predicate in the workspace's references() scan.
    python/packages/core/src/varar_core/plan.py:69
  • Although this records the source document for a spliced step, the Python executor still reports using the containing plan's oath path. Failures in referenced sections therefore point at the host oath instead of the file containing step.match_span; use step.doc_path when constructing the failure location.
    python/packages/core/src/varar_core/plan.py:535
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with no doc_path because target.path == from_doc.path; the outer reference branch appends those steps without tagging them. A host → shared#outer → shared#inner chain consequently loses the shared source identity. Preserve an existing step.doc_path when tagging recursive results.
    python/packages/core/src/varar_core/plan.py:456
  • _start_merged(spliced) copies offsets from the referenced document, while _finish_merged converts them using the host oath's source. A cross-file reference consequently gives PlannedExample.span a host line/column unrelated to the shared step, which misplaces test/editor locations just as in the new splice golden. The merged example needs to retain a host span or its source identity instead of mixing documents.
    python/packages/core/src/varar_core/plan.py:520
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying expected_outcome, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make out nonempty. Reject/report these constructs instead.
    python/packages/core/src/varar_core/reference.py:110
  • This inbound-reference scan applies the same first-body-only rule as plan(), so a link paragraph followed by a table or fence marks the section as consumed even though it is not a reference block. Keep this index predicate identical to the planner's full-body link-only check; otherwise standalone planning is suppressed for invalid references.
    python/packages/core/src/varar_core/reference.py:97
  • The workspace-relative oath-path contract preserves ../ for documents outside the root, but this join drops .. once segments is empty. A link from varar/a.md to ../../shared/b.md therefore becomes shared/b.md instead of ../shared/b.md, which can resolve to the wrong oath or a false reference-not-found. Preserve unresolved leading .. segments.
    python/packages/core/src/varar_core/reference.py:71
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    python/packages/pytest/src/varar_pytest/plugin.py:125
  • Consumed oaths now produce an empty execution_plan.examples, so this collector never creates a result entry for the defining oath. pytest_sessionfinish only writes keys recorded by ResultsCollector, leaving .varar/<consumed-oath>.json absent or stale; seed the collector for every discovered oath (and emit an empty examples array) just as the Vitest adapter does.
    python/packages/unittest/src/varar_unittest/init.py:125
  • Consumed oaths now produce an empty execution_plan.examples, so this collector never creates a result entry for the defining oath. The process-exit flush only writes keys recorded by ResultsCollector, leaving .varar/<consumed-oath>.json absent or stale; seed the collector for every discovered oath (and emit an empty examples array) just as the Vitest adapter does.
    ruby/packages/core/lib/varar/core/plan.rb:224
  • structurer stores an immediately following table or fence in the same example body, but this first-body-only check still classifies [Setup](...) plus that attachment as a reference and drops the attachment. That violates the link-only rule and silently loses deferred parameter tables or error fences. Require exactly one body block here, and use the same predicate in the workspace's references scan.
    ruby/packages/core/lib/varar/core/plan.rb:21
  • Although this records the source document for a spliced step, the Ruby execution/result path does not use doc_path when reporting a failure; Failures.to_failure ignores the oath path and the executor only attaches the span. A failure in a referenced section therefore cannot identify the file containing that span. Thread step.doc_path through the failure/result representation (separate from the acknowledged persisted-result v2 work).
    ruby/packages/core/lib/varar/core/plan.rb:180
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with no doc_path because target.path == from_doc.path; the outer reference branch appends those steps without tagging them. A host → shared#outer → shared#inner chain consequently loses the shared source identity. Preserve an existing step path when tagging recursive results.
    ruby/packages/core/lib/varar/core/plan.rb:112
  • start_merged(spliced) copies offsets from the referenced document, while finish_merged converts them using the host oath's source. A cross-file reference consequently gives PlannedExample.span a host line/column unrelated to the shared step, which misplaces test/editor locations just as in the new splice golden. The merged example needs to retain a host span or its source identity instead of mixing documents.
    ruby/packages/core/lib/varar/core/plan.rb:166
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying expected_outcome, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make out nonempty. Reject/report these constructs instead.
    ruby/packages/core/lib/varar/core/reference.rb:89
  • The workspace-relative oath-path contract preserves ../ for documents outside the root, but this join drops .. once segments is empty. A link from varar/a.md to ../../shared/b.md therefore becomes shared/b.md instead of ../shared/b.md, which can resolve to the wrong oath or a false reference-not-found. Preserve unresolved leading .. segments.
    ruby/packages/core/lib/varar/core/reference.rb:100
  • This inbound-reference scan applies the same first-body-only rule as plan(), so a link paragraph followed by a table or fence marks the section as consumed even though it is not a reference block. Keep this index predicate identical to the planner's full-body link-only check; otherwise standalone planning is suppressed for invalid references.
    ruby/packages/core/lib/varar/core/reference.rb:64
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    ruby/packages/minitest/lib/varar/minitest.rb:68
  • Consumed oaths now produce no pairs, so this test case never records a result for the defining oath. flush_all therefore has nothing to write, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath and emit an empty examples array, as the Vitest adapter does.
    ruby/packages/rspec/lib/varar/rspec.rb:66
  • Consumed oaths now produce no pairs, so this group never records a result for the defining oath. results.flush therefore returns without writing, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath and emit an empty examples array, as the Vitest adapter does.
    rust/cargotest/src/lib.rs:132
  • Consumed oaths now produce an empty execution.examples, so this collector never creates a result entry for the defining oath. flush_all iterates only recorded examples, leaving .varar/<consumed-oath>.json absent or stale; register every discovered oath with its source and emit an empty examples array, as the Vitest adapter does.
    rust/core/src/plan.rs:351
  • structurer stores an immediately following table or fence in the same example body, but this first-body-only check still classifies [Setup](...) plus that attachment as a reference and drops the attachment. That violates the link-only rule and silently loses deferred parameter tables or error fences. Require exactly one body block here, and use the same predicate in the workspace's references() scan.
    rust/core/src/plan.rs:65
  • Although this records the source document for a spliced step, execute.rs still passes the containing plan's oath path to the failure location. Failures in referenced sections therefore point at the host oath instead of the file containing step.match_span; use step.doc_path when constructing the location.
    rust/core/src/plan.rs:334
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with no doc_path because target.path == from.path; the outer reference branch appends those steps without tagging them. A host → shared#outer → shared#inner chain consequently loses the shared source identity. Preserve an existing step.doc_path when tagging recursive results.
    rust/core/src/plan.rs:134
  • start_merged(spliced) copies offsets from the referenced document, while finish_merged converts them using the host oath's source. A cross-file reference consequently gives PlannedExample.span a host line/column unrelated to the shared step, which misplaces test/editor locations just as in the new splice golden. The merged example needs to retain a host span or its source identity instead of mixing documents.
    rust/core/src/plan.rs:281
  • The cycle check has the chain in scope, but this diagnostic keeps only the code and span. ADR 0016 requires the whole chain (for example library.md#stocked → billing.md#fees → library.md#stocked) to be reported; Rust callers therefore lose the only information that identifies which references form the cycle. Preserve the chain in the diagnostic and its renderers.
    rust/core/src/plan.rs:315
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying expected_outcome, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make out nonempty. Reject/report these constructs instead.
    rust/core/src/reference.rs:135
  • This inbound-reference scan applies the same first-body-only rule as plan(), so a link paragraph followed by a table or fence marks the section as consumed even though it is not a reference block. Keep this index predicate identical to the planner's full-body link-only check; otherwise standalone planning is suppressed for invalid references.
    rust/core/src/reference.rs:122
  • The workspace-relative oath-path contract preserves ../ for documents outside the root, but this join drops .. once segments is empty. A link from varar/a.md to ../../shared/b.md therefore becomes shared/b.md instead of ../shared/b.md, which can resolve to the wrong oath or a false reference-not-found. Preserve unresolved leading .. segments.
    rust/core/src/reference.rs:51
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    typescript/packages/core/src/plan.ts:345
  • structurer appends an immediately following table or fence to this Example.body, but this check only examines body[0]. A link followed by an attached table or error fence is therefore treated as a reference and the attachment is silently discarded, even though the reference syntax requires the entire block content to be one link (and parameterized references are deferred). Require the candidate to contain exactly one body block here, and apply the same condition in references() so the workspace does not consume the target for a non-reference.
    typescript/packages/core/src/plan.ts:78
  • The planner now records the parameter notation from the document where each step was written, but execute.ts still slices plan.doc.source with step.paramSpans. For a referenced step those offsets belong to the shared document, so a sensor mismatch displays unrelated host-oath text as its expected value. Consume step.paramTexts in the executor.
    typescript/packages/core/src/plan.ts:69
  • Although this records the source document for a spliced step, execute.ts still passes the containing plan's doc.path to augmentStack. Failures in referenced sections therefore point terminals and failure locations at the host oath instead of the file containing step.matchSpan; use step.docPath ?? plan.doc.path when reporting the step.
    typescript/packages/core/src/plan.ts:244
  • When a referenced section contains another reference in the same target document, the recursive call returns steps with no docPath because target.path === from.path; the outer reference branch appends those steps without tagging them. A cross-file chain such as host → shared#outer → shared#inner consequently loses the shared source identity. Preserve an existing step.docPath when tagging the recursive result.
    typescript/packages/core/src/plan.ts:219
  • ADR 0016 declares header-bound tables and error fences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carrying expectedOutcome, so a referenced error-fenced fragment can change the host example's expected result instead of failing as an invalid reference; a table is also silently hidden whenever other steps make out nonempty. Reject/report these constructs instead.
    typescript/packages/core/src/reference.ts:102
  • This workspace index uses the same first-body-only test as the planner. Because attachments are stored in the same Example.body, [Setup](...) followed by a table/fence is added to referenced even though it is not a link-only block; the defining section can then be suppressed as consumed while the referring block is ordinary content. Restrict the index to candidates whose body has exactly one text-bearing block, in sync with the planner.
    typescript/packages/core/src/reference.ts:59
  • The docs promise GFM-compatible heading anchors, but this regex removes any underscore pair without GFM's intraword delimiter rules. For ## Foo_bar_baz, GFM keeps the underscores literal and the slug is foo_bar_baz, while this computes foobarbaz; a valid #foo_bar_baz reference is then reported as missing/empty. Use GFM-aware emphasis parsing (or preserve intraword underscores) and cover this case.
    typescript/packages/lsp/src/server.ts:83
  • This new guarantee is false: var/renderExpressionText still invokes handlers.renderExpressionText without awaiting settled(), even though that handler reads the live registry. A step-file edit can therefore produce a stale rendered expression until another request triggers a reindex; make that request await settled() as well.
    typescript/packages/lsp/src/server.ts:121
  • The change marks the index dirty only after awaiting the filesystem write. A request can enter settled() during that await, observe dirty === false, and return the old inFlight promise, so completion/hover/diagnostic work can read a stale workspace after the editor has already sent the change. Mark the update as pending before the await while still serializing reindexing after the write.
    typescript/packages/vitest/src/plugin.ts:174
  • The ADR specifies a skipped-suite placeholder for an oath whose sections are all consumed, but this generated module registers an ordinary passing test(...). That makes a shared-only oath appear as a successful standalone test in Vitest's counts/reporter instead of skipped, contradicting the intended consumed-oath semantics. Keep the bookkeeping needed for empty results/baselines while making the placeholder skipped.
  • Files reviewed: 159/160 changed files
  • Comments generated: 9
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +99 to +103
// Every oath in the project, plus which sections a reference block consumes
// (ADR 0016). Required, not defaulted: a caller that has not built it would
// otherwise silently run consumed sections as standalone examples — green,
// and wrong. Pass emptyWorkspace() to plan a document in isolation.
workspace: OathWorkspace,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0239689: the playground now plans against emptyWorkspace() (it runs one oath on its own), and the stray third argument to parse() is gone. For the record, astro check is not a CI gate and already reports five unrelated errors on main (cm-generate-step.ts, var-worker.ts), so this was a runtime failure rather than a build failure — the call threw when plan() dereferenced the missing workspace.

Comment thread typescript/packages/lsp/src/server.ts Outdated
Comment on lines +117 to +121
documents.onDidChangeContent(async (e) => {
await opts?.onDidChangeDocument?.(e.document.uri, e.document.getText())
if (!store) return
await store.fs().write(uriToPath(e.document.uri), e.document.getText())
await store.reindex()
afterReindex()
scheduleReindex()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a30c4a8. Write-throughs are chained on a single promise, the index is marked dirty at the moment of the edit (before the write lands), and settled() awaits the write chain before reindexing — so no request can observe an index older than the edit it answers about, and a late-finishing older write can no longer overwrite a newer buffer.

Comment thread dotnet/Varar.Core/Plan.cs
private static StepsUnit TagWithDoc(StepsUnit unit, string docPath, string hostPath) =>
docPath == hostPath
? unit
: unit with { Steps = [.. unit.Steps.Select(s => s with { DocPath = docPath })] };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by design: a spliced step's spans belong to the document it was written in, and docPath says which — the alternative (rebasing them onto the host) would leave a mismatch diff pointing at text that isn't there. Two things did need fixing, and are: the example span (which is host-side) was built from the foreign offsets — 691b2c4 places it at the reference block in every port, pinned by the new 23-reference-only-example bundle; and the consumers that read step spans against the running oath's source are addressed by #112, where the run result and each port's failure placement carry docPath (run-result v2).

Comment on lines +533 to +536
return replace(
unit,
steps=tuple(replace(step, doc_path=doc_path) for step in unit.steps),
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by design: a spliced step's spans belong to the document it was written in, and docPath says which — the alternative (rebasing them onto the host) would leave a mismatch diff pointing at text that isn't there. Two things did need fixing, and are: the example span (which is host-side) was built from the foreign offsets — 691b2c4 places it at the reference block in every port, pinned by the new 23-reference-only-example bundle; and the consumers that read step spans against the running oath's source are addressed by #112, where the run result and each port's failure placement carry docPath (run-result v2).

def tag_with_doc(unit, doc_path, host_path)
return unit if doc_path == host_path

unit.with(steps: unit.steps.map { |step| step.with(doc_path: doc_path) })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by design: a spliced step's spans belong to the document it was written in, and docPath says which — the alternative (rebasing them onto the host) would leave a mismatch diff pointing at text that isn't there. Two things did need fixing, and are: the example span (which is host-side) was built from the foreign offsets — 691b2c4 places it at the reference block in every port, pinned by the new 23-reference-only-example bundle; and the consumers that read step spans against the running oath's source are addressed by #112, where the run result and each port's failure placement carry docPath (run-result v2).

Comment thread rust/core/src/plan.rs
Comment on lines +333 to +335
for step in &mut unit.steps {
step.doc_path = Some(doc_path.to_string());
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by design: a spliced step's spans belong to the document it was written in, and docPath says which — the alternative (rebasing them onto the host) would leave a mismatch diff pointing at text that isn't there. Two things did need fixing, and are: the example span (which is host-side) was built from the foreign offsets — 691b2c4 places it at the reference block in every port, pinned by the new 23-reference-only-example bundle; and the consumers that read step spans against the running oath's source are addressed by #112, where the run result and each port's failure placement carry docPath (run-result v2).

Comment on lines +243 to +245
if (docPath === hostPath) return unit
return { ...unit, steps: unit.steps.map((step) => ({ ...step, docPath })) }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 691b2c4, in all seven ports: an example a reference opens starts at the reference block, ends there until a paragraph of its own extends it, and keeps the referring document's scopeStack. Bundle 20's golden had pinned the wrong span (line 5 col 31, offset 114 — a shared.md offset read against example.md) and moves to the reference block; 23-reference-only-example pins the reference-only case. The dogfood varar.lock.json had recorded a prose paragraph as live because the wrong span covered it, and is re-recorded.

Comment on lines +88 to +89
if (segment === '..') segments.pop()
else segments.push(segment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 65aa3e9, in all seven ports: a .. with nothing left to climb out of is kept, so ../../shared/b.md from varar/a.md resolves to ../shared/b.md, matching what toOathPath records.

if (!def) continue
matches.push({
fileMatches.push({
oathPath: file.path,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8535bcc: a spliced match is labelled with the oath its ranges address (step.docPath ?? file.path), and a section two oaths reference yields one match rather than one per referrer, so rename edits don't double up.

…nce block, under its own headings

The steps a reference splices in keep their spans in the document they were
written in — that is the point of docPath — but the EXAMPLE they open belongs
to the referring document. The merged example took its start/end offsets and
its heading chain from the spliced unit, so finishMerged() read another
file's offsets against the host source: the example landed on an unrelated
line (or past the end of the file), and a reference-only example sat under
the shared section's headings instead of its own.

The example now starts and ends at the reference block, extends only when a
paragraph of its own follows, and keeps the referring document's scopeStack.
Bundle 20's golden pinned the wrong span and moves; 23-reference-only-example
pins an example that is nothing but a reference. The dogfood baseline had
recorded a prose paragraph as live because the wrong span covered it — it is
re-recorded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQVAKGfMEKPbT919FqAChP
aslakhellesoy and others added 4 commits September 14, 2026 12:12
…leading ../

toOathPath deliberately keeps `../` for an oath matched outside the root, but
the link resolver clamped a `..` with nothing left to climb out of, so
`../../shared/b.md` from `varar/a.md` resolved to `shared/b.md` — the wrong
oath, or a false reference-not-found. An unresolvable `..` now stays in the
path, in every port.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQVAKGfMEKPbT919FqAChP
Two keystrokes in quick succession were two independent async writes of the
same file; the earlier one finishing last left the older text on disk for
the debounced reindex to read. Write-throughs are now serialised, the index
is marked dirty before the write completes, and a reindex waits for every
write queued ahead of it — so no request can observe an index older than the
edit it answers about.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQVAKGfMEKPbT919FqAChP
The workspace index labelled a step a reference block spliced in with the
referring oath, while its ranges addressed the referenced one. The editor
looks matches up by path, so the host file was painted at another file's
offsets and the shared file showed nothing. A spliced match now carries its
own document, and a section two oaths reference yields one match, not two.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQVAKGfMEKPbT919FqAChP
plan() takes the workspace as a required argument (ADR 0016); the browser
runner still called it with the old arity, which threw at run time. The
playground runs one oath on its own, so the empty workspace is the honest
one. The stray third argument to parse() goes too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQVAKGfMEKPbT919FqAChP
@aslakhellesoy
aslakhellesoy merged commit 36a494b into main Sep 14, 2026
11 checks passed
@aslakhellesoy
aslakhellesoy deleted the reuse-is-a-link branch September 14, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants