The previous commit removed `hooks.rs` but left `mod hooks;` in the
workflow test module, so the test crate did not compile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The five twin-mode tests in `tests/it/workflow/hooks.rs` have not tested
hooks since runs moved into the server. They wrote their `[[run.hooks]]`
entries into the user's settings file, which `fabro run` no longer
transmits, so no hook ran; and even with the hooks moved into a workflow
config, the run executes in the isolated test server, which never learns
the twin's base URL and calls the real OpenAI API with the twin namespace
as its key. Prompt hooks fail open on an LLM error, so the tests that
expect the run to proceed passed for the wrong reason, and the one that
expects a block never could.
Removing them rather than leaving ignored tests that pass or fail for
unrelated reasons. Bringing hook coverage back needs the test harness to
hand the isolated server the twin's base URL through its runtime
environment; the pull request records what that involves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A node with no `shape` defaulted to `box`, which resolves to the agent
handler. That made a shapeless `script` node run as an LLM call prompted
with its own label, while the `script` was reported as inert — wrong
behavior behind a warning.
`script` is read by the command handler and by nothing else, so a
shapeless node that sets it is unambiguously a command node. `shape()`
now infers `parallelogram` in that case. An explicit `shape` still wins.
Two rules keep the inference honest:
- `script_prompt_conflict` — setting both `script` and `prompt` is an
error. No handler reads both. It fires regardless of shape so that
adding one cannot downgrade the error to a warning.
- `command_requires_script` — a command node without a script is an
error. Without this the original trap just moves: a node meant as a
command that omits its script silently becomes an agent again.
Also drops the `tool_command` alias in favor of `script` alone, routing
the six read sites through a new `Node::script()` accessor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diagnostics have carried a `fix` field all along, but the CLI renderer
never printed it — the suggestion was only reachable through --json. The
actionable half of every validation failure was invisible to the person
running the command.
print_diagnostics now emits the fix as a dim-labelled continuation line
under any diagnostic that has one, at both error and warning severity.
Gating it behind --verbose would defeat the point, and printing it only
for errors would read as "this warning has no fix" — the warning
suggestions are useful on their own. Diagnostics that set no fix simply
omit the line.
The severity match moved into print_diagnostic so the fix line is
appended once in the loop rather than copied into all five arms; the
rest of the diff is reindentation.
print_diagnostics is shared by validate, preflight, graph, exec, and
dry-run, so this covers all five. Eleven inline snapshots across four
files gain a fix line; every change is additive.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>