From e5a70e5bd1570ee9081704961299085d8b35e0df Mon Sep 17 00:00:00 2001 From: Debasish Bose Date: Sat, 12 Sep 2026 19:03:18 +1000 Subject: [PATCH] Move spec docs into specs/ Collect the six root-level *spec*.md files under specs/ to declutter the repo root: the build specs (modelith-spec.md, modelith-spec-v2.md), the ontology/KG spec, and the VS Code/AI specs (vscode-ai-spec, -extended, drift-spec). Tracked files moved with git mv (history preserved); the two untracked ones (drift, ai-spec-extended) are added here. Update the references that pointed at the old root paths: README's spec link and the "Spec addendum to modelith-spec.md" lines in docs/collaboration-model.md and modelith-collaboration-model.md now point at specs/modelith-spec.md. --- README.md | 2 +- docs/collaboration-model.md | 2 +- modelith-collaboration-model.md | 2 +- specs/modelith-drift-spec.md | 153 ++++++++++++++++ .../modelith-ontology-kg-spec.md | 0 .../modelith-spec-v2.md | 0 modelith-spec.md => specs/modelith-spec.md | 0 specs/modelith-vscode-ai-spec-extended.md | 170 ++++++++++++++++++ .../modelith-vscode-ai-spec.md | 0 9 files changed, 326 insertions(+), 3 deletions(-) create mode 100644 specs/modelith-drift-spec.md rename modelith-ontology-kg-spec.md => specs/modelith-ontology-kg-spec.md (100%) rename modelith-spec-v2.md => specs/modelith-spec-v2.md (100%) rename modelith-spec.md => specs/modelith-spec.md (100%) create mode 100644 specs/modelith-vscode-ai-spec-extended.md rename modelith-vscode-ai-spec.md => specs/modelith-vscode-ai-spec.md (100%) diff --git a/README.md b/README.md index 4916cc6..b84a825 100644 --- a/README.md +++ b/README.md @@ -1119,7 +1119,7 @@ cd vscode && npm install && npm run build && npm run package # produces the .v team from an empty repo to a working practice (platform, then SMEs, architects, engineers). - [`docs/collaboration-model.md`](docs/collaboration-model.md): the operating model, review routes, and the merge driver. -- [`modelith-spec.md`](modelith-spec.md): the full build specification. +- [`modelith-spec.md`](specs/modelith-spec.md): the full build specification. ## License diff --git a/docs/collaboration-model.md b/docs/collaboration-model.md index 944e029..a58fbe2 100644 --- a/docs/collaboration-model.md +++ b/docs/collaboration-model.md @@ -1,6 +1,6 @@ # Modelith: Collaboration Model -**Spec addendum to `modelith-spec.md`.** +**Spec addendum to [`specs/modelith-spec.md`](../specs/modelith-spec.md).** > **Implementation status.** Mechanics shipped: `mdl classify` (routes A–E, §4), > semantic merge driver + `mdl init --git-hooks` (§6.1, verified against real diff --git a/modelith-collaboration-model.md b/modelith-collaboration-model.md index 60efdd6..ece2ed0 100644 --- a/modelith-collaboration-model.md +++ b/modelith-collaboration-model.md @@ -1,6 +1,6 @@ # Modelith: Collaboration Model -**Spec addendum to `modelith-spec.md`. Target path in repo: `docs/collaboration-model.md`.** +**Spec addendum to [`specs/modelith-spec.md`](specs/modelith-spec.md). Target path in repo: `docs/collaboration-model.md`.** Three personas, one git repo, one truth. This document defines repo topology, ownership, change routes, merge mechanics and the escape hatches that stop the gate becoming a blocker. diff --git a/specs/modelith-drift-spec.md b/specs/modelith-drift-spec.md new file mode 100644 index 0000000..e9d358b --- /dev/null +++ b/specs/modelith-drift-spec.md @@ -0,0 +1,153 @@ +# Modelith — Drift, surfaced in VS Code and chat + +## Context + +Drift is Modelith's sharpest advantage: because the **model and the dbt implementation live +close** (one git repo, one `mdl` engine), a divergence between the committed model and the +compiled warehouse is *detectable, classified, and explainable* — not discovered in +production. The engine already exists and is strong: + +- `compute_drift()` → `DriftReport` of `DriftItem{severity, kind, model, column, detail, payload}` + (`packages/reverse/src/mdl_reverse/drift.py`). +- Severity is deterministic — breaking / additive / cosmetic (`packages/core/src/mdl_core/severity.py`); + the tests pin direction, so **the LLM must never re-derive it**. +- `payload` on each item is a machine fix-recipe; `reconcile()` folds only additive+cosmetic, + breaking is human-gated (`packages/reverse/src/mdl_reverse/reconcile.py`). +- **`mdl drift --format json` already emits the whole report** (`render.py:44`) with per-item + severity/kind/model/column/detail/payload + `max_severity`/`has_breaking`. + +**What exists in VS Code today is thin:** `modelith.driftCheck` (`vscode/src/extension.ts:270`) +runs `drift --check`, dumps text to an output channel, and shows a modal on breaking. No +per-model surfacing, no navigation to the affected file, no fix path, no explanation. The +JSON is there; the UX isn't. + +## Research findings that shape the design + +- **Drift is a diagnostics story, not a chat toy.** VS Code's own guidance: chat participants + "should not be purely question-answering bots" and should hand off to commands/code actions + for anything that edits, asking consent before destructive/costly operations. Findings that + map to specific files with concrete fixes belong in the **Problems panel + code actions**, + which is the idiom linters use (`DiagnosticCollection`, `CodeActionProvider`). +- **A dedicated view is the established idiom** for structured findings about a dbt project + (dbt Power User uses a lineage panel + command-palette entries + click-to-navigate). Drift is + a list of findings across many models — a **TreeView** grouped by severity fits it. +- **Chat CAN be actionable**, not just prose: `ChatResponseStream` gives `stream.button()` + (invoke a command), `stream.markdown()` with `command:` links, `stream.reference()` / + `stream.anchor()` (link a file+range), `stream.filetree()`. So the `@modelith` drift answer + can explain *and* offer "Reconcile safe changes" / "Show in Problems" buttons. + +Sources: +- VS Code Chat Participant API — https://code.visualstudio.com/api/extension-guides/ai/chat +- Programmatic Language Features (diagnostics, code actions) — https://code.visualstudio.com/api/language-extensions/programmatic-language-features +- code-actions-sample (diagnostics from a tool → quick fixes) — https://github.com/microsoft/vscode-extension-samples/blob/main/code-actions-sample/src/diagnostics.ts +- dbt Power User (panel + palette idiom for dbt findings) — https://github.com/AltimateAI/vscode-dbt-power-user +- Recce / Datafold (drift as a review artifact reviewers see) — https://blog.reccehq.com/column-level-lineage-options-for-dbt + +## The design — three cooperating surfaces, one engine + +The deterministic `mdl drift --format json` is the single source of truth. Every surface +consumes that JSON; none re-derives severity. The LLM only *explains and sequences a fix*. + +### 1. Problems panel — drift as diagnostics (primary, non-AI) +A `DiagnosticCollection` (`modelith-drift`), separate from the LSP's validation diagnostics. +Each `DriftItem` becomes a `Diagnostic` on the model YAML file the item's `model` maps to +(resolve model name → `logical/entities/.yaml`; range = the file, or the attribute line +when `column` is set and locatable). Severity maps: breaking → Error, additive → Warning, +cosmetic → Information. This gives squiggles, the Problems list, and the status-bar count for +free — exactly how an engineer already reads validation findings. Findings that don't resolve +to a file (e.g. `model_removed`) attach to `mdl-project.yaml` with the model named in the message. + +### 2. Drift view — a TreeView grouped by severity (navigation) +A `modelithDrift` view (activity-bar or the existing Modelith container) listing findings +grouped **Breaking / Additive / Cosmetic**, each node showing `kind` + `model` + `column`, +click-to-open the file at the range. Inline node actions: "Reconcile" (additive/cosmetic only), +"Explain" (opens `@modelith /drift `). A root action reconciles all safe changes. This +is the dbt-Power-User-style panel adapted to drift. + +### 3. Code actions — the fix at the point of the problem +A `CodeActionProvider` on model YAML: for an additive/cosmetic drift diagnostic, offer a +`QuickFix` "Reconcile: fold this change into the model" that runs the reconcile for that one +item via `apply_command` using the item's `payload`. For a **breaking** diagnostic, offer +"Explain this breaking change (@modelith)" — never an auto-apply. This respects the engine's +own reconcile boundary at the exact spot the user is looking. + +### Commands (palette: `Ctrl/Cmd+Shift+P` → "Modelith: …") +- **`Modelith: Check Drift`** (`modelith.driftCheck`, upgraded) — run `drift --format json`, + populate the DiagnosticCollection + the tree, set the status bar, and reveal the view. Replaces + today's text-dump-to-output behaviour. Wire it to also run on demand and (optionally, setting) + when `target/manifest.json` changes. +- **`Modelith: Reconcile Safe Drift`** (`modelith.driftReconcile`) — apply additive+cosmetic + via the CLI `drift --reconcile`; breaking stays untouched. Confirm first (VS Code consent + guidance), then re-run the check to refresh. +- **`Modelith: Explain Drift`** (`modelith.driftExplain`) — hand off to `@modelith /drift`. +- A **status-bar item** reflecting drift state (✓ no drift / 🟡 N safe / 🔴 N breaking), + clicking it reveals the Drift view — mirrors the existing validation status item. + +### 4. `@modelith /drift` — the AI explainer (chat) +Ask-mode slash command. Grounds the user's Copilot model on the **drift JSON** (never +re-deriving severity — the prompt states severity is authoritative) and streams: +- a narrative grouped by severity ("3 additive, 1 breaking"), +- for each **breaking** item, a plain-English impact + the recommended remediation composed + from its `payload` (which the engine's own `reconcile` uses), +- actionable trailer via `ChatResponseStream`: `stream.button()` "Reconcile safe changes" + (→ `modelith.driftReconcile`), `stream.reference()` links to each affected model file, and a + note that breaking changes need a human decision. +`/drift ` scopes to one model. Fails closed: if no `request.model`, fall back to the +deterministic markdown from `render_markdown` (already in `render.py`). + +### 5. MCP tool `explain_drift()` (agent mode / other clients) +Returns the drift JSON plus a compact per-item structure sized for an agent's context. The +agent's own model narrates; any reconcile the agent performs goes through `apply_command` +(safe ops only — the tool refuses to auto-apply breaking, matching `reconcile()`). + +### 6. `mdl drift --explain` (CLI, deterministic) +No LLM in the CLI. `--explain` emits the JSON already produced *plus* the `render_markdown` +narrative and, per item, the concrete reconcile command its `payload` implies — the structured +facts the chat/MCP layers consume, and a useful human rendering on its own. This is the source +of truth the other two surfaces wrap. + +## The safety boundary (identical everywhere) + +Mirror `reconcile()` exactly: **additive + cosmetic** can be one-click reconciled (with +confirmation); **breaking** is only ever explained + recommended, never auto-applied, and a +human decides. The LLM annotates and sequences; it never sets severity and never folds a +breaking change. Every write lands through `apply_command`/`drift --reconcile` after validation. + +## Build order + +| Step | Contents | +|---|---| +| **A. Engine** | `mdl drift --explain` (JSON + markdown + per-item reconcile command). Small — the JSON exists; add the narrative + command mapping. | +| **B. Diagnostics** | `DiagnosticCollection` + model-name→file resolver; upgrade `modelith.driftCheck` to populate it from `drift --format json` instead of dumping text; status-bar item. | +| **C. View + code actions** | `modelithDrift` TreeView (grouped by severity, click-to-open, inline reconcile/explain); `CodeActionProvider` (quick-fix reconcile for safe items, explain for breaking); `modelith.driftReconcile` command. | +| **D. Chat** | `@modelith /drift [model]` — grounded explainer with `stream.button`/`reference` actions and deterministic fallback. | +| **E. MCP** | `explain_drift()` tool. | + +Each step leaves the suite green and is independently useful (A alone improves the CLI; B alone +makes drift a first-class Problems citizen). + +## Critical files + +- `packages/reverse/src/mdl_reverse/drift.py`, `render.py` — engine + JSON/markdown (wrap, don't change severity) +- `packages/reverse/src/mdl_reverse/reconcile.py` — the safe/breaking boundary to mirror +- `packages/cli/src/mdl_cli/main.py` — `mdl drift --explain` (the `drift` command is at ~`main.py:551`) +- `vscode/src/extension.ts` — upgrade `modelith.driftCheck` (`:270`); add `driftReconcile`, `driftExplain`, status item, view registration +- `vscode/src/driftView.ts` (NEW) — TreeDataProvider; `vscode/src/driftDiagnostics.ts` (NEW) — DiagnosticCollection + model→file resolver + CodeActionProvider +- `vscode/src/chatParticipant.ts` — `/drift` command (reuse `readModel`/`answerWithModel`, add `stream.button`/`reference`) +- `vscode/package.json` — contribute the commands, the view + viewsContainer, the `modelith.driftExplain` chat command, menus (editor/context on model YAML → Check Drift) +- `packages/mcp/src/mdl_mcp/server.py` — `explain_drift()` tool +- `packages/core/src/mdl_core/query.py` — reuse the model-name→file mapping if one exists (`repo.path_for_ulid`) rather than re-implementing + +## Verification + +- **Python (`uv run pytest && uv run ruff check packages/`):** `mdl drift --explain` against the + IBoR demo emits JSON with per-item payload + a reconcile command per additive/cosmetic item; + breaking items carry no auto-apply command. Break a generated column's contract, `dbt parse`, + and assert the report classifies it. +- **Extension (`cd vscode && npx tsc --noEmit && node esbuild.mjs`, Node 20 via nvm):** typecheck + + bundle; commands, view, and code-action provider register. +- **Manual, `demo/ibor`:** break a column → `dbt parse` → `Modelith: Check Drift` populates the + Problems panel (Error on breaking, Warning on additive) and the Drift view; a quick-fix + reconciles a safe item and it disappears on re-check; a breaking item offers only Explain; + `@modelith /drift` narrates and its "Reconcile safe changes" button runs the command. Dev build + uses the workspace `.venv` mdl (no global reinstall). diff --git a/modelith-ontology-kg-spec.md b/specs/modelith-ontology-kg-spec.md similarity index 100% rename from modelith-ontology-kg-spec.md rename to specs/modelith-ontology-kg-spec.md diff --git a/modelith-spec-v2.md b/specs/modelith-spec-v2.md similarity index 100% rename from modelith-spec-v2.md rename to specs/modelith-spec-v2.md diff --git a/modelith-spec.md b/specs/modelith-spec.md similarity index 100% rename from modelith-spec.md rename to specs/modelith-spec.md diff --git a/specs/modelith-vscode-ai-spec-extended.md b/specs/modelith-vscode-ai-spec-extended.md new file mode 100644 index 0000000..87132df --- /dev/null +++ b/specs/modelith-vscode-ai-spec-extended.md @@ -0,0 +1,170 @@ +# AI features for Modelith — Phase 1 roadmap + +## Context + +Modelith already ships two AI surfaces: an **MCP server** (`packages/mcp/src/mdl_mcp/server.py`, +7 tools — reads + `create_entity`/`update_entity`) and a **VS Code `@modelith` chat +participant** (`vscode/src/chatParticipant.ts`, Ask-mode, grounded on the user's Copilot +model). Both read through the shared query layer `packages/core/src/mdl_core/query.py`. + +The question this plan answers: *what else can AI add that generic Copilot cannot?* The +answer is Modelith's four **structured knowledge assets** a generic assistant can't see — +the ontology alignment engine, the drift engine (with fix-recipe payloads), the +DecisionLedger governance seam, and the validation/naming engines (which emit concrete +fixes). Each feature wraps a deterministic engine as the source of structural truth and +uses the LLM only to **explain, rank, compose prose, and draft** — never to invent +structure. Every write still lands through `apply_command` after validation. + +The user chose **four features across all three surfaces (MCP + `@modelith` + CLI)**: +1. **Alignment Reviewer** — explain + recommend a verdict on each ontology proposal. +2. **Diagnostic/Naming Fixer** — explain each MDL-* diagnostic; apply deterministic naming fixes. +3. **Definition Generator** — draft definitions for undocumented objects. +4. **Drift Explainer** — narrate a drift report; compose a recommended fix from the payload. + +**Out of scope (owned by a separate cloud session):** functional-dependency capture / a +normalization checker. Do not build it here. + +## The shared foundation (build first) + +Two reusable pieces make all four features small; build them before any feature. + +### A. AI grounding helper — `packages/core/src/mdl_core/ai_context.py` (NEW) +A pure function set that assembles the compact, structured "fact sheet" each feature hands +to an LLM — an extension of what `query.py` already does. No LLM call lives in core; core +only shapes facts and parses back structured results. This keeps the engine as the source +of truth and stays testable without a model. + +### B. Surface pattern (repeat per feature, per surface) +- **CLI (`mdl`, Typer):** the engine + a JSON/text `--explain`-style output. The CLI has no + LLM — it emits the structured facts an external model (or the `@modelith`/MCP layers) + consume, plus a deterministic human-readable rendering. This is the source of truth each + other surface wraps. +- **MCP tool (`packages/mcp`):** a new `@mcp.tool()` that returns the same structured facts + (JSON) for the agent's own model to reason over. Reads shape facts; any write goes through + `apply_command` exactly as `create_entity`/`update_entity` do today (`server.py:200`). +- **`@modelith` (`vscode/src/chatParticipant.ts`):** a slash command that fetches the facts + via the `mdl` JSON command (the existing `readModel` path) and grounds `request.model` to + compose the answer, pinned to the facts (same pattern as `answerWithModel`). + +The LLM lives only in the MCP client's model and in `@modelith`'s `request.model`. The CLI +and core stay LLM-free and deterministic. + +## Feature 1 — Alignment Reviewer (Phase 1a, effort S) + +For each unpromoted alignment proposal, explain in plain English why the top candidate fits +(and why the runner-up doesn't) and recommend accept/reject/needs-review. Never picks +candidates — `align_model` already ranks them with confidence bands. + +- **Wraps:** `align_model` + `AlignmentProposal`/`Candidate` (`packages/ontology/src/mdl_ontology/align.py`); + the `DecisionLedger` pending set (`packages/reverse/src/mdl_reverse/ledger.py`, `Decision` + carries `confidence`, `subject`, structured `evidence`). Human promotes via the existing + `promote_alignment` op (`commands.py`). +- **NEW:** `mdl ontology align --explain` (renders proposals + evidence as structured JSON); + MCP tool `explain_alignment(object_name?)`; `@modelith /align `. The + **"explain + recommend verdict over a ledger Decision"** helper built here is reused by F4-review, F2, and later features. +- **Safety:** writes nothing. Annotates existing `proposed` ledger entries; MDL-W206 keeps + flagging unpromoted. Safest possible surface. + +## Feature 2 — Diagnostic / Naming Fixer (Phase 1a, effort S) + +Explain each `Diagnostic` (MDL-* code) in context; for naming hits, offer the fix the engine +already computed. + +- **Wraps:** `validate()` → `DiagnosticSet` (`packages/core/src/mdl_core/validate.py`, already + MCP-exposed at `server.py:98`); `naming.lint()` → `(DiagnosticSet, NamingFix)` + (`packages/core/src/mdl_core/naming.py`) where `NamingFix.entities`/`.attributes` hold the + exact corrected names. Applies via `rename_entity` + attribute-rename ops (the CLI's + `_apply_naming_fixes` already maps `NamingFix` → commands). +- **NEW:** MCP tools `explain_diagnostics()` / `suggest_fixes()`; `@modelith /fix`; extend + `mdl validate`/`mdl naming` with an explain flag. Small — the LLM only explains; corrected + names come from `NamingFix` verbatim. +- **Safety:** deterministic naming fixes are opt-in direct-apply behind confirmation (as + `mdl naming --fix` is today). Structural diagnostics with no auto-fix are explained only. + No new write authority. + +## Feature 3 — Definition Generator (Phase 1b, effort M) + +Find objects with a null `.definition` and draft one grounded in the object's own +attributes, keys, relationships, and any ontology alignment. + +- **Wraps:** the query-layer shapes (definition already threaded through `get_entity` etc.); + writes via existing `set_definition` / `set_object_definition` (`commands.py`). +- **NEW:** (a) a **definition-gap scanner** over the IR — mirror `coverage_report`'s shape in + `packages/ontology/src/mdl_ontology/layers.py` (there is no definition-coverage report + today, only ontology-alignment coverage); (b) LLM draft/parse. Surfaces: `mdl doc gaps` + (report) + `mdl doc draft` (stage); MCP `draft_definitions(scope?)`; `@modelith /document `. +- **Safety:** definitions are prose — route drafts through the `DecisionLedger` as + `kind="definition_draft"` proposals (reuse `signal_key` dedupe so re-runs don't re-propose + accepted drafts). Human reviews a batch; a promote folds accepted drafts through + `set_definition`. Never bulk auto-write. + +## Feature 4 — Drift Explainer (Phase 1b, effort M) + +Narrate a `DriftReport` grouped by severity; for breaking items (which `--reconcile` refuses) +compose a recommended remediation from the item's `payload` fix recipe. + +- **Wraps:** `compute_drift` → `DriftReport`/`DriftItem` (`packages/reverse/src/mdl_reverse/drift.py`); + each `DriftItem` carries `severity`, `kind`, `detail`, and a machine `payload`. Severity + vocabulary in `packages/core/src/mdl_core/severity.py`. +- **NEW:** `mdl drift --explain`; MCP `explain_drift()`; `@modelith /drift`. LLM narrates + + sequences a fix from the recipe; it must never re-derive severity/direction (the tests pin + those). +- **Safety:** mirror the existing `reconcile` boundary exactly — additive/cosmetic can be + staged for one-click apply; breaking is explained + recommended only, staged in the ledger + for a human verdict, never auto-applied. + +## Phasing + +| Phase | Contents | Why | +|---|---|---| +| **0** | Shared foundation: `ai_context.py` grounding helper; the reusable "explain + recommend verdict over a ledger Decision" component; establish the per-surface pattern (CLI JSON → MCP tool → `@modelith` slash command). | Everything else is thin on top of this. | +| **1a** | **F1 Alignment Reviewer** + **F2 Diagnostic/Naming Fixer** — both effort S, both wrap engines that already emit structured/reproducible output, both extend the thin MCP surface. F1 builds the ledger-verdict layer F3 reuses. | Fastest to shippable value, lowest risk. | +| **1b** | **F3 Definition Generator** + **F4 Drift Explainer** — both effort M; F3 needs the new gap scanner, F4 maps payloads to staged commands. | Higher-value, moderate new code, both reuse the Phase 0 seams. | + +Each phase leaves the suite green and ships all three surfaces for its features. + +## The one guardrail (enforce across all four) + +The LLM's output is always **annotation, prose, or a proposal of named operations** — never +raw structure. The engine (align / validate / naming / drift / `apply_command`) remains the +sole source of structural truth; every write lands through `apply_command` after validation, +and staged proposals go through the `DecisionLedger` for a human verdict. A feature must fail +closed: if the LLM is unavailable, the deterministic CLI/engine output still works (as +`@modelith` already falls back today). + +## Critical files + +- `packages/core/src/mdl_core/ai_context.py` (NEW — grounding helper) +- `packages/core/src/mdl_core/query.py` (existing shapes to extend) +- `packages/mcp/src/mdl_mcp/server.py` (new tools; reuse the `apply_command` write path at `server.py:200`) +- `packages/cli/src/mdl_cli/main.py` (new `--explain` flags + `mdl doc` group, following the Typer subcommand pattern) +- `vscode/src/chatParticipant.ts` (new slash commands, reusing `readModel`/`answerWithModel`) +- Engines wrapped: `packages/ontology/src/mdl_ontology/align.py` (F1), + `packages/core/src/mdl_core/{validate,naming}.py` (F2), + `packages/ontology/src/mdl_ontology/layers.py` as the gap-scanner pattern (F3), + `packages/reverse/src/mdl_reverse/drift.py` (F4) +- `packages/reverse/src/mdl_reverse/ledger.py` (the propose→verdict seam F1/F3 write into) + +## Verification + +Each feature is testable without an LLM, because the engine + CLI JSON output is +deterministic — that is the layer the tests target. + +- **Python (`uv run pytest && uv run ruff check packages/`):** + - F1: `mdl ontology align --explain` emits structured proposals with candidates + evidence + for the demo model; a promoted alignment disappears from the pending set. + - F2: `mdl validate`/`mdl naming` explain output includes each MDL-* code and, for naming, + the exact `NamingFix` correction; applying it renames on disk and re-validates clean. + - F3: the definition-gap scanner finds the null-definition objects in a fixture; a staged + `definition_draft` promotes through `set_definition` and the gap closes; re-run doesn't + re-propose an accepted draft (signal_key dedupe). + - F4: `mdl drift --explain` groups by severity and attaches the `payload` recipe; breaking + items are never in the auto-apply set. + - New MCP tools: drive them through the FastMCP `call_tool` path (as `packages/mcp/tests/test_server.py` + already does) and assert the JSON shape. +- **Extension (`cd vscode && npx tsc --noEmit && node esbuild.mjs`, Node 20 via nvm):** + typecheck + bundle; the slash commands compile and register. +- **Manual, against `demo/ibor/model` in VS Code:** `@modelith /align instrument`, + `@modelith /fix`, `@modelith /document position`, `@modelith /drift` — each grounds on real + facts and, for writes, shows the proposal before anything lands. Reinstall path uses the + workspace `.venv` mdl (dev builds pick up working-tree code without a global reinstall). \ No newline at end of file diff --git a/modelith-vscode-ai-spec.md b/specs/modelith-vscode-ai-spec.md similarity index 100% rename from modelith-vscode-ai-spec.md rename to specs/modelith-vscode-ai-spec.md