Reuse setup between examples by linking to a section (ADR 0016) - #104
Conversation
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
|
Follow-ups from this PR are filed, so the known gaps don't have to hold up the merge:
|
There was a problem hiding this comment.
🟡 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
structurerstores 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 orerrorfences. Require exactly one body block here, and use the same predicate in the workspace'sReferencesscan.
dotnet/Varar.Core/Plan.cs:16- Although this records the source document for a spliced step,
Execute.csstill reports failures against the containing plan's path. Failures in referenced sections therefore point at the host oath instead of the file containingstep.MatchSpan; usestep.DocPath ?? plan.Doc.Pathwhen 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
DocPathbecausetarget.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 existingDocPathwhen tagging recursive results.
dotnet/Varar.Core/Plan.cs:112 StartMerged(resolved[i])copies offsets from the referenced document, whileFinishMergedconverts them using the host oath's source. A cross-file reference consequently givesPlannedExample.Spana 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
errorfences 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 makeresultnonempty. 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..oncesegmentsis empty. A link fromvarar/a.mdto../../shared/b.mdtherefore becomesshared/b.mdinstead of../shared/b.md, which can resolve to the wrong oath or a falsereference-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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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 reachesresults.Recordfor the defining oath.FlushAllonly writes paths with recorded examples, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath with its source and emit an emptyExamplesarray, as the Vitest adapter does.
go/core/diagnostics.go:64 - Although the caller constructs the complete chain,
referenceCycledrops 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 structurerstores 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 orerrorfences. Require exactly one body block here, and use the same predicate in the workspace'sReferencesscan.
go/core/plan.go:314- A spliced step's
MatchSpanandParamSpansstill point into the referenced document, but this only changesDocPath. 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 useDocPathrather 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.gostill slices the hostplan.Doc.Sourcewithstep.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. Consumestep.ParamTextsin the executor.
go/core/plan.go:54 - Although this records the source document for a spliced step,
execute.gostill reports failures against the containing plan's path. Failures in referenced sections therefore point at the host oath instead of the file containingstep.MatchSpan; usestep.DocPathwhen 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
DocPathbecausetarget.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 existingDocPathwhen tagging recursive results.
go/core/plan.go:127 startMerged(spliced)copies offsets from the referenced document, whilefinishMergedconverts them using the host oath's source. A cross-file reference consequently givesPlannedExample.spana 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
errorfences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carryingExpectedOutcome, 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 makeoutnonempty. 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..oncesegmentsis empty. A link fromvarar/a.mdto../../shared/b.mdtherefore becomesshared/b.mdinstead of../shared/b.md, which can resolve to the wrong oath or a falsereference-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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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.FlushAlliterates only paths recorded by a test case, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath with its source and emit an emptyExamplesarray, as the Vitest adapter does.
java/core/src/main/java/dev/varar/core/Plan.java:356 - A spliced step's
matchSpanandparamSpansstill point into the referenced document, but this only changesdocPath. 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 usedocPathrather than the host source.
java/core/src/main/java/dev/varar/core/Plan.java:372 structurerstores 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 orerrorfences. Require exactly one body block here, and use the same predicate in the workspace'sreferences()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.javastill slices the hostplan.doc().source()withstep.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. Consumestep.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.javastill builds the synthetic failure frame from the containing oath path. Failures in referenced sections therefore point at the host oath instead of the file containingstep.matchSpan(); usestep.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
docPathbecausetarget.path()equalsfrom.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, whilefinishMergedconverts them using the host oath's source. A cross-file reference consequently givesPlannedExample.spana 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
errorfences 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 makeoutnonempty. 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..oncesegmentsis empty. A link fromvarar/a.mdto../../shared/b.mdtherefore becomesshared/b.mdinstead of../shared/b.md, which can resolve to the wrong oath or a falsereference-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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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()callsResults.flush, which currently returns without writing when nothing was recorded, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath and emit an emptyexamplesarray, 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.afterSpecflushes only recorded paths, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath and emit an emptyexamplesarray, as the Vitest adapter does.
python/packages/core/src/varar_core/plan.py:551 structurerstores 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 orerrorfences. Require exactly one body block here, and use the same predicate in the workspace'sreferences()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; usestep.doc_pathwhen 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_pathbecausetarget.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 existingstep.doc_pathwhen tagging recursive results.
python/packages/core/src/varar_core/plan.py:456 _start_merged(spliced)copies offsets from the referenced document, while_finish_mergedconverts them using the host oath's source. A cross-file reference consequently givesPlannedExample.spana 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
errorfences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carryingexpected_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 makeoutnonempty. 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..oncesegmentsis empty. A link fromvarar/a.mdto../../shared/b.mdtherefore becomesshared/b.mdinstead of../shared/b.md, which can resolve to the wrong oath or a falsereference-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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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_sessionfinishonly writes keys recorded byResultsCollector, leaving.varar/<consumed-oath>.jsonabsent or stale; seed the collector for every discovered oath (and emit an emptyexamplesarray) 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 byResultsCollector, leaving.varar/<consumed-oath>.jsonabsent or stale; seed the collector for every discovered oath (and emit an emptyexamplesarray) just as the Vitest adapter does.
ruby/packages/core/lib/varar/core/plan.rb:224 structurerstores 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 orerrorfences. Require exactly one body block here, and use the same predicate in the workspace'sreferencesscan.
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_pathwhen reporting a failure;Failures.to_failureignores the oath path and the executor only attaches the span. A failure in a referenced section therefore cannot identify the file containing that span. Threadstep.doc_paththrough 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_pathbecausetarget.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, whilefinish_mergedconverts them using the host oath's source. A cross-file reference consequently givesPlannedExample.spana 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
errorfences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carryingexpected_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 makeoutnonempty. 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..oncesegmentsis empty. A link fromvarar/a.mdto../../shared/b.mdtherefore becomesshared/b.mdinstead of../shared/b.md, which can resolve to the wrong oath or a falsereference-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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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_alltherefore has nothing to write, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath and emit an emptyexamplesarray, 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.flushtherefore returns without writing, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath and emit an emptyexamplesarray, 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_alliterates only recorded examples, leaving.varar/<consumed-oath>.jsonabsent or stale; register every discovered oath with its source and emit an emptyexamplesarray, as the Vitest adapter does.
rust/core/src/plan.rs:351 structurerstores 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 orerrorfences. Require exactly one body block here, and use the same predicate in the workspace'sreferences()scan.
rust/core/src/plan.rs:65- Although this records the source document for a spliced step,
execute.rsstill 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 containingstep.match_span; usestep.doc_pathwhen 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_pathbecausetarget.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 existingstep.doc_pathwhen tagging recursive results.
rust/core/src/plan.rs:134 start_merged(spliced)copies offsets from the referenced document, whilefinish_mergedconverts them using the host oath's source. A cross-file reference consequently givesPlannedExample.spana 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
errorfences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carryingexpected_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 makeoutnonempty. 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..oncesegmentsis empty. A link fromvarar/a.mdto../../shared/b.mdtherefore becomesshared/b.mdinstead of../shared/b.md, which can resolve to the wrong oath or a falsereference-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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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 structurerappends an immediately following table or fence to thisExample.body, but this check only examinesbody[0]. A link followed by an attached table orerrorfence 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 inreferences()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.tsstill slicesplan.doc.sourcewithstep.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. Consumestep.paramTextsin the executor.
typescript/packages/core/src/plan.ts:69 - Although this records the source document for a spliced step,
execute.tsstill passes the containing plan'sdoc.pathtoaugmentStack. Failures in referenced sections therefore point terminals and failure locations at the host oath instead of the file containingstep.matchSpan; usestep.docPath ?? plan.doc.pathwhen 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
docPathbecausetarget.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 existingstep.docPathwhen tagging the recursive result.
typescript/packages/core/src/plan.ts:219 - ADR 0016 declares header-bound tables and
errorfences inside referenced sections unreferenceable and requires an error. This branch merely drops header-bound units, and it accepts a matched unit carryingexpectedOutcome, 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 makeoutnonempty. 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 toreferencedeven 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 isfoo_bar_baz, while this computesfoobarbaz; a valid#foo_bar_bazreference 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/renderExpressionTextstill invokeshandlers.renderExpressionTextwithout awaitingsettled(), 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 awaitsettled()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, observedirty === false, and return the oldinFlightpromise, 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.
| // 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, |
There was a problem hiding this comment.
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.
| 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() |
There was a problem hiding this comment.
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.
| private static StepsUnit TagWithDoc(StepsUnit unit, string docPath, string hostPath) => | ||
| docPath == hostPath | ||
| ? unit | ||
| : unit with { Steps = [.. unit.Steps.Select(s => s with { DocPath = docPath })] }; |
There was a problem hiding this comment.
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).
| return replace( | ||
| unit, | ||
| steps=tuple(replace(step, doc_path=doc_path) for step in unit.steps), | ||
| ) |
There was a problem hiding this comment.
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) }) |
There was a problem hiding this comment.
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).
| for step in &mut unit.steps { | ||
| step.doc_path = Some(doc_path.to_string()); | ||
| } |
There was a problem hiding this comment.
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).
| if (docPath === hostPath) return unit | ||
| return { ...unit, steps: unit.steps.map((step) => ({ ...step, docPath })) } | ||
| } |
There was a problem hiding this comment.
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.
| if (segment === '..') segments.pop() | ||
| else segments.push(segment) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
…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
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:
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.tsfile — 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:
db65278b…dce7412b966cc3be(the last commit) for what actually shipped.c5216be5doc.pathmeant 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.4ed817fe397a3894,80e15ed3packages/core/src/reference.tsand the splice inplan.tsare the heart of it.2ba138f9…1287590037d43c56Conformance
Two new bundles pin the feature across all seven ports:
20-reference-splice— a spliced step, itsdocPath, 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.ScopeStackwasinit-only, so the name-replacement rule updated the name but left the referenced section's heading chain on the example.parity.jsongains thereferencecapability;make checkis green (all seven ports, parity, commit lint, adapter smoke contracts).Known gaps (recorded in the ADR)
docPathreaches 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.headingsfield toDoc, which kept everydoc.jsongolden untouched.Deviations from the ADR, and why
plan(), not the structurer — equally pure there, and it left every port'sdoc.jsongolden untouched.PlannedStepgainedparamTexts— 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
c5216be5also carries an unrelatedpnpm-lock.yamlchange:pnpm installpruned a stale@pnpm/exeentry. 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