fabro/lib
Scott Werner ee7453418b
Unify @file inlining under an ImportableTemplate type (prompt + goal) (#527)
## What

Introduces an `ImportableTemplate` type that unifies the "inline content
**or**
`@path` file import" concept used by node `prompt`s, the graph `goal`,
and
`output_schema`. This is the last template-side piece of the
interpolation
unification: a single named type now owns the `@`-classification and
static-reference validation that was previously hand-rolled in three
places.

This is a **behavior-preserving refactor** — no user-visible change.

## How

- New `ImportableTemplate { Inline(String), Import { path } }` in
`transforms/importable_template.rs`, with `parse` (classifies a value —
a
leading `@` marks a file import), `import_path`, and `validate` (rejects
template syntax in an import path). Callers of templated fields classify
the
  **already-rendered** string, because a leading `@` can be produced by
  rendering (e.g. `{{ inputs.prompt_file }}` → `@prompts/work.md`).
- `prompt` + `goal`: render the inline value, then — if it's an `@file`
import —
load and render the file contents via the type. The missing-file →
literal
  passthrough is preserved.
- `output_schema`: shares the same classification but is loaded
**verbatim** (it
is intentionally not a template), keeping its hard-error-on-missing-file
  behavior.
- Deletes the dead `resolve_file_ref` helper (no non-test callers) and
inlines
  the trivial `render_file_contents` wrapper.
- Migrates the `FilesystemFileResolver` coverage (tilde, `..`,
fallback-dir
  precedence, missing file) — which previously only existed through
  `resolve_file_ref`'s tests — onto direct `file_resolver` tests.

`TemplateTransform` and the import transform are untouched, so
goal-before-
prompts ordering and the goal-self-reference guard are preserved
exactly.

## Scope

Covers the DOT node `prompt` + graph `goal` `@file` path. The
settings-layer
`run.goal` resolution is intentionally left as-is — it uses a different
model
(interpolates env into the file path and does not render file contents),
so
folding it in would be a semantic change, not a refactor. That
convergence can
be a deliberate follow-up.

## Testing

- `cargo nextest run -p fabro-workflow` — 1182 passed (31
e2e/credentialed
skipped). New unit tests on the type (classification, validation) and
the
  migrated `FilesystemFileResolver` tests.
- Regression net kept green: file-inlining (prompt/goal, output_schema
  verbatim/error/routing, `{% include %}` rooting, fallback dir), the
`TemplateTransform` goal/self-reference/ordering tests, and the
cross-pass
  `reports_goal_self_reference_once_across_passes`.
- `cargo +nightly fmt --check --all` and nightly
  `clippy --workspace --all-targets -- -D warnings` clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:51:46 -04:00
..
crates Unify @file inlining under an ImportableTemplate type (prompt + goal) (#527) 2026-06-25 15:51:46 -04:00
packages/fabro-api-client feat(workflow): support overriding cwd for local sandbox provider (#467) 2026-06-14 12:32:32 -04:00