ADR-0014 — pack infer: heuristic .feature → pack.yaml inference#
Status#
Accepted — 2026-05-14
Context#
Authoring a pack.yaml is model-first: you write requirements →
use_cases → commands → events, and only then the Gherkin
scenarios. The executable artifact — the .feature — comes last.
That ordering has a waterfall smell, and it is also the hardest way to
start: a blank pack.yaml is a blank page. The friction analysis in
mejoras/visual-pack-authoring-todo.md flagged "invert the flow" as the
highest-leverage ergonomic move — higher than any visual editor, because
it removes the blank-page problem rather than decorating it.
Decision#
Ship pack infer --from <feature-file>. It reads a Gherkin .feature
and prints a proposed pack.yaml fragment (requirements, use_cases,
commands, events, scenarios) to stdout.
The inference is heuristic and deterministic:
Source in the .feature | Inferred |
|---|---|
@REQ-NNN tag | a requirement reference (else a REQ-XXX placeholder) |
Feature: name | the use_case name |
When step | a PascalCased command |
quoted PascalCase token in a Then step | an event |
each Scenario: | a scenarios[] entry |
Everything the heuristic cannot infer is emitted as an explicit TODO:
string. --format json emits the same model as a structured object.
Rationale#
- Heuristic, not LLM, for v1. Deterministic output is testable, reviewable in a git diff, works offline, has no vendor dependency, and is instant. An LLM would produce richer guesses but at the cost of every one of those properties. The heuristic is intentionally crude — its job is to kill the blank page, not to be correct.
TODO:over silent guesses. A field the heuristic cannot fill is marked, not invented. The author always knows exactly what still needs judgement. The fragment is a skeleton, never a final answer.- stdout, not in-place edit.
pack inferprints; it does not mutatepack.yaml. The author decides what to merge. This keeps the command pure, composable (pack infer ... >> pack.yaml, or pipe to a diff), and impossible to use destructively by accident. - Round-trips through the project's own YAML reader. The emitted
fragment is parseable by
parseYamlLite— verified in tests — so it drops straight into a realpack.yaml.
Alternatives considered#
- LLM-assisted inference as the default. Rejected for v1 — kills
determinism, testability, offline use and vendor neutrality. Kept as a
possible future
--llmflag using the same shell-out pattern asharness run. - Edit
pack.yamlin place / merge automatically. Rejected — merging into a structured file is a judgement call (whichUCdoes this belong to? does theREQalready exist?). Printing to stdout keeps the author in control and the command non-destructive. - One use case per scenario. Rejected for v1 — over-produces. One use case per feature, one command per scenario, is a saner default skeleton; the author splits further if needed.
- Infer aggregates / bounded contexts too. Rejected — nothing in a
.featurereliably signals an aggregate boundary. Guessing it would produce confident noise. Left as an explicitTODO:.
Consequences#
Positive#
- Inverts the authoring flow: the executable scenario leads, the model follows. No blank-page start.
- Deterministic and fast — usable in a pre-commit hook or a scaffold script, not just interactively.
- Pairs with
pack lint --graphand the VS Code extension: infer a skeleton, then see and link-check the graph it produced.
Negative / trade-offs#
- The heuristic is crude. Command names from
Whensteps are often awkward (MoreVehiclesEnterTheCommand); the author must rename them. This is acceptable — a named thing to rename beats a blank line. - It only reads one
.featureat a time. Multi-file inference (a wholefeatures/tree → one pack) is a follow-up. - No aggregate/bounded-context inference — those stay
TODO:.
Follow-ups#
--llmmode: shell out to a configurable agent for richer inference, vendor-neutral, same pattern asharness run.- Multi-file inference:
pack infer --from-dir features/. - An
init_packintegration that offers to seed from an existing.featuredirectory.
References#
scripts/infer_pack.tsdocs/specs/domain-pack-format.md§5cmejoras/visual-pack-authoring-todo.md— Phase 3. (Document removed 2026-08-16 once its phases were done; it is in git history.)- ADR-0013 —
harness run(the shell-out pattern a future--llmmode would reuse)