Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog

Accepted

ontoref
Projects accumulate many just recipes (and loose scripts). ontoref already

Context

Projects accumulate many just recipes (and loose scripts). ontoref already INVENTORIES recipes — describe.nu::scan-just-recipes runs `just --list` and categorizes each recipe by a name-prefix heuristic (categorize-recipe), and `ontoref describe tools` lists them. But the inventory is shallow: it knows a recipe's name, its trailing `#` comment, and a guessed bucket. It cannot answer "which is the canonical way to run all checks", "show me the deploy tasks", or "what does build-standalone do and why" — because the recipe carries no declared INTENT and no real categorization, only a prefix guess.

Two adjacent layers already exist and must not be duplicated:

- Recipes (justfiles) are the MECHANISM layer — the concrete command. Single source of truth for HOW. - Modes (reflection/modes/*.ncl) are the PROCEDURE layer — multi-step gated DAGs with actors/steps/depends_on, executed by `ontoref run`.

What is missing is not a third store ("devtask" is a target-side abstraction, not ontoref vocabulary). What is missing is for a recipe to SELF-DESCRIBE — to declare its intent and categorization in place — exactly as Rust handlers do with #[onto_api(method, path, description, tags)]: the annotation sits next to the code, a proc-macro parses it, and inventory aggregates a zero-cost catalog that `ontoref describe api` / GET /api/catalog / MCP all read. The same shape is wanted for recipes: an annotation next to the recipe, parsed by ontoref, that makes justfiles queryable.

The qa routing fix in the same session (ontoref qa show/list/search/add) added the seeding primitive — a tag-categorized, nickel-validated write path. The question this ADR settles is the GRAMMAR and the BOUNDARIES of seeding/enriching the tool surface: how an annotation is written, how it is mutated, and how a project that already has its own justfiles adopts compliance without losing its recipes.

The load-bearing tension is the named Spiral `formalization-vs-adoption`: richer formalization (typed annotations on every recipe) buys ecosystem visibility but raises adoption cost. The tension's own resolution in core.ncl is "schemas are optional layers, not mandatory gates" — which constrains this design directly.

Decision

Make justfiles self-describing through a typed comment annotation, expose a transversal `+`/`-` mutation verb to write/remove annotations and other store entries, and ship a NON-DESTRUCTIVE compliance/migration mechanism so projects adopt the surface while keeping their own recipes. Migration 0030 propagates the grammar, the recommended mode taxonomy, and the new surface to consumers.

DESIGN PROPERTIES (each is a constraint below):

1. Annotation grammar — `# @onto key=val ...`. A single comment line directly above the recipe carries typed metadata:

# Run all CI checks locally # @onto mode=ci labels=ci,gate intent="all checks before push" ci-full: ...

Keys: `mode` (one token — the domain category: tools|dev|build|dev-build| dply-build|test|docs|dist|secrets|…), `labels` (csv — free tags), `intent` (quoted free text — the "best way to X" sentence the inventory cannot derive). The recipe name and its plain `#` doc comment remain unchanged; `@onto` enriches, it does not replace.

2. The recipe is the single source of truth. Annotations live IN the justfile, above the recipe — never in a parallel devtasks store. ontoref parses them (scan-just-recipes gains an @onto parser) and DERIVES a catalog, exactly as #[onto_api] derives artifacts/api-catalog-*.ncl. `ontoref tool export` writes the derived catalog; `ontoref describe tools --mode <m> --label <l>` reads it.

3. Mutation via a transversal verb. `ontoref + <noun> <args>` upserts and `ontoref - <noun> <args>` removes, across stores:

ontoref + tool ci-full --mode ci --labels ci,gate --intent "all checks" ontoref - tool ci-full # removes the @onto line only ontoref + qa "<q>" "<a>" --tags devtool,ci # the qa add path ontoref - qa <id>

`+ tool` / `- tool` edit the `@onto` comment ABOVE the named recipe in place, validated against `just --list`; they NEVER touch the recipe body. `+`/`-` are first-level dispatcher tokens taking `<noun> <args>`, so new stores plug in without new top-level verbs.

4. Annotations are OPTIONAL enrichment, never a gate. An un-annotated recipe stays valid, runnable, and still inventoried by name+category (today's behavior). Annotation raises a recipe from guessed to declared; it is never required to run a recipe. This is the `formalization-vs-adoption` balance made literal — "optional layers, not mandatory gates".

5. Non-destructive compliance/migration. A `compliant` mode (and `ontoref tool migrate`) scans a project's existing justfiles, proposes `@onto` annotations (seeding `mode` from categorize-recipe + name, leaving `intent` for the author), and — opt-in — reorganizes recipes into justfiles/<mode>.just. It MUST preserve every project-owned recipe and its semantics: no recipe is deleted or renamed, reorg is opt-in and reversible, and the project's own recipes are first-class (the mechanism annotates them, it does not supplant them with ontoref-blessed ones).

6. Drift-checkable round-trip. `ontoref tool audit` reports orphan annotations (an `@onto` whose recipe is gone from `just --list`) and, at compliance level, un-annotated recipes. Mirrors docs-drift: the annotation and the recipe must stay in sync because they are co-located.

A multi-step task that deserves gating/steps graduates to a MODE, not an annotation; `@onto` is for single-recipe intent+categorization. Modes and annotated recipes are complementary, not competing.

Constraints

  • Hard A recipe in a justfile is the single source of truth for its command. @onto metadata MUST live in a comment co-located with the recipe; there MUST NOT be a parallel store of recipe command strings. Any tool catalog is DERIVED from the annotations and MUST be regenerated, not hand-edited.
  • Hard The annotation MUST be a single comment line `# @onto key=val ...` immediately above the recipe. Recognized keys: `mode` (one token), `labels` (comma-separated), `intent` (double-quoted free text). Unknown keys MUST be ignored, not error. The recipe name and its plain `#` doc comment MUST be left intact.
  • Hard Annotation/entry mutation MUST go through `ontoref + <noun> <args>` (upsert) and `ontoref - <noun> <args>` (remove). For noun `tool`, the verb edits/removes ONLY the @onto comment above the named recipe, validated against `just --list`, and MUST NOT modify the recipe body. New stores MUST be added as nouns, not as new top-level verbs.
  • Hard An un-annotated recipe MUST remain valid, runnable, and inventoried by name+category. Annotation MUST NOT be a precondition for running or listing a recipe. Compliance is reported by audit, never enforced as a gate.
  • Hard The compliance/migration mechanism MUST NOT delete or rename any project-owned recipe. It annotates in place and may propose reorganization into justfiles/<mode>.just only as an opt-in, semantics-preserving, reversible step. A project's own recipes are first-class and MUST survive migration unchanged in behavior.
  • Soft `ontoref tool audit` MUST report orphan annotations (an @onto whose recipe is absent from `just --list`) and, at compliance level, un-annotated recipes. Every @onto MUST reference an existing recipe.
  • Soft The recommended `mode` taxonomy (tools/dev/build/test/ci/deploy/docs/dist/secrets/…) is advisory guidance, not an enforced enum; `labels` are free-form. Validation MUST NOT reject an unrecognized mode or label.

Alternatives considered

  • A standalone devtasks.ncl store holding command strings + labelsrejected: Third source of truth alongside justfiles and modes; the stored command drifts from the real recipe, and it re-imports the loose-script problem. The recipe must stay the single source; the annotation co-locates metadata with it.
  • Promote every dev/deploy recipe into a full NCL moderejected: Modes are the procedure layer for multi-step gated DAGs. Forcing single-command recipes through mode authoring is heavy and collapses the mechanism/procedure distinction. Multi-step tasks DO graduate to modes; single-recipe intent belongs in an annotation.
  • Make annotations mandatory (a compliance gate that fails un-annotated recipes)rejected: Collapses the named `formalization-vs-adoption` Spiral toward the Yang pole, contradicting its own resolution ('optional layers, not mandatory gates'). Annotations must enrich an always-valid baseline; audit reports compliance, it does not block.
  • Glued verb `+tool` / per-store `tool add` instead of a transversal `+ <noun>`rejected: Per-store verbs do not project to new stores without new top-level commands, and `+tool` glued is a one-off. A transversal `+ <noun> <args>` is the uniform, extensible idiom the user asked for.
  • Destructive reorg — ontoref owns justfiles/ and rewrites them to a canonical layoutrejected: Adoption-hostile: projects have their own recipes and conventions. Compliance must be non-destructive — annotate in place, propose reorg opt-in, never delete or rename a project's recipes.

Anti-patterns

  • A Standalone Devtasks Store Duplicating Recipe Commands — A parallel devtasks.ncl stores copied command strings + labels alongside the justfiles. It becomes a third source of truth that drifts from the real recipe and re-imports the loose-script problem.
  • Making Annotations Mandatory / Gating on Them — Compliance fails un-annotated recipes or blocks running them. This collapses the formalization-vs-adoption Spiral toward Yang, contradicting its own 'optional layers, not mandatory gates' resolution.
  • +/- Editing the Recipe Body or Destroying Project Recipes — `+ tool` rewrites the recipe command, or migration deletes/renames a project-owned recipe. Adoption-hostile; violates recipe-as-single-source and non-destructive-migration.
  • Hand-Editing the Derived Tool Catalog — Someone edits the generated tool catalog artifact directly. It is derived from the annotations and overwritten on regeneration, so the edit drifts from source.
  • Forcing Single-Command Recipes Through Mode Authoring — Every dev/deploy recipe is promoted to a full NCL mode. Modes are for multi-step gated DAGs; over-applying them collapses the mechanism/procedure distinction and burdens adoption.
Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.