From 109040ffe435359359ac36b92a9b215f89abdebf Mon Sep 17 00:00:00 2001 From: Rafael Richards Date: Mon, 11 May 2026 00:34:20 -0400 Subject: [PATCH] phase3-A: recipe schema + docs/recipes/ scaffold + task_index recipes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 Track A — recipe schema and directory scaffold. Foundation for Phase 3 Tracks B/C/D (B authors recipes; C TDDs the handshake test; D wires Make + CI). Per phase3-plan.md §2. Four parts in one coherent PR: 1. profile/recipe.schema.json (new) — JSON Schema 2020-12 for the YAML frontmatter at the top of every docs/recipes/.md. Required: id (typedID, recipe:* prefix), title (≤70 char), intent (matches a task_index entry), touches (≥1 typedID), verified_on (ISO date), ci_verifiable (bool). Optional: prereqs, manual_checklist_url, tier (tier-1|2|3), notes. allOf rule: ci_verifiable=false REQUIRES manual_checklist_url. typedID grammar duplicated in $defs so the schema validates standalone — same regex as tools.schema.json + task_index.schema.json. 2. docs/recipes/README.md (new) — describes the recipe contract, frontmatter table, the seven priority recipes (gating/buffer/ post-exit), the CI gate that Track D will implement, authoring guide, and what the directory is NOT. ~95 lines of prose. 3. profile/task_index.json — new top-level "recipes" category with one row per priority recipe mapping intent → recipe: typed ID + see_also pointers to the tools / cmds / modules each recipe touches. 7 rows. Total intent count 59 → 66. 4. (audit-only) A1 findings captured in this PR body — no existing recipe content anywhere in the org; only references in planning prose, and one adjacent surface (tools.json workflow block: tdd_inner_loop + minimal_application_recipe) that recipes do not replace. Verification, all green: - recipe.schema.json valid JSON Schema 2020-12; synthetic positive + two negative fixtures (missing manual_checklist_url when ci_verifiable=false; malformed typedID) both rejected as expected. - task_index.json validates against task_index.schema.json (now 6 categories, 66 intents). - profile/tools.json + task_index.json schema-strict via validate-catalog.py: OK. - make catalog byte-idempotent. - pytest profile/build/: 26/26. - make check-docs-prose green (docs/recipes/README.md is .md). - make phase0-smoke: PASS. Phase 3 launch state: - Track A this PR — foundation for everything else. - Tracks B/C/D ready to start in parallel once this lands. - recipes 1–4 are gating for Phase-3 exit (Track B); #5 is buffer; #6 (tier-2) + #7 (tier-3) are post-exit follow-ups. --- docs/recipes/README.md | 123 +++++++++++++++++++++++++++++++++++++ profile/recipe.schema.json | 91 +++++++++++++++++++++++++++ profile/task_index.json | 38 ++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 docs/recipes/README.md create mode 100644 profile/recipe.schema.json diff --git a/docs/recipes/README.md b/docs/recipes/README.md new file mode 100644 index 0000000..c72e85e --- /dev/null +++ b/docs/recipes/README.md @@ -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 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/.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:` 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). diff --git a/profile/recipe.schema.json b/profile/recipe.schema.json new file mode 100644 index 0000000..447424b --- /dev/null +++ b/profile/recipe.schema.json @@ -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/.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/.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" } + } + } +} diff --git a/profile/task_index.json b/profile/task_index.json index cb04f67..edf3860 100644 --- a/profile/task_index.json +++ b/profile/task_index.json @@ -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"] + } } } }