WORK-618
ID:WORK-618Status:ready

Resolve preprocessors in one tree-order walk

SPEC-141's main change. Replace the three whole-AST passes (walkAndReplaceIncludes, walkAndReplaceSnippets, walkAndReplaceData) and their hand-written order in config.ts with one walk that dispatches per tag, and move each preprocessor into the tag module that owns it:

Priority:highComplexity:complexMilestone:v0.39.0Source:SPEC-141
changeset-release/main View source

Criteria completion

Criteria completion: 0 of 12 (0%) checked; tracking started on Oct 7, no incremental history yet0%25%50%75%100%Oct 7Oct 11

Tracking started Oct 7 — check back for trends.

Branches 5
History 4
  1. 9870c2a
    Content editedby Claude
    docs(plan): close WORK-615, WORK-618 and BUG-027 with resolutions and PR
  2. b77900b
    Content editedby Claude
    test(runes): preprocessors resolve in tree order — failing (BUG-027)
  3. c9b8c9c
    Created (ready)by github-actions[bot]
  4. 1046f41
    Content editedby Claude
    docs(plan): add v0.39.0 milestone, Declarations part two

Decisions carried from the spec

  • D1 — the hook receives its own node, not the tree; Node[] splices.
  • D2 — PluginPipelineHooks.preprocess stays, undeprecated.
  • D3 — include keeps its own nested expansion and cycle stack; data's internal subquery walk is removed in favour of the generic descent.
  • D4 — the ancestor chain is part of the walk context; data's enclosing check reads it.
  • D7 — snippet's throwing transform stays.
  • D8 — emitTag stays declarative; no content model is narrowed.

Acceptance Criteria

  • ContentModelSchemaOptions accepts preprocess, receiving the rune's own node and returning a replacement, several, or nothing
  • One walk resolves all preprocessors; walkAndReplaceIncludes, walkAndReplaceSnippets and walkAndReplaceData no longer exist as three separate traversals
  • The walk descends into a replacement, and data's internal subquery walk is removed in favour of it (D3)
  • include keeps its own nested expansion and its cycle stack; a direct and an indirect cycle are still named rather than overflowing
  • snippet's and include's preprocessors live in their tag modules; snippet-pipeline.ts and include-pipeline.ts are gone as top-level modules
  • config.ts no longer states a preprocessor order
  • {% snippet %} inside a {% data %} row template resolves against the bound row — BUG-027, with a regression test
  • Nested {% data %} still resolves its subquery per outer row, with the outer row binding the inner tag's attributes and not its body (SPEC-127 / WORK-553)
  • {% snippet %} and {% data %} inside an included file still resolve — the existing include-pipeline.test.ts cases pass unchanged
  • emitTag remains a declarative field; no rune's content model is narrowed to match the emitted tag, and match still states the authored input shape (D8)
  • refrakt contracts --check and npm run seo:baseline:check report no drift
  • The rune authoring guide documents preprocess, and says when to reach for the plugin-level hook instead (D2)

Blocked by

  • WORK-615

Blocks

  • BUG-027

References

  • SPEC-141 — the spec
  • SPEC-127 — per-row templates; what made data a producer
  • SPEC-129 — include and the load-bearing order this replaces