Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions docs/recipes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# m-dev-tools recipes

CI-verifiable, end-to-end developer workflows for the m-dev-tools stack.
Each recipe is one Markdown file under this directory with YAML
frontmatter (schema: [`profile/recipe.schema.json`](../../profile/recipe.schema.json))
and a body that runs top-to-bottom on a fresh clone.

A recipe is "done" only when running its commands in order on a clean
machine produces the documented result. The Phase-3 discovery handshake
test (`profile/build/test-discovery-protocol.py`, landing in Phase-3
Track C) executes each `ci_verifiable: true` recipe and gates on its
success.

## What a recipe looks like

```markdown
---
id: recipe:new-app-tdd-ci
title: Scaffold a new M project with TDD + CI in 60 seconds
intent: Start a new M project with TDD and CI scaffolding
touches:
- cmd:m-cli#new
- cmd:m-cli#test
- cmd:m-cli#fmt
- cmd:m-cli#lint
- cmd:m-cli#coverage
- cmd:m-cli#ci
- tool:m-test-engine
- module:m-stdlib#STDASSERT
prereqs: []
verified_on: 2026-05-11
ci_verifiable: true
tier: tier-1
---

# Scaffold a new M project with TDD + CI

…recipe body: step-by-step shell commands, expected outputs, link to the
exit assertion…
```

Frontmatter contract:

| Field | Required? | Notes |
|---|---|---|
| `id` | yes | Typed `recipe:<slug>`. Slug matches filename (`new-app-tdd-ci` → `new-app-tdd-ci.md`). |
| `title` | yes | ≤ 70 chars. |
| `intent` | yes | Matches at least one entry in [`profile/task_index.json`](../../profile/task_index.json). |
| `touches` | yes (≥1) | Typed IDs the recipe references. The handshake test resolves each through `tools.json`. |
| `prereqs` | no | Other recipe / tool typed-IDs that must succeed first. |
| `verified_on` | yes | ISO date the recipe was last run end-to-end. Phase-5 freshness gate rejects > 90 days old. |
| `ci_verifiable` | yes | `true` if commands run unattended in CI. `false` requires `manual_checklist_url`. |
| `manual_checklist_url` | conditional | Required iff `ci_verifiable: false`. |
| `tier` | no | `tier-1` / `tier-2` / `tier-3`. Determines which Phase ships the recipe (recipes 1–5 are tier-1; #6 is tier-2; #7 is tier-3). |
| `notes` | no | Free-text, not consumed mechanically. |

## The seven priority recipes

Per [`AI-discoverability-plan.md` §5.1](../AI-discoverability-plan.md) and
the [Phase-3 implementation plan](../phase3-plan.md):

| # | Slug | Tier | Phase-3 status | What it proves |
|---|---|---|---|---|
| 1 | `new-app-tdd-ci` | tier-1 | gating (Track B) | Whole-stack scaffold → red test → green → CI passes |
| 2 | `use-stdlib-module` | tier-1 | gating (Track B) | Agent looks up `parse^STDJSON` and calls it correctly |
| 3 | `add-stdlib-module` | tier-1 | gating (Track B) | Author a new STD* module with TDD; manifest regenerates |
| 4 | `add-lint-rule` | tier-1 | gating (Track B) | Ship a new `M-MOD-*` rule with fixer linkage; cliff falls right |
| 5 | `add-m-cli-command` | tier-1 | buffer (Track B if budget allows) | New subcommand registered; `m capabilities --json` picks it up |
| 6 | `investigate-failure` | tier-2 | **post-exit** | Standard triage path when `m test` is red |
| 7 | `add-editor-support` | tier-3 | **post-exit** | Extend the VS Code extensions with a new file association / setting |

Phase-3 exit per [`phase3-plan.md`](../phase3-plan.md): at least **4 of
the 7** are written + executable + CI-verified. Recipes 1–4 are the
exit set; #5 is buffer; #6 + #7 land after Phase 3 closes.

## CI gate (Phase-3 Track D)

`make recipes-check` runs from `.github`'s working tree on every
push/PR. For each `*.md` under this directory:

1. Validate frontmatter against `recipe.schema.json`.
2. Assert every typed ID in `touches` and `prereqs` resolves through
`tools.json` + `task_index.json`.
3. Assert `intent` matches at least one entry in `task_index.json`.
4. If `ci_verifiable: true`, exec the body's command block in a fresh
environment and assert exit 0 + expected output.
5. Link-check every URL in the body (HEAD; HTTP 200 required).

The handshake test (`profile/build/test-discovery-protocol.py`) drives
the whole chain end-to-end: pick an intent → resolve to typed ID →
follow to recipe → run recipe → assert exit code.

## Authoring a new recipe

1. Pick the slug — must match an existing `intent` in
`task_index.json` (or add one via the same PR).
2. Write `docs/recipes/<slug>.md` with the frontmatter above.
3. Run `make recipes-check` locally — fixture-level validation only;
the CI step does the full exec.
4. Bump `verified_on` to today on every edit. If the body changes,
re-run the command sequence top-to-bottom and confirm before
bumping.
5. Open a PR. The recipe joins the catalog on merge.

## Naming + slug conventions

- Slug pattern: `^[a-z0-9_-]+$` (matches the typedID regex's slug
group).
- Slug → filename → typed `recipe:<slug>` ID are 1:1.
- One H1 in the body: same text as `title` is conventional but not
enforced.
- Section headers under `## Setup` / `## Steps` / `## Verify` /
`## Cleanup` are the recommended shape; not yet schema-enforced.

## What this directory is NOT

- A general documentation tree. That's [`../`](../) — for plans,
guides, and history.
- A test corpus. That's `m-modern-corpus` (and, by reach, the VistA
corpus surfaced via `tree-sitter-m`).
- A tutorial. Recipes are mechanically-runnable; tutorials are prose
that explains why. The user-facing guide lives in
[`../AI-discoverability-guide.md`](../AI-discoverability-guide.md).
91 changes: 91 additions & 0 deletions profile/recipe.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/m-dev-tools/.github/main/profile/recipe.schema.json",
"title": "m-dev-tools recipe frontmatter",
"description": "Validation contract for the YAML frontmatter block at the top of every docs/recipes/<slug>.md file. A recipe is a CI-verifiable end-to-end developer workflow — agents (and the Phase-3 handshake test) execute its command sequence top-to-bottom and verify the documented result. Recipes reference typed IDs from profile/tools.json and profile/task_index.json; the typedID grammar is duplicated here so this schema validates standalone.",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"title",
"intent",
"touches",
"verified_on",
"ci_verifiable"
],
"properties": {
"id": {
"$ref": "#/$defs/typedID",
"description": "Self-id, must start with `recipe:`. The typedID slug after `recipe:` matches the filename slug (docs/recipes/<slug>.md)."
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 70,
"description": "Human-readable one-line title. ≤ 70 chars (table-friendly)."
},
"intent": {
"type": "string",
"minLength": 1,
"description": "Plain-English statement of what the recipe achieves. Must match the `intent` of at least one entry in profile/task_index.json (the handshake test cross-checks this)."
},
"touches": {
"$ref": "#/$defs/typedIDList",
"description": "Typed IDs the recipe references in its command sequence (tools, subcommands, modules, data tables). The handshake test resolves each entry through tools.json and asserts the pointer is reachable.",
"minItems": 1
},
"prereqs": {
"$ref": "#/$defs/typedIDList",
"description": "Other recipes or tools that must succeed before this one runs. Typically empty for foundation recipes; populated for layered recipes (e.g. add-stdlib-module prereqs new-app-tdd-ci).",
"default": []
},
"verified_on": {
"type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
"description": "ISO date the recipe was last run end-to-end and produced the documented result. Bump on every meaningful edit. Phase-5 freshness gate (when added) will reject recipes older than 90 days."
},
"ci_verifiable": {
"type": "boolean",
"description": "True if the recipe's command sequence can run unattended in CI (Linux, no GUI, no human-in-the-loop). False requires `manual_checklist_url` to point at a published manual verification checklist."
},
"manual_checklist_url": {
"type": "string",
"format": "uri",
"description": "Required iff ci_verifiable is false. URL to a published manual checklist (typically a github.com/.../blob/main URL to a Markdown file)."
},
"tier": {
"type": "string",
"enum": ["tier-1", "tier-2", "tier-3"],
"description": "Which tier of repos the recipe exercises. Determines when the recipe can ship (tier-1 unblocked by Phase 0; tier-2 by Phase 2; tier-3 by Phase 4 MCP)."
},
"notes": {
"type": "string",
"description": "Free-text notes for human readers. Not consumed by the catalog generator or handshake test."
}
},
"allOf": [
{
"description": "If ci_verifiable is false, manual_checklist_url is required.",
"if": {
"properties": {
"ci_verifiable": { "const": false }
},
"required": ["ci_verifiable"]
},
"then": {
"required": ["manual_checklist_url"]
}
}
],
"$defs": {
"typedID": {
"description": "Typed catalog identifier — mirrors tools.schema.json/$defs/typedID and task_index.schema.json/$defs/typedID. Kinds: tool, cmd, module, rule, doc, data, workflow, task, recipe.",
"type": "string",
"pattern": "^(tool|cmd|module|rule|doc|data|workflow|task|recipe):[a-z0-9_-]+(#[A-Za-z0-9._-]+)?$"
},
"typedIDList": {
"type": "array",
"items": { "$ref": "#/$defs/typedID" }
}
}
}
38 changes: 38 additions & 0 deletions profile/task_index.json
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,44 @@
"primary": "doc:m-dev-tools#m-tooling-tier1",
"doc": "https://github.com/m-dev-tools/.github/blob/main/docs/history/m-tooling-tier1.md"
}
},

"recipes": {
"scaffold_new_app_tdd_ci": {
"intent": "Scaffold a new M project with TDD + CI in 60 seconds",
"primary": "recipe:new-app-tdd-ci",
"see_also": ["cmd:m-cli#new", "cmd:m-cli#test", "cmd:m-cli#fmt", "cmd:m-cli#lint", "cmd:m-cli#coverage", "cmd:m-cli#ci", "tool:m-test-engine", "module:m-stdlib#STDASSERT"]
},
"use_stdlib_module": {
"intent": "Look up an m-stdlib symbol (signature + example) and call it correctly",
"primary": "recipe:use-stdlib-module",
"see_also": ["cmd:m-cli#doc", "cmd:m-cli#search", "cmd:m-cli#examples", "module:m-stdlib#STDJSON"]
},
"add_stdlib_module": {
"intent": "Author a new STD* module in m-stdlib with TDD; regenerate the manifest",
"primary": "recipe:add-stdlib-module",
"see_also": ["tool:m-stdlib", "module:m-stdlib#STDASSERT", "cmd:m-cli#test"]
},
"add_lint_rule": {
"intent": "Ship a new M-MOD-* lint rule with fixer linkage and corpus validation",
"primary": "recipe:add-lint-rule",
"see_also": ["tool:m-cli", "cmd:m-cli#lint", "tool:m-modern-corpus", "tool:tree-sitter-m"]
},
"add_m_cli_command": {
"intent": "Register a new m-cli subcommand discoverable via m capabilities --json",
"primary": "recipe:add-m-cli-command",
"see_also": ["tool:m-cli", "cmd:m-cli#capabilities"]
},
"investigate_failure": {
"intent": "Standard triage path when `m test` is red",
"primary": "recipe:investigate-failure",
"see_also": ["cmd:m-cli#test", "cmd:m-cli#doctor", "tool:tree-sitter-m", "tool:m-test-engine"]
},
"add_editor_support": {
"intent": "Extend the VS Code extensions with a new file association or setting",
"primary": "recipe:add-editor-support",
"see_also": ["tool:tree-sitter-m-vscode", "tool:m-stdlib-vscode", "cmd:m-cli#lsp"]
}
}
}
}
Loading