Commit graph

10 commits

Author SHA1 Message Date
Bryan Helmkamp
74e2c3597c
Simplify model stylesheet template plumbing
Apply cleanup review findings on the model stylesheet template branch:

- Move the root-only stylesheet rule into visit_graph_references via a
  GraphPosition parameter, so the bundler and workflow-version stop
  re-implementing the entrypoint guard with duplicated match arms
- Let ModelStylesheetTemplateTransform build its own template store and
  skip the pass entirely when the graph has no stylesheet; drop its dead
  Transform impl and the template_render_store re-export
- Parse fix-message namespaces with the typed Namespace enum, share the
  vars/goal fix strings with script_interpolation_fix, and replace the
  attribute_name magic-string check with a restricted-namespace fix the
  stylesheet transform sets on its own render target
- Drop template_render_store's content parameter; the store's render
  always overwrites it before rendering
- Trim redundant tests and add a transform_options() helper in
  pipeline/validate.rs tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FBHEs42qNHDeKmsqTSDSQ
2026-08-25 18:54:06 -04:00
Bryan Helmkamp
a522414bdc
Add model stylesheet templates 2026-08-25 18:14:25 -04:00
Scott Werner
a0845d8346
Merge pull request #756 from fabro-sh/refactor/graph-reference-kind
Classify graph attributes with a graph-only reference kind
2026-08-18 12:57:32 -04:00
Scott Werner
8dfbfb9aa5 Classify graph attributes with a graph-only reference kind
reference_kind_for_attribute returned the full ReferenceKind, which
includes the config-sourced Dockerfile kind the classifier can never
yield, so the shared graph walker carried a silent `continue` and an
`unreachable!` for impossible kinds; each new config-sourced kind widens
those filler arms, and a classifier extension that reuses an existing
kind would be dropped by the walker without validation, visitation, or a
compiler error. Return a GraphReferenceKind subset instead (converting
into ReferenceKind for validation), making the walker's matches total
with every arm meaningful.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 17:58:46 -04:00
Scott Werner
9459ce1d04 Attribute template discovery errors to their source by construction
TemplateDiscoveryError only named a failing source through the Display
strings of its variants: parse and load failures forwarded transparently
to inner errors whose source naming varies (parent for some load
failures, the child path for dynamic dependencies, nothing for I/O
faults), so consumers that need the failing template's path had to
string-round-trip error messages. Carry the parent path on every
variant, exposing a total source_path() accessor, and render parse and
load failures with a parent-naming message above the preserved source
chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 17:53:13 -04:00
Scott Werner
679bc6701b Parse template dependencies whose paths collide with discovery roots
Dependency discovery pre-seeded roots into the path-keyed result map
and reused that map as the traversal-dedup set, so a loaded include
target whose path matched a root was recorded but never parsed (an
include chain that reaches the file anchoring a root silently skips its
content), and a second root occurrence at an already-seeded path was
dropped without parsing. Dedup traversal on the full
(path, root, content) occurrence instead, so every distinct authored
occurrence is parsed exactly once and identical duplicates parse once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 17:51:46 -04:00
Scott Werner
13755d7c2b Unify the workflow graph reference walkers
Move the static-reference vocabulary out of fabro-workflow so every
consumer shares one definition: ReferenceKind, AttributeScope, and
reference_kind_for_attribute land in fabro-types::graph, and
validate_static_reference plus a new visit_graph_references walker land
in fabro-template. The manifest bundler drops its ad-hoc graph scan and
walks references through the shared walker.

Unifying the walkers forces three semantic alignments, each matching
what the engine actually executes rather than what the old scanners
happened to match:

- stack.child_dotfile is no longer classified as a child-workflow
  reference; the engine never resolved it as one.
- import and stack.child_workflow only count at node scope; graph- and
  edge-level occurrences were scanned but never executed.
- @@-escaped goals flow through the shared walker's escape handling
  instead of the bundler's own prefix stripping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:34:23 -04:00
Release Repro
b04684aec4
fix(workflow): harden script value interpolation 2026-07-28 17:21:35 -04:00
Bryan Helmkamp
6bcd730284
feat(workflow): interpolate goal, inputs, and vars in command scripts
Command node `script` attributes were literal text: a `{{ inputs.x }}`
reached bash verbatim, and the only signal was a `detemplated_attribute`
warning. Scripts now substitute `{{ goal }}`, `{{ inputs.NAME }}`, and
`{{ vars.NAME }}` at run creation, alongside goals and prompts.

Scripts use `InterpString` token substitution rather than the MiniJinja
pass that renders prompts. Shell source is full of brace syntax that must
survive untouched — jq filters, awk programs, Go templates, brace
expansion — and `InterpString` claims only the narrow token forms,
leaving everything else literal.

`env` and `secrets` are deliberately not wired and now fail loudly
instead of passing through as text. A script reads the environment with
`$NAME`, which needs no interpolation, and a resolved secret would be
baked into the `CommandStarted` event that records the script verbatim.
The error points at `[environments.<slug>.env]` for the secret case.

`ResolveCtx` gains opt-in `with_inputs` and `with_goal`. Namespace
availability stays scope-determined per call site, so every existing
config-layer context leaves both unwired and keeps its current behavior.
`goal` names a single value rather than a namespace of them, so it has
no dotted form: only the exact body `goal` produces a token and
`{{ goal.title }}` stays literal.

Values substitute verbatim without shell quoting, matching
`[[run.prepare.steps]].script` where the snippet is the author's to
quote. Substituted text is never rescanned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 19:56:50 -04:00
Scott Werner
47bc772f7b refactor: organize crates into three layers 2026-07-23 17:59:34 -04:00