@dforge-core/dforge-mcp 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,237 @@ All notable changes to `@dforge-core/dforge-mcp`. This project uses semver-ish
4
4
  `0.1.0-rc.N` pre-release tags; the published version is set at publish time via
5
5
  the release workflow, so committed `package.json` versions are placeholders.
6
6
 
7
+ ## 0.2.0
8
+
9
+ Tools go from 27 → 34; the single wizard skill splits into a router plus three
10
+ stage skills; `dforge_module_validate` grows the checks that previously only
11
+ surfaced at install.
12
+
13
+ ### Fixed
14
+
15
+ - **`dforge_module_inspect` reported nothing useful about actions.** It read
16
+ `entity` / `mode` / `background`, but `dforge_action_add` has always written
17
+ `entityCode` / `executionMode` / `isAsync` (matching the installer and the
18
+ canonical example), so every action inspected as `entity: "?"`. Since the
19
+ skill makes inspect the pre-patch source of truth, agents were planning
20
+ against wrong data. Legacy key names are still read as a fallback.
21
+ - **`dforge_module_inspect` listed `["jobs"]` as the job list** — `logic/jobs.json`
22
+ is `{ jobs: [...] }`, not a code-keyed map, so `Object.keys` returned the
23
+ wrapper key. Jobs, triggers and webhooks are now reported properly, alongside
24
+ queries, print templates, domains and `supportedLocales`.
25
+ - **Every entity's `toString` was reported as an inherited function.** `toString`
26
+ comes from `Object.prototype`, so reading `entity.toString` never yields
27
+ `undefined`; `JSON.stringify` then dropped it silently. Now read as an own
28
+ property.
29
+ - **Freshly scaffolded entities shipped a dangling `toString`.** dforge-cli's
30
+ `buildEntity` emits `"{id}"`, but the `identity` trait names the PK
31
+ `{entity}_id` — so the template referenced a column that never existed.
32
+ Normalized to `{<entity>_id}` on the way out of `module_create` / `entity_add`.
33
+ - **Phase labels in the tool descriptions had drifted** from the skill: entity
34
+ and field tools claimed Phase 2 (they're Phase 1); views and reports claimed
35
+ Phase 4 (they're Phase 3). Tool descriptions are always in context and the
36
+ skill table isn't, so the wrong one was winning.
37
+ - **Resource descriptions never reached the client.** The deprecated
38
+ `server.resource()` signature dropped them; `registerResource` carries them.
39
+
40
+ ### Added — composite entity tools
41
+
42
+ Three dForge concepts each span several coordinated keys, and hand-assembling
43
+ them from `entity_field_add` is the top source of broken modules. These take the
44
+ intent and emit the whole shape, so the broken variants aren't representable:
45
+
46
+ - **`dforge_entity_reference_add`** — a relation as all three of its parts:
47
+ hidden FK (`cuid` / `EM` / no `fieldTypeCd`), visible Reference (`R` /
48
+ `lookup` / `link`), and the `references` entry. Also *completes* a half-built
49
+ relation by reusing an FK column an import already created.
50
+ - **`dforge_entity_rollup_add`** — a child total as a **Generated** (`G`) column,
51
+ creating the parent's Set column when needed, and refusing to aggregate a
52
+ virtual `F`/`R`/`S` child (the `column old.<field> does not exist` failure).
53
+ - **`dforge_entity_status_add`** — a dropdown with `params.options` objects and
54
+ the initial value as a `formula`, never the `defaultValue` key the entity
55
+ schema rejects.
56
+
57
+ ### Added — DSL static checking
58
+
59
+ - **`dforge_action_check`** — check a draft `dslBody` before committing to
60
+ `action_add`, or an `actionCode` already on disk (its execution mode and job
61
+ bindings are read from the module). Catches `TODAY()`/`NOW()` inside
62
+ `execute:`, `[field]` record-context in batch mode or a job-invoked action,
63
+ block order/duplication, top-level `return`, `:param` SQL placeholders, and
64
+ unknown host functions.
65
+ - `dforge_action_add` now runs the same checker and rejects errors, replacing
66
+ the single hard-coded `TODAY()` grep.
67
+ - `dforge_module_validate` runs it over every action body in the module.
68
+
69
+ ### Added — the element tools that were missing
70
+
71
+ - **`dforge_menu_add`** — menus had a reference doc and a schema but no tool, so
72
+ agents hand-wrote the JSON and hit the same three documented mistakes. Now
73
+ `dataViewCode` is validated against `data_views.json`, `itemType` lands on
74
+ leaves only, and icons are normalized to the bare form menus require.
75
+ - **`dforge_translation_sync`** — generates every install-required key from the
76
+ module's own contents (entities + trait-provided fields, views, menus, roles,
77
+ actions, folders, settings), never overwriting existing translated text.
78
+ "Missing translation key" was a documented install-failure mode with no tool
79
+ behind it, and role labels are completeness-enforced.
80
+ - **`dforge_seed_add`** — enforces the four documented seed traps: numeric
81
+ `{entity}_id` PKs, parent-before-child load order, `audit-full` System-user
82
+ columns, and FKs pointing at seeded parents.
83
+
84
+ ### Added — `apply: true`
85
+
86
+ Every patch tool accepts `apply`. It writes the file map to disk and returns the
87
+ paths instead of the contents — cutting the full-file round trip through context
88
+ on routine patches, and guaranteeing the `deletes` half of a rename/delete is
89
+ applied. Refuses to write outside `moduleDir`; never writes report payloads.
90
+
91
+ ### Added — full-lifecycle phase ledger
92
+
93
+ `docs/phase.json` now records Phases 1-6, not just Phase 0.
94
+ `dforge_module_plan({ action: "check" })` returns `currentPhase`, `nextSkill`
95
+ and `gaps` derived from the ledger **plus** evidence read from the module
96
+ (entities without fields, without a view, without a Select grant).
97
+ `complete_phase` records a phase as done or deliberately skipped. Previously a
98
+ resumed session had to guess the phase by cross-referencing inspect output,
99
+ which can't distinguish "skipped deliberately" from "not started".
100
+
101
+ ### Fixed — follow-ups from review
102
+
103
+ - **`dbDatatype: "number"` produced a suggestion with unbalanced quotes.** The
104
+ alias table smuggled quote characters into its value to fake a list, so the
105
+ rendered message depended on the surrounding template. Multi-option
106
+ suggestions are now real arrays formatted by one helper — `use 'int' /
107
+ 'bigint' / 'numeric'.`
108
+ - **Folder codes were only checked among siblings.** A folder is addressed flat
109
+ and path-less outside the tree — `folder:<code>` in role rights,
110
+ `folders.<code>.label` in translations — so the same code in two branches was
111
+ ambiguous in the rights matrix and let one folder's label silently overwrite
112
+ the other's in a synced translation file. `dforge_folder_add` now refuses a
113
+ code used anywhere in the tree, `dforge_module_validate` errors on duplicates,
114
+ and `dforge_translation_sync` refuses to generate colliding keys.
115
+ - **`apply: true` without `moduleDir` silently degraded to a preview** — a
116
+ client relying on apply semantics got a success response and no write. It now
117
+ errors.
118
+ - **Raw NUL bytes were being used as composite-key separators** (the validator's
119
+ view+entity keys and `dforge_translation_sync`'s keep/prune path keys). Beyond
120
+ being invisible in a diff and unreadable in debug output, a NUL makes `grep`
121
+ classify the whole source file as BINARY and stop reporting matches in it — so
122
+ the usual "search the codebase" check came back clean while the problem sat
123
+ right there. Both now use a shared, exported `compositeKey()` / `KEY_SEP =
124
+ "::"`, and a `source-hygiene` test fails the build on any raw NUL or other
125
+ invisible control character in `src/` or `test/`.
126
+ - **`dforge_entity_reference_add` reused an existing FK column without checking
127
+ its shape.** An import commonly emits the FK as a visible numeric column;
128
+ reusing it as-is produced a pair that looked complete and failed at install.
129
+ The column is now normalized to the hidden-FK shape (`cuid` / `EM`, no
130
+ `fieldTypeCd`, no `columnType`), preserving author metadata like `orderNum`
131
+ and `description`, and the response reports exactly what changed.
132
+ - **`dforge_module_plan` was registered through the ToolResult envelope with a
133
+ cast**, which would have absorbed a real shape mismatch — most of its actions
134
+ return lifecycle state with no `files` at all. It now has an explicit
135
+ `PlanResult` type and a `serialize()` envelope that makes no ToolResult
136
+ assumption. (The envelope was also renamed off `readOnly`: read-only-ness is
137
+ declared by the tool `annotations`, and some tools it wraps do return files.)
138
+ - **`applyToDisk` would throw an opaque error if `deletes` ever contained a
139
+ directory.** `deletes` is a file-only contract, so it now says so: directories
140
+ are refused by name rather than removed recursively (silently deleting a
141
+ subtree would be the worst response to a tool bug), and symlinks are unlinked
142
+ rather than followed.
143
+
144
+ ### Fixed — second review pass
145
+
146
+ - **`dforge_seed_add` hard-coded a list of "trait-provided columns" that was
147
+ wrong on almost every entry.** The registry's real names are `created_date` /
148
+ `last_updated` (audit), `order_num` (sorting), `active` (soft-delete) — not
149
+ `created_at` / `updated_at` / `sort_order` / `is_deleted`, and `audit-full`'s
150
+ `created_by_user` / `last_updated_by_user` and the whole `period` set were
151
+ missing. Seeding a legitimate `created_date` was rejected as an unknown
152
+ column. Now derived from `expandTraits` for the entity's actual trait list.
153
+ - **Unknown trait codes were silently dropped, not rejected.** `expandTraits`
154
+ returns only the codes it recognizes, so a typo'd trait doesn't fail — its
155
+ columns just vanish, and every downstream check then reports them as "not a
156
+ column". `dforge_module_validate` now flags the cause, and `seed_add` /
157
+ `translation_sync` refuse to run against one.
158
+ - **`dforge_translation_sync` silently skipped unreadable entity files**,
159
+ producing an incomplete skeleton while reporting success — the gap would only
160
+ surface later as a missing-label install failure. A missing or malformed
161
+ entity file named in the manifest is now fatal, and names the path.
162
+ - **The SQL-literal scan used a regex that stopped at the first escaped quote**,
163
+ so `query('... \' ... :cid')` truncated mid-statement and the placeholder
164
+ check missed what followed. Replaced with an escape-aware string scanner;
165
+ concatenation is now detected after the literal rather than inside it.
166
+ - `dforge_module_plan`'s `complete_phase` cast its own check result back to an
167
+ untyped bag, discarding the `PlanResult` typing; `buildPhaseCheck` now returns
168
+ a declared `BuildPhaseCheck` and the fields are destructured.
169
+ - Renamed the control-character guard test to match what it scans (whole source,
170
+ not only string literals).
171
+
172
+ ### Changed — validation is now the single choke point
173
+
174
+ Field rules lived only in the `entity_field_add` zod schema, so anything
175
+ entering via `module_import`, `dbml_import`, the scaffolder or a hand edit
176
+ bypassed them entirely. They now live in one place and run in both. New checks
177
+ in `dforge_module_validate`:
178
+
179
+ - every field re-checked against the full rule set, module-wide
180
+ - Formula (`F`) and Generated (`G`) column shape; Reference (`R`) column shape
181
+ - `dbDatatype` values that are really `fieldTypeCd`s
182
+ - `toString` present, and its `{braces}` resolving to real columns
183
+ - an `F` column carrying a set aggregate; a `G` aggregate over a virtual child
184
+ - action `script` files missing from disk, or not bare filenames
185
+ - triggers/jobs firing actions that don't exist; job actions using `[field]`
186
+ - DSL static checks on every action body
187
+ - translation completeness: a file per `supportedLocales` entry, and a
188
+ `roles.<code>.label` in every locale file including the en-US base
189
+
190
+ ### Fixed — the skills installer did not work on Windows
191
+
192
+ `scripts/install-skills.sh` was bash-only, so it could not run in cmd.exe or
193
+ PowerShell at all — and `npm run install-skills` invoked `bash`, which fails
194
+ there too. Under WSL it appeared to work but resolved `$HOME` to the *Linux*
195
+ home, silently installing to `/home/you/.claude` while a Windows Claude Code
196
+ read `C:\Users\you\.claude`. `scripts/` was also absent from package.json
197
+ `files`, so the script did not ship at all.
198
+
199
+ The installer is now **`scripts/install-skills.mjs`** (Node), behaving
200
+ identically on Windows/macOS/Linux/Git Bash/WSL:
201
+
202
+ - exposed as a `dforge-install-skills` bin, so
203
+ `npx -y -p @dforge-core/dforge-mcp dforge-install-skills` needs no clone and
204
+ no bash (npm shims it as `.cmd` on Windows)
205
+ - destination resolves `DEST` → `CLAUDE_CONFIG_DIR/skills` →
206
+ `os.homedir()/.claude/skills`, so Windows gets `%USERPROFILE%` regardless of
207
+ the invoking shell
208
+ - `--from-npm` uses `npm pack` + `tar` (built into Windows 10 1803+), which
209
+ fetches only this package rather than its ~35 MB native CLI dependency
210
+ - handles `EPIPE` so piping into `head`/`less` doesn't dump a stack trace
211
+ - `scripts/install-skills.mjs` added to package.json `files`
212
+
213
+ `scripts/install-skills.sh` is kept as a thin wrapper that execs the Node
214
+ script, so the documented `curl … | bash` one-liner still works.
215
+
216
+ ### Changed — skills split by lifecycle stage
217
+
218
+ `dforge-mcp-author` becomes a thin router; `dforge-module-design` (Phase 0),
219
+ `dforge-module-build` (Phases 1-5) and `dforge-module-ship` (Phase 6) own the
220
+ instructions. Each stage needs a different half of the knowledge base, and the
221
+ 488-line monolith was resident for all of it. Handoff is deterministic via
222
+ `dforge_module_plan`'s `nextSkill`. The router directory still carries the
223
+ shared `references/` + `examples/` the MCP server serves as resources.
224
+
225
+ `scripts/install-skills.sh` installs all four (`--from-npm` to pull the
226
+ published package).
227
+
228
+ ### Changed — internal
229
+
230
+ - Migrated from the deprecated `server.tool()` / `server.resource()` to
231
+ `registerTool` / `registerResource`, adding titles and annotations
232
+ (`readOnlyHint` on inspect/validate/action_check, `destructiveHint` on the
233
+ delete/rename refactors and install, `openWorldHint` on pack/install).
234
+ - Tests: 80 → 161, including an end-to-end suite that drives a module through
235
+ the real tool surface on a temp directory and asserts it validates clean. That
236
+ suite is what caught the inspect key mismatch and the scaffolder's `{id}`.
237
+
7
238
  ## 0.1.12
8
239
 
9
240
  > Release prerequisite: publish `@dforge-core/metadata@0.0.10` first (adds the
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @dforge-core/dforge-mcp
2
2
 
3
- MCP server for dForge module authoring. Exposes 27 composable tools and the canonical schemas so AI agents (Claude Code, Cursor, Zed, etc.) can drive the full module lifecycle — scaffold → entities → actions → views → security → install — through structured tool calls instead of free-form JSON generation.
3
+ MCP server for dForge module authoring. Exposes 34 composable tools and the canonical schemas so AI agents (Claude Code, Cursor, Zed, etc.) can drive the full module lifecycle — scaffold → entities → actions → views → security → install — through structured tool calls instead of free-form JSON generation.
4
4
 
5
- Ships with a wizard Skill (`skills/dforge-mcp-author/`) that walks the AI through six phases with explicit backtrack support when later phases expose earlier gaps. The skill bundle includes 23 detailed reference files (field types, flags, traits, formulas, DSL, security, etc.) and an annotated `simple-todo` example module.
5
+ Ships with **four Claude Skills** (`skills/`): a router plus one skill per lifecycle stage — design (Phase 0), build (Phases 1-5), and ship (Phase 6). The router directory carries 25 reference files (field types, flags, traits, formulas, DSL, security, etc.) and two annotated example modules.
6
6
 
7
7
  **New here?** Start with **[docs/creating-modules.md](docs/creating-modules.md)** — three ways to scaffold a module (terminal CLI, VS Code sidebar, AI wizard) and when to pick each.
8
8
 
@@ -74,7 +74,7 @@ claude mcp list
74
74
  # Should show: dforge — npx -y @dforge-core/dforge-mcp — connected
75
75
  ```
76
76
 
77
- Or inside a Claude Code session, type `/mcp` to see all connected servers + their tools. The 27 `dforge_*` tools should be listed.
77
+ Or inside a Claude Code session, type `/mcp` to see all connected servers + their tools. The 34 `dforge_*` tools should be listed.
78
78
 
79
79
  ### Cursor / Zed
80
80
 
@@ -82,7 +82,7 @@ Same `command + args` config shape; check their docs for the file location. Veri
82
82
 
83
83
  ## What it exposes
84
84
 
85
- ### Tools (27)
85
+ ### Tools (34)
86
86
 
87
87
  Grouped by typical phase in the wizard flow. All "return" tools emit `{ summary, files: { '<relPath>': '<contents>' } }`; the client decides whether to write — lets the AI preview diffs with the user before committing.
88
88
 
@@ -91,11 +91,11 @@ Phase 0 (identity → requirements → design → validation) is orchestrated by
91
91
  **Module-level**
92
92
  | Tool | Behavior |
93
93
  |---|---|
94
- | `dforge_module_plan` | **Phase 0 orchestrator** — drives identity → requirements → design → validation, returning the next step each call; gates `dforge_module_create` until `readyToScaffold: true`. Call first in any session (`action: "check"`) |
94
+ | `dforge_module_plan` | **Lifecycle orchestrator** — drives Phase 0 (identity → requirements → design → validation), gates `dforge_module_create` until `readyToScaffold: true`, then tracks Phases 1-6 via a ledger in `docs/phase.json`. `action: "check"` returns `currentPhase` + `nextSkill`; `complete_phase` records a phase done or skipped. Call first in any session |
95
95
  | `dforge_module_create` | New module scaffold (blocked until Phase 0 passes) |
96
96
  | `dforge_module_import` | Import a normalized **table-spec** (tables → columns → relationships) into an existing module as entities. Infers `fieldTypeCd` from SQL type / sample values / name (metadata-validated, `dbDatatype` derived) and builds the FK+Reference pair per relationship. Fed by DBML/SQL, Excel/CSV, or a hand-authored spec |
97
97
  | `dforge_module_inspect` | Read current module state. Full structured data is in `files["_inspect.json"]`; `summary` is one-line stats |
98
- | `dforge_module_validate` | Offline cross-reference check (run before pack): dangling FK targets, missing hidden-FK columns, view/menu/role refs to non-existent things, uncovered entities. Errors + warnings in `files["_validate.json"]` |
98
+ | `dforge_module_validate` | Offline check (run before pack): dangling FK targets, missing hidden-FK columns, view/menu/role refs to non-existent things, uncovered entities, **field-spec rules re-run across every field**, `toString` templates, Formula-vs-Generated set aggregates, missing action DSL files, triggers/jobs firing non-existent actions, DSL static checks, and translation completeness. Errors + warnings in `files["_validate.json"]` |
99
99
  | `dforge_module_pack` | Shells to `dforge-cli module pack` via bundled CLI, PATH fallback, or `DFORGE_CLI_BINARY`. Returns tarball path + size |
100
100
  | `dforge_module_install` | Shells to `dforge-cli module install`. Args: `pathOrTarball`, optional `tenantUrl` / `token` / `tenantCode` — fall back to `DFORGE_URL` / `DFORGE_TOKEN` env. Returns `ok`, `exitCode`, `command`, and raw CLI `output` so the AI can fix install-time module defects and retry |
101
101
 
@@ -110,23 +110,41 @@ Phase 0 (identity → requirements → design → validation) is orchestrated by
110
110
  | `dforge_entity_field_rename` | **Refactor-safe rename** — propagates the new name to the paired Reference, formulas, view columns + order, seed data, and other entities' FKs |
111
111
  | `dforge_entity_field_remove` | **Refactor-safe remove** — cascade-cleans paired Reference, view columns + order, seed keys; warns on formula/cross-entity dependents |
112
112
 
113
+ **Composite entity tools (Phase 1) — one call per concept, prefer these**
114
+
115
+ Each of these spans several coordinated keys that hand-assembly gets wrong. They exist so the broken shapes aren't expressible.
116
+
117
+ | Tool | Emits |
118
+ |---|---|
119
+ | `dforge_entity_reference_add` | A whole relation: hidden FK (`cuid`/`EM`) **+** Reference column (`R`/`lookup`/`link`) **+** the `references` entry. The documented #1 source of broken modules, in one call. Also completes a half-built relation by reusing an FK an import already created |
120
+ | `dforge_entity_rollup_add` | A child total as a **Generated** (`G`) column — never a Formula, whose set-aggregates silently render empty — creating the parent's Set column if needed, and refusing to aggregate a virtual `F`/`R`/`S` child (`column old.<field> does not exist`) |
121
+ | `dforge_entity_status_add` | A dropdown with `params.options` objects and the initial value as a `formula` — entity fields have no `defaultValue` key |
122
+
113
123
  The field/entity tools validate against the `@dforge-core/metadata` registry: an unknown `fieldTypeCd` (e.g. `integer`, `reference`), `columnType`, or trait code is rejected at authoring time with a "did you mean" hint, and `dbDatatype` is auto-derived from the field type (currency → `numeric(18,2)`, text → `varchar`; reference/formula columns get none) unless you set it explicitly.
114
124
 
115
125
  **Behavior (Phase 2 — optional)**
116
126
  | Tool | Behavior |
117
127
  |---|---|
118
- | `dforge_action_add` | DSL script + `ui/actions.json` entry |
128
+ | `dforge_action_add` | DSL script + `ui/actions.json` entry. Runs the DSL static checker first — errors reject the call |
129
+ | `dforge_action_check` | Statically check a **draft** `dslBody` before committing, or an `actionCode` already on disk. Catches `TODAY()` in `execute:`, `[field]` in batch/job context, block order, top-level `return`, `:param` placeholders, unknown host functions |
119
130
  | `dforge_trigger_add` | DB-event trigger in `logic/triggers.json` (entity event + optional condition → action) |
120
131
  | `dforge_job_add` | Scheduled job in `logic/jobs.json` (5-field cron + timeout + action) |
121
132
  | `dforge_webhook_add` | Outbound webhook in `logic/webhooks.json` (entity event → POST to endpoint) |
122
133
 
123
- **Views + reports (Phase 3)**
134
+ **Views, menus + reports (Phase 3)**
124
135
  | Tool | Behavior |
125
136
  |---|---|
126
137
  | `dforge_view_add` | Add a data view |
127
138
  | `dforge_view_modify` | Replace a view's spec |
139
+ | `dforge_menu_add` | Add a menu leaf or section. Validates `dataViewCode` against `data_views.json`, emits `itemType: "V"` on leaves only, and normalizes icons to the bare form menus require (strips `bi-`) |
128
140
  | `dforge_report_add` | Add a report |
141
+
142
+ **Polish (Phase 4)**
143
+ | Tool | Behavior |
144
+ |---|---|
129
145
  | `dforge_setting_add` | Configurable module-level setting |
146
+ | `dforge_translation_sync` | Generate/refresh `translations/<locale>.json` from the module's own contents. Never overwrites existing translated text; fills the role labels that are **completeness-enforced at install**. Defaults to en-US + every `supportedLocales` entry |
147
+ | `dforge_seed_add` | Write a seed file for one entity. Enforces numeric `{entity}_id` PKs, parent-before-child load order, `audit-full` System-user columns, and FKs that point at seeded parents |
130
148
 
131
149
  **Security (Phase 5)**
132
150
  | Tool | Behavior |
@@ -141,23 +159,33 @@ The field/entity tools validate against the `@dforge-core/metadata` registry: an
141
159
  | `dforge_dependency_add` | Add a dep on another dForge module |
142
160
  | `dforge_dbml_import` | **DBML front-end** to `module_import` — parses DBML (Table blocks, typed columns, inline + top-level refs) into the table-spec, drops the source PK (identity provides `{entity}_id`), and imports. Pass `module` for a greenfield import |
143
161
 
144
- ### Resources (13)
162
+ ### Writing files: preview vs `apply`
163
+
164
+ Every patch tool returns `{ summary, files, deletes? }` for the client to write, so the agent can preview a diff with the user first. Passing **`apply: true`** instead writes the files (and honours `deletes`) directly and returns only the paths touched.
145
165
 
146
- | URI | Content |
166
+ ```jsonc
167
+ // preview (default) — full file contents come back through the model's context
168
+ { "moduleDir": "/path/to/mod", "entityName": "order", "fieldName": "note", "field": { ... } }
169
+
170
+ // apply — writes to disk, returns { applied: true, written: [...], deleted: [...] }
171
+ { "moduleDir": "/path/to/mod", "entityName": "order", "fieldName": "note", "field": { ... }, "apply": true }
172
+ ```
173
+
174
+ Use `apply` for routine patches inside an already-approved plan, and for every `rename`/`delete` refactor — it guarantees the `deletes` half is applied, which is easy to drop by hand. It refuses to write outside `moduleDir`, and never writes the `_inspect.json` / `_validate.json` / `_action_check.json` report payloads, which only reuse the file-map shape for transport.
175
+
176
+ ### Resources
177
+
178
+ Served over MCP with descriptions and mime types, so the agent can tell what a URI is for before pulling it.
179
+
180
+ | URI pattern | Content |
147
181
  |---|---|
148
- | `dforge://schema/manifest` | manifest.json JSON Schema |
149
- | `dforge://schema/entity` | entity files |
150
- | `dforge://schema/data-views` | ui/data_views.json |
151
- | `dforge://schema/folders` | ui/folders.json |
152
- | `dforge://schema/menus` | ui/menus.json |
153
- | `dforge://schema/roles` | security/roles.json |
154
- | `dforge://schema/jobs` | logic/jobs.json |
155
- | `dforge://schema/seed-data` | seed-data/*.json |
156
- | `dforge://schema/reports` | ui/reports.json |
157
- | `dforge://schema/settings` | settings.json |
158
- | `dforge://schema/traits` | entity trait codes |
159
- | `dforge://schema/webhooks` | ui/webhooks.json |
182
+ | `dforge://schema/<name>` | JSON Schemas: `manifest`, `entity`, `domains`, `data-views`, `folders`, `menus`, `roles`, `jobs`, `seed-data`, `traits`, `webhooks`, `triggers`, `print-templates`, `settings`, `reports` |
183
+ | `dforge://reference/<name>` | 25 per-element authoring references — schema shape + worked example + common-mistakes list for one element type |
184
+ | `dforge://example/<path>` | Files from the canonical `simple-todo` module (mandatory structure validators) |
185
+ | `dforge://example/matrix-budget/<path>` | Files from the `matrix` (pivot) view example |
160
186
  | `dforge://docs/conventions` | MODULE_CONVENTIONS.md |
187
+ | `dforge://docs/dsl` | Full action-DSL grammar, built-ins, patterns, anti-patterns |
188
+ | `dforge://script/xlsx-to-model` | Stdlib Python `.xlsx` → table-spec extractor |
161
189
 
162
190
  Schemas + conventions are vendored at build time from `iash44/dForge-core`'s `docs/`. The published npm tarball ships them under `resources/`, and jsdelivr serves them at:
163
191
 
@@ -167,70 +195,76 @@ https://cdn.jsdelivr.net/npm/@dforge-core/dforge-mcp@latest/resources/schemas/<n
167
195
 
168
196
  **Compatibility:** schemas vendored for this release came from `iash44/dForge-core` `main` as of the publish date stamped in `package.json`. If the platform adds new entity properties / field types after this release, generated modules using those features may validate locally but be rejected at install time. Bump the dforge-mcp version when the platform schemas change materially.
169
197
 
170
- ## Claude Skill — the wizard
198
+ ## Claude Skills — router + three stage skills
171
199
 
172
- The skill bundle lives at `skills/dforge-mcp-author/` and contains:
200
+ Authoring guidance ships as **four** skills, split by lifecycle stage. Each stage needs a different half of the knowledge base — Phase 0 needs none of the field-type/DSL detail, Phase 6 needs none of the intake guardrails — so loading only the active stage is what keeps a full module build inside one context window.
201
+
202
+ | Skill | Phases | Owns |
203
+ |---|---|---|
204
+ | `dforge-mcp-author` | — | **Router. Start here.** Calls `dforge_module_plan` to find the current phase and hands off. Also carries the shared `references/` + `examples/`. |
205
+ | `dforge-module-design` | 0a–0d | Identity, intake guardrails, design doc, pre-scaffold validation. Ends at `readyToScaffold: true`. |
206
+ | `dforge-module-build` | 1–5 | Entities, behavior, views/menus, polish, security. Loading-policy table, core-rules cheat sheet, backtrack protocol. |
207
+ | `dforge-module-ship` | 6 | Validate → pre-pack review → version audit → pack → install-fix loop. |
208
+
209
+ The handoff is deterministic rather than vibes-based: `dforge_module_plan({ action: "check" })` returns `currentPhase` **and** `nextSkill`, derived from the Phase 0 artifacts, the phase ledger in `docs/phase.json`, and evidence read from the module itself (entities without fields, entities without a view, entities without a Select grant).
210
+
211
+ The router directory also holds the shared assets:
173
212
 
174
213
  | Path | Contents |
175
214
  |---|---|
176
- | `SKILL.md` | Six-phase co-pilot wizard |
177
- | `references/*.md` | 23 detailed reference files (field types, flags, traits, formulas, DSL, security, views, menus, translations, …) |
215
+ | `references/*.md` | 25 reference files (field types, flags, traits, formulas, DSL, security, views, menus, translations, …) |
178
216
  | `examples/simple-todo/` | Annotated reference module showing all core patterns |
217
+ | `examples/matrix-budget/` | Worked example for the `matrix` (pivot) view type |
218
+ | `scripts/xlsx_to_model.py` | Stdlib Python `.xlsx` → table-spec extractor |
219
+
220
+ **Skills are NOT auto-installed by `npm install`** — they ship in the npm tarball, but Claude Code looks in `~/.claude/skills/`, not `node_modules`. Use the installer, which is a Node script so it behaves identically on **Windows (cmd.exe / PowerShell), macOS, Linux, Git Bash and WSL**:
221
+
222
+ ```bash
223
+ # Straight from the published package — no clone needed:
224
+ npx -y -p @dforge-core/dforge-mcp dforge-install-skills
225
+
226
+ # From a local checkout:
227
+ npm run install-skills
179
228
 
180
- **It is NOT auto-installed by `npm install`** — the skill ships in the npm tarball but Claude Code looks for skills in `~/.claude/skills/`, not in `node_modules`. Sync the whole bundle manually:
229
+ # Equivalently, without npm:
230
+ node scripts/install-skills.mjs [--from-npm]
231
+ ```
232
+
233
+ Destination resolution, in order: `DEST` → `CLAUDE_CONFIG_DIR/skills` → `<home>/.claude/skills`. The home directory comes from Node's `os.homedir()`, so on Windows it's `%USERPROFILE%` no matter which shell invoked it.
181
234
 
182
235
  ```bash
183
- # Resolve the actual latest published version from the npm registry,
184
- # then pin the jsdelivr URL to it. We don't use jsdelivr's `@latest`
185
- # alias directly — that CDN endpoint caches aggressively (6-12h lag
186
- # after a new publish), which silently serves stale Skill content.
187
- VERSION=$(npm view @dforge-core/dforge-mcp version)
188
- BASE="https://cdn.jsdelivr.net/npm/@dforge-core/dforge-mcp@${VERSION}/skills/dforge-mcp-author"
189
-
190
- # Wizard
191
- mkdir -p ~/.claude/skills/dforge-mcp-author
192
- curl -fsSL "$BASE/SKILL.md" -o ~/.claude/skills/dforge-mcp-author/SKILL.md
193
-
194
- # Reference files (23 guides — load on demand per the table in SKILL.md)
195
- mkdir -p ~/.claude/skills/dforge-mcp-author/references
196
- for f in action-dsl column-types conventions data-migration data-views \
197
- field-types filters flags formulas jobs manifest menus \
198
- number-sequences print-templates queries reports schema-import \
199
- security settings traits translations validation-checklist; do
200
- curl -fsSL "$BASE/references/${f}.md" \
201
- -o ~/.claude/skills/dforge-mcp-author/references/${f}.md
202
- done
203
-
204
- # simple-todo example
205
- mkdir -p ~/.claude/skills/dforge-mcp-author/examples/simple-todo/{entities,logic/actions,ui,security,seed-data}
206
- for f in README.md manifest.json; do
207
- curl -fsSL "$BASE/examples/simple-todo/$f" \
208
- -o ~/.claude/skills/dforge-mcp-author/examples/simple-todo/$f
209
- done
210
- # … entity, ui, security, seed-data files follow the same pattern
211
-
212
- # Or, straight from GitHub main (always fresh, but pre-release content):
213
- # curl -fsSL https://raw.githubusercontent.com/dforge-core/dforge-mcp/main/skills/dforge-mcp-author/SKILL.md \
214
- # -o ~/.claude/skills/dforge-mcp-author/SKILL.md
236
+ DEST=/path/to/skills npm run install-skills
215
237
  ```
216
238
 
217
- > **Note on CLAUDE.md:** Every module gets a `CLAUDE.md` in its root, authored during Phase 0a by the `dforge-mcp-author` skill (the AI drafts it; you write it) and kept current as later phases complete. It tells Claude Code that the directory is a dForge module, instructs it to use the `dforge-mcp-author` skill, describes the module layout, and carries a live **Module status** tracker (which phase is done) so future sessions resume accurately.
239
+ <details>
240
+ <summary>Windows notes</summary>
241
+
242
+ - **cmd.exe / PowerShell**: `npm run install-skills` works directly. There is no bash requirement.
243
+ - **WSL**: run it from *Windows*, not inside WSL — inside WSL, `os.homedir()` is the Linux home (`/home/you`), so the skills would land where a Windows Claude Code never looks. If you deliberately run Claude Code inside WSL, then installing from WSL is correct.
244
+ - `--from-npm` shells out to `tar`, built into Windows 10 1803+ (and macOS/Linux). Without it, clone the repo and run the installer without `--from-npm`.
245
+ - `scripts/install-skills.sh` still exists and works (it just execs the Node script), so the older `curl … | bash` one-liner keeps working on macOS/Linux/Git Bash.
246
+
247
+ </details>
218
248
 
219
- Re-run after every dforge-mcp upgrade — the skill version isn't checked at runtime, so a stale skill against new tools will misroute calls.
249
+ It replaces each skill directory wholesale — a stale reference file left behind is worse than a missing one, because the agent will happily author against it.
250
+
251
+ **Re-run after every dforge-mcp upgrade.** The skill version isn't checked at runtime, so stale skills against new tools will misroute calls.
252
+
253
+ > **Note on CLAUDE.md:** Every module gets a `CLAUDE.md` in its root, authored during Phase 0a (the AI drafts it; you write it) and kept current as later phases complete. It tells Claude Code that the directory is a dForge module, points at the skills, describes the module layout, and carries a live **Module status** tracker so future sessions resume accurately.
220
254
 
221
255
  The phases:
222
256
 
223
- | Phase | Required? | Tools used |
224
- |---|---|---|
225
- | 0. Identity / requirements / design / validation | yes | (docs authored directly) |
226
- | 1. Domain | yes | `module_create`, `entity_add`, `entity_field_*` |
227
- | 2. Actions | optional | `action_add` |
228
- | 3. Views + Reports | views yes, reports optional | `view_*`, `report_add`, `setting_add` |
229
- | 4. Polish (translations, seed) | optional | (file map authored directly) |
230
- | 5. Security | roles required, folders optional | `role_add`, `role_right_set`, `folder_add` |
231
- | 6. Verify | yes | `module_pack`, `module_install` |
232
-
233
- Key principles encoded in the Skill: inspect-before-patch, one-at-a-time, deterministic backtrack on earliest-phase-first rule, tool-failure protocol that distinguishes auth/connectivity from module defects, end-of-session cleanup user-driven.
257
+ | Phase | Required? | Skill | Tools used |
258
+ |---|---|---|---|
259
+ | 0. Identity / requirements / design / validation | yes | design | `module_plan` |
260
+ | 1. Domain | yes | build | `module_create`, `entity_add`, `entity_field_*`, `entity_reference_add`, `entity_rollup_add`, `entity_status_add`, `module_import`, `dbml_import` |
261
+ | 2. Behavior | optional | build | `action_add`, `action_check`, `trigger_add`, `job_add`, `webhook_add` |
262
+ | 3. Views + menus, reports | 3a yes, rest optional | build | `view_*`, `menu_add`, `report_add` |
263
+ | 4. Polish | optional | build | `setting_add`, `translation_sync`, `seed_add` |
264
+ | 5. Security | 5a yes, folders optional | build | `role_add`, `role_right_set`, `folder_add` |
265
+ | 6. Verify | yes | ship | `module_validate`, `module_pack`, `module_install` |
266
+
267
+ Key principles encoded in the skills: inspect-before-patch, one-at-a-time, composite-tools-first, deterministic backtrack on the earliest-phase-first rule, a tool-failure protocol that distinguishes auth/connectivity from module defects, and user-driven end-of-session cleanup.
234
268
 
235
269
  ## For maintainers
236
270