@dforge-core/dforge-mcp 0.1.0-rc.3 → 0.1.0-rc.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dforge-core/dforge-mcp",
3
- "version": "0.1.0-rc.3",
3
+ "version": "0.1.0-rc.4",
4
4
  "description": "MCP server for dForge module authoring. Exposes scaffold/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/iash44/dForge-core",
@@ -27,7 +27,7 @@
27
27
  "typecheck": "tsc --noEmit"
28
28
  },
29
29
  "dependencies": {
30
- "@dforge-core/dforge-cli": "^0.1.0",
30
+ "@dforge-core/dforge-cli": "^0.1.2",
31
31
  "@modelcontextprotocol/sdk": "^1.29.0",
32
32
  "zod": "^4.4.3"
33
33
  },
@@ -33,8 +33,7 @@
33
33
  "description": "SQL SELECT statement for view-backed entities (isView=true). Executed as CREATE OR REPLACE VIEW during module install."
34
34
  },
35
35
  "toString": {
36
- "type": "string",
37
- "description": "Display pattern using column placeholders, e.g. \"{first_name} {last_name}\""
36
+ "description": "Display pattern using column placeholders, e.g. \"{first_name} {last_name}\". Optional in extension entities (with `extends`). No `type` constraint here because `toString` collides with Object.prototype.toString in JSON validators — they read the inherited function and reject it, even when the property is absent from the JSON."
38
37
  },
39
38
  "traits": {
40
39
  "type": "array",
@@ -2,15 +2,11 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://dforge.dev/schemas/folders.schema.json",
4
4
  "title": "dForge Folders",
5
- "description": "Folder definitions for a dForge module",
6
- "type": "object",
7
- "additionalProperties": {
8
- "$ref": "#/$defs/folder"
9
- },
5
+ "description": "Folder tree for a dForge module. The file IS the root folder (not a map keyed by code). Sub-folders live under `children`, keyed by folder code.",
6
+ "$ref": "#/$defs/folder",
10
7
  "$defs": {
11
8
  "folder": {
12
9
  "type": "object",
13
- "required": ["label"],
14
10
  "properties": {
15
11
  "label": {
16
12
  "type": "string",
@@ -18,19 +14,19 @@
18
14
  },
19
15
  "description": {
20
16
  "type": "string",
21
- "description": "Optional description text (shown in breadcrumb/header area)"
17
+ "description": "Optional description text (shown in breadcrumb / header area)"
22
18
  },
23
19
  "parentCode": {
24
20
  "type": ["string", "null"],
25
- "description": "Parent folder code (null for root folders)"
21
+ "description": "Parent folder code. Usually omitted: nested folders go under `children` of their parent. Only used by sparse-tree imports."
26
22
  },
27
23
  "folderPath": {
28
24
  "type": "string",
29
- "description": "URL-friendly folder path"
25
+ "description": "URL-friendly folder path override"
30
26
  },
31
27
  "color": {
32
28
  "type": "string",
33
- "description": "Folder accent color (hex)"
29
+ "description": "Folder accent color (hex, e.g. '#2196F3')"
34
30
  },
35
31
  "icon": {
36
32
  "type": "string",
@@ -38,14 +34,21 @@
38
34
  },
39
35
  "inheritSecurity": {
40
36
  "type": "boolean",
41
- "description": "Whether to inherit security from parent folder"
37
+ "description": "Whether to inherit security rights from the parent folder. Default: true."
42
38
  },
43
39
  "entities": {
44
40
  "type": "object",
45
- "description": "Entity code → folder-entity config",
41
+ "description": "Entity code → folder-entity binding (which view to show, quick-add, row-level filter).",
46
42
  "additionalProperties": {
47
43
  "$ref": "#/$defs/folderEntity"
48
44
  }
45
+ },
46
+ "children": {
47
+ "type": "object",
48
+ "description": "Nested folders keyed by sub-folder code. Recursive — children can have their own children.",
49
+ "additionalProperties": {
50
+ "$ref": "#/$defs/folder"
51
+ }
49
52
  }
50
53
  },
51
54
  "additionalProperties": false
@@ -55,14 +58,48 @@
55
58
  "properties": {
56
59
  "viewName": {
57
60
  "type": "string",
58
- "description": "Entity view name to use in this folder"
61
+ "description": "Entity view name (data view code) to use in this folder. Common values include 'default'."
59
62
  },
60
63
  "quickAdd": {
61
64
  "type": "boolean",
62
- "description": "Enable quick-add for this entity in this folder"
65
+ "description": "Enable the quick-add (+) button for this entity in this folder"
63
66
  },
64
67
  "rowFilter": {
65
- "description": "Row-level filter criteria for this entity in this folder"
68
+ "description": "Row-level filter. Either a SQL expression string (e.g. \"is_customer = true\") or a canonical filter object (`{c,o,v}` for one condition, `{g,i:[]}` for a group).",
69
+ "oneOf": [
70
+ { "type": "string" },
71
+ { "$ref": "#/$defs/filter" }
72
+ ]
73
+ }
74
+ },
75
+ "additionalProperties": false
76
+ },
77
+
78
+ "filter": {
79
+ "description": "Canonical dForge filter expression — same shape as in data_views.json. Either `{c, o, v}` or `{g, i: [...]}`.",
80
+ "oneOf": [
81
+ { "$ref": "#/$defs/filterCondition" },
82
+ { "$ref": "#/$defs/filterGroup" }
83
+ ]
84
+ },
85
+ "filterCondition": {
86
+ "type": "object",
87
+ "required": ["c", "o"],
88
+ "properties": {
89
+ "c": { "type": "string" },
90
+ "o": { "type": "string" },
91
+ "v": {}
92
+ },
93
+ "additionalProperties": false
94
+ },
95
+ "filterGroup": {
96
+ "type": "object",
97
+ "required": ["g", "i"],
98
+ "properties": {
99
+ "g": { "type": "string", "enum": ["and", "or"] },
100
+ "i": {
101
+ "type": "array",
102
+ "items": { "$ref": "#/$defs/filter" }
66
103
  }
67
104
  },
68
105
  "additionalProperties": false
@@ -21,6 +21,12 @@
21
21
  "pattern": "^[a-z][a-z0-9_-]*$",
22
22
  "description": "Human-readable module code (becomes schema name and module_cd in DB)"
23
23
  },
24
+ "kind": {
25
+ "type": "string",
26
+ "enum": ["module", "pack"],
27
+ "default": "module",
28
+ "description": "Package kind. 'module' (default) — a regular module with its own entities/views/actions. 'pack' — a dependency bundle: installs a curated set of other modules via `dependencies` in order, owns no entities itself."
29
+ },
24
30
  "version": {
25
31
  "type": "string",
26
32
  "pattern": "^\\d+\\.\\d+\\.\\d+",
@@ -68,12 +74,37 @@
68
74
  },
69
75
  "dependencies": {
70
76
  "type": "object",
71
- "description": "Module dependencies: module code → semver range",
77
+ "description": "Module dependencies: module code → semver range OR { version, entities? } object for partial deps that only import specific entities from the dependency.",
72
78
  "additionalProperties": {
73
- "type": "string",
74
- "description": "Semver version range (e.g. '>=1.0.0')"
79
+ "oneOf": [
80
+ {
81
+ "type": "string",
82
+ "description": "Semver version range (e.g. '>=1.0.0')"
83
+ },
84
+ {
85
+ "type": "object",
86
+ "required": ["version"],
87
+ "properties": {
88
+ "version": {
89
+ "type": "string",
90
+ "description": "Semver version range"
91
+ },
92
+ "entities": {
93
+ "type": "array",
94
+ "items": { "type": "string" },
95
+ "description": "Entity codes from the dependency that this module needs. If omitted, all entities are accessible."
96
+ }
97
+ },
98
+ "additionalProperties": false
99
+ }
100
+ ]
75
101
  }
76
102
  },
103
+ "auditHistory": {
104
+ "type": "string",
105
+ "enum": ["none", "minimal", "full"],
106
+ "description": "Default audit history mode for entities in this module ('full' captures every field change; 'minimal' captures only inserts/deletes; 'none' disables). Individual entities can override."
107
+ },
77
108
  "entities": {
78
109
  "type": "object",
79
110
  "description": "Entity definitions and extensions: entity code → relative path to entity JSON file. Dotted keys (e.g. 'fin.invoice') indicate extensions of other modules' entities (the entity file must have an 'extends' property).",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "menuItem": {
39
39
  "type": "object",
40
- "required": ["orderNum", "label"],
40
+ "required": ["label"],
41
41
  "properties": {
42
42
  "itemType": {
43
43
  "type": ["string", "null"],
@@ -159,10 +159,27 @@
159
159
  "description": "Field type code (see settings.schema.json for the common list)"
160
160
  },
161
161
  "label": { "type": "string" },
162
- "required": { "type": "boolean", "default": false },
162
+ "required": {
163
+ "type": "boolean",
164
+ "default": false,
165
+ "description": "Whether the user must provide a value (older modules also use `isRequired`)"
166
+ },
167
+ "isRequired": {
168
+ "type": "boolean",
169
+ "description": "Alternate spelling of `required` used by some modules. Prefer `required` for new code."
170
+ },
163
171
  "default": {
164
172
  "description": "Default value applied if the user doesn't provide one"
165
173
  },
174
+ "link": {
175
+ "type": "object",
176
+ "description": "Entity link for lookup-type params (alternative to nesting under `params.link`). Shape: { entity: '<code>', otherKey?: '<column>' }.",
177
+ "properties": {
178
+ "entity": { "type": "string" },
179
+ "otherKey": { "type": "string" }
180
+ },
181
+ "additionalProperties": false
182
+ },
166
183
  "params": {
167
184
  "type": "object",
168
185
  "description": "Field-type-specific params (e.g. lookup → { link: { entity, otherKey } })"
@@ -1,116 +1,227 @@
1
1
  ---
2
2
  name: dforge-mcp-author
3
- description: Author dForge modules using the dforge-mcp tool surface. Use when the user has the @dforge-core/dforge-mcp MCP server connected and asks to scaffold, extend, pack, or install a dForge module. Replaces hand-writing JSON files with structured tool calls that produce schema-valid output on the first try.
3
+ description: Co-pilot for authoring dForge modules via the dforge-mcp tool surface. Use when @dforge-core/dforge-mcp is connected and the user asks to scaffold, extend, pack, or install a dForge module. Drafts and proposes; the user approves at named gates. Walks the user through six phases with explicit gates for confirmation, a deterministic backtrack protocol, a tool-failure protocol, and resume-from-partial-state support.
4
4
  ---
5
5
 
6
- # dForge Module Author (MCP-driven)
6
+ # dForge Module Author — Co-pilot
7
7
 
8
- You're authoring a dForge module via the `dforge-mcp` MCP server. dForge is a metadata-driven, multi-tenant business platform modules are JSON metadata + DSL scripts.
8
+ You are a co-pilot: you **draft**, **propose**, and **call tools**; the **user approves** at named gates. Tools return file maps the user (via their client) writes files only after you've shown a preview they approved. Never write without confirmation.
9
9
 
10
- **Your edge here** is that you have *tools*, not just text generation. Always prefer a tool call over writing a file by hand. Every tool either returns a file map (so the user can preview before commit) or shells out to the validated native CLI.
10
+ ## Tool reference
11
11
 
12
- ---
13
-
14
- ## Tool surface
12
+ The phase column below indicates the **typical** use. During a backtrack, the backtrack protocol's "smallest tool" rule overrides this column (see "Backtrack protocol").
15
13
 
16
- | Tool | What it does | Returns |
14
+ | Tool | Typical phase | What it does |
17
15
  |---|---|---|
18
- | `dforge_module_create` | Build a brand-new module's file map | `{ files: {...} }` client writes |
19
- | `dforge_entity_add` | Add an entity to an existing module | `{ files: {...}, warning?: ... }` — client writes |
20
- | `dforge_module_pack` | Pack a module dir into a `.dforge` tarball | `{ tarballPath, sizeBytes }` (writes the tarball) |
21
- | `dforge_module_install` | Install a module to a tenant | `{ ok, output }` (live action) |
22
- | `dforge_dbml_import` | Generate from DBML (stub) | not yet implemented |
23
-
24
- ## Resources
25
-
26
- Read these *before* generating any module content so your output matches the canonical schema. Don't memorise — load each on demand when you're about to write that kind of file.
27
-
28
- | URI | What it covers |
29
- |---|---|
30
- | `dforge://schema/manifest` | manifest.json required fields, semver, dependencies, `entities` map, `tags` |
31
- | `dforge://schema/entity` | entities/*.json `description`, `dbObject`, `toString`, `traits`, `fields` |
32
- | `dforge://schema/data-views` | ui/data_views.json `viewType` enum, `dataSources[]` (with per-source `filter` + `order`), `viewConfig` per view type, the canonical `filter` shape (`{c,o,v}` or `{g,i:[]}`) |
33
- | `dforge://schema/folders` | ui/folders.json folder tree, per-entity view bindings, icon, color |
34
- | `dforge://schema/menus` | ui/menus.json menus + items (with nested `children` for sections), `itemType: V/D/R/null` |
35
- | `dforge://schema/roles` | security/roles.json — role → entity → rights string (`SIUDC` or `E`) |
36
- | `dforge://schema/jobs` | logic/jobs.json cron + action binding for the scheduler |
37
- | `dforge://schema/seed-data` | seed-data/*.json — initial rows inserted at install |
38
- | `dforge://schema/settings` | settings.json `fieldTypeCd`, `defaultValue`/`formula`, `params` per setting |
39
- | `dforge://schema/reports` | ui/reports.json `layout.panels[]`, `datasets` (Q/S types), filter reuse |
40
- | `dforge://schema/traits` | reference for entity trait codes |
41
- | `dforge://schema/webhooks` | ui/webhooks.json outbound webhooks |
42
- | `dforge://docs/conventions` | naming, FK+Reference pattern, traits cheat sheet, security model |
16
+ | `dforge_module_inspect` | any | Read current module state. **Read-only** its output does NOT require user confirmation to view; you summarize it and continue. |
17
+ | `dforge_module_create` | 1 | Scaffold a new module (returns file map; user writes) |
18
+ | `dforge_entity_add` | 1 | Add a whole entity to an existing module |
19
+ | `dforge_entity_field_add` | 1 | Patch one field onto an existing entity |
20
+ | `dforge_entity_field_modify` | 1 | Replace one field's spec |
21
+ | `dforge_entity_field_remove` | 1 | Drop one field (warns about dependents) |
22
+ | `dforge_action_add` | 2 | DSL action + ui/actions.json entry |
23
+ | `dforge_view_add` | 3 | Data view in ui/data_views.json |
24
+ | `dforge_view_modify` | 3 | Replace a view's spec |
25
+ | `dforge_report_add` | 3 | Report in ui/reports.json |
26
+ | `dforge_setting_add` | 4 | Configurable module setting |
27
+ | `dforge_role_add` | 5 | Role with rights matrix |
28
+ | `dforge_role_right_set` | 5 | Grant / revoke one right on one object |
29
+ | `dforge_folder_add` | 5 | Security folder (optional) |
30
+ | `dforge_dependency_add` | any | Add a dep on another module |
31
+ | `dforge_module_pack` | 6 | Produce .dforge tarball (needs dforge-cli on PATH) |
32
+ | `dforge_module_install` | 6 | Install to tenant the real validator |
33
+
34
+ ## Resources to load once per session
35
+
36
+ - `dforge://docs/conventions` — naming, FK+Reference pattern, traits, security model
37
+ - `dforge://schema/manifest`, `entity`, `data-views`, `folders`, `menus`, `roles`, `reports`, `settings`, `jobs`, `seed-data` consult before emitting each file kind
38
+
39
+ **If a resource fails to load, halt and notify the user.** Do not invent conventions or schemas from memory.
43
40
 
44
- ---
41
+ ## Hard rules
45
42
 
46
- ## Standard workflow
43
+ These are absolute. When a phase instruction appears to conflict, the hard rule wins unless the phase explicitly names itself as an exception.
47
44
 
48
- ### 1. Gather requirements (one short turn)
45
+ 1. **Co-pilot stance.** Draft propose → user approves → tool call → file write. Never write without confirmation.
46
+ 2. **Inspect before patching.** Run `dforge_module_inspect` at session start and after every backtrack. Inspect output is read-only; show a summary and continue without asking confirmation for the inspect itself — confirmation applies only to **write** tools.
47
+ 3. **One entity / view / role / action / report at a time** when proposing. Never batch these. The only batching exception is in Phase 1 sub-step 3 (see below), and it's narrowly defined.
48
+ 4. **Tabs in JSON, trailing newline** — tools already emit this; don't reformat.
49
+ 5. **Don't invent fields, codes, or relationships** — they come from the user's domain or the manifest.
49
50
 
50
- Ask the user, in order:
51
- 1. **What's the module for?** One sentence is enough.
52
- 2. **What's the module code?** Lowercase, hyphen-or-underscore, e.g. `crm`, `pm`, `hr-admin`.
53
- 3. **What entities does it own?** Rough list with one-line descriptions each.
54
- 4. **Greenfield or extending an existing module?** If extending, ask which.
51
+ ## Tool failure protocol
55
52
 
56
- Don't ask about field types, view layouts, or DSL actions yet — those come *after* a working skeleton.
53
+ If any MCP tool returns an error at any time:
57
54
 
58
- ### 2. Read the schemas + conventions
55
+ 1. **Surface the raw error verbatim** to the user. Do not paraphrase.
56
+ 2. **Do not attempt a workaround** with a different tool or hand-crafted JSON.
57
+ 3. **Ask the user to resolve the tool-level issue** (missing dep, bad path, schema validation, etc.) before continuing.
58
+ 4. **Do not advance the phase** until the failing tool succeeds.
59
59
 
60
- Pull `dforge://schema/manifest`, `dforge://schema/entity`, and `dforge://docs/conventions` into context. Skim, don't memorise.
60
+ Two specific tool errors have known causes worth distinguishing:
61
61
 
62
- ### 3. Call `dforge_module_create`
62
+ - **`dforge_module_pack` / `_install` reports "command not found" or PATH error**: dforge-cli isn't installed. Tell the user: "dforge-cli is not on your PATH. Install with `npm install -g @dforge-core/dforge-cli`, then re-run. Do not continue Phase 6 until resolved."
63
+ - **`dforge_module_install` reports HTTP 401/403 or connection refused**: this is auth/connectivity, NOT a module defect. Tell the user: "This appears to be a credentials or connectivity issue, not a module defect. Verify `DFORGE_URL` and `DFORGE_TOKEN` before re-running install." Do not backtrack to earlier phases.
63
64
 
64
- Pass:
65
- - `code`, `displayName`, `description` from the user's answers
66
- - `entities`: array of `{ name, label, traits }` — default traits to `"identity+audit"`
67
- - `preset`: `"minimal"` unless the user wants full template (settings/translations/seed)
68
- - `dependencies`: usually `["admin", "metadata"]` — both are required for typical modules
65
+ ## Resume-from-partial-state
69
66
 
70
- You'll get back `{ summary, files: { "<relPath>": "<contents>", ... } }`.
67
+ At every session start, call `dforge_module_inspect` on the module dir (if it exists).
71
68
 
72
- ### 4. Preview the file map with the user
69
+ - If the dir doesn't exist or has no `manifest.json` start fresh from Phase 0.
70
+ - If it does exist:
71
+ 1. Read `_brief/00-intake.md` and `_brief/changelog.md` if present.
72
+ 2. Cross-reference the inspect output against the brief to infer the last completed phase (e.g. entities exist + views exist + roles missing → last completed = Phase 3).
73
+ 3. Summarize: "Found module `<code>` v`<version>`. Looks like Phase N was the last completed phase. Resume from Phase N+1, or revisit an earlier phase?"
74
+ 4. Wait for the user's answer before proceeding.
73
75
 
74
- Show the file list (paths only) + the manifest contents. Ask "write these to `./<code>`?". Don't write without confirmation.
76
+ ## Phase 0 Intake (required, ~1 turn)
75
77
 
76
- ### 5. Write the files
78
+ **Preconditions:** none.
77
79
 
78
- Use your filesystem tool (Write / bash heredoc / fs.writeFileSync, whatever your client offers). Each value in `files` is the literal file contents including JSON indentation. Don't re-format.
80
+ **Action:** Ask the **four** questions below in a **single message**. Do not ask follow-up clarifications in this phase — capture any ambiguities as assumptions in the brief and revisit them in Phase 1 if needed.
79
81
 
80
- ### 6. Iterate
82
+ 1. One-sentence purpose ("what does this module do?")
83
+ 2. Target user roles (e.g. "sales reps + sales managers"). If only one, security stays trivial.
84
+ 3. Existing dForge modules to depend on? (`admin` + `metadata` are implicit.)
85
+ 4. Language scope. Default English-only; ask only if the user mentions other locales.
81
86
 
82
- Use `dforge_entity_add` to add more entities incrementally it reads the existing manifest, regenerates the dependent UI/security files, and returns ONLY the files that change. Re-preview each time before writing.
87
+ **Write:** `_brief/00-intake.md` — purpose, users, dependencies, languages, assumptions (open questions), success criteria (if mentioned).
83
88
 
84
- For fields, ref columns, actions, formulas, settings, reports write those directly into the entity JSON / new files under `logic/`, `ui/`, etc. The schemas are your guide; the tools don't (yet) cover field-level changes.
89
+ **Gate:** Show the brief, ask "Captures intent? Anything to fix?". Proceed on confirmation.
85
90
 
86
- ### 7. Pack + install
91
+ ## Phase 1 Domain (required)
87
92
 
88
- Once the module is shaped right:
89
- 1. `dforge_module_pack` with `moduleDir: "./<code>"` → returns the tarball path.
90
- 2. (Optional) `dforge_module_install` with `pathOrTarball: <tarballPath or moduleDir>` and a `tenantUrl` + `token` (or rely on `DFORGE_URL`/`DFORGE_TOKEN` env). This runs the *full* server-side validator — the only validator available. Surface its output verbatim; if it fails, fix and re-pack.
93
+ **Preconditions:** intake brief written.
91
94
 
92
- ---
95
+ **Sub-steps:**
93
96
 
94
- ## Hard rules
97
+ 1. **Propose the entity inventory** (list of names + one-line descriptions). Get user sign-off. Write to `_brief/01-domain.md`.
98
+ 2. **Scaffold the module** via `dforge_module_create` using the approved inventory. Preview the file map, get approval, then user writes.
99
+ 3. **Per-entity loop.** For each entity, propose fields + traits + references. **Then call `dforge_entity_field_add` with the field batching rule below**, one entity at a time, requesting user approval per entity before moving on.
100
+ 4. **Extension entities last.** If extending another module's entity, use `extends: "module.entity"`, `toString: null` (inherits base), and a dotted manifest key. **Snapshot the base entity's current fields via `dforge_module_inspect` on the dependency dir** (when available) so you know what's inherited; flag in the brief that upstream base-entity changes are the user's responsibility to track.
95
101
 
96
- - **Always preview file maps before writing.** Tools return the user decides.
97
- - **Use `dforge_entity_add`, not regenerate-from-scratch.** It preserves the existing manifest's UUID, version, dependencies, etc.
98
- - **Tabs in JSON.** All emitted files use `\t` indentation. Don't re-pretty-print with spaces.
99
- - **Don't invent `code` or `moduleId`.** `code` comes from the user; `moduleId` is auto-generated by the tool (UUID). Never hand-write a UUID.
100
- - **Refer to the conventions doc for FK+Reference, traits, flags.** The MCP server doesn't enforce these — your output does. The first install will catch violations, but a clean first install is the goal.
102
+ **Field batching rule** (the only Phase-1 exception to the hard rule):
101
103
 
102
- ---
104
+ A field is **batchable** only if ALL of these are true: scalar primitive (string / integer / decimal / boolean / date), no FK or Reference, no `formula`, and the nullability is unambiguous (e.g. required-not-null per intake context). Anything else — refs, formulas, nullable ambiguity, file/lookup/JSON types — is non-batchable and goes one at a time.
103
105
 
104
- ## When NOT to use the tools
106
+ **Exit criteria:** every entity has at least PK + audit traits + 1 user-visible field; FK references resolve; manifest's `entities` map reflects reality.
105
107
 
106
- - **Modifying a single existing field in an entity JSON.** Just edit the file.
107
- - **Writing an action DSL script.** No tool for that — write to `logic/actions/<name>.dsl` directly. Reference the `dforge://docs/conventions` doc for DSL syntax.
108
- - **Querying live tenant state.** No tool for that either — shell out to `dforge-cli` via your bash tool if the user has it installed.
108
+ ## Phase 2 Actions (optional, skip-able)
109
109
 
110
- ---
110
+ **Preconditions:** Phase 1 complete.
111
+
112
+ Skip entirely if the module is pure CRUD. Do **not** fabricate actions to fill the phase.
113
+
114
+ When the user has a real business operation: read the DSL reference section of `dforge://docs/conventions`, then call `dforge_action_add` per action — one at a time — with the full DSL body.
115
+
116
+ **Exit criteria:** every action you added is intended, named, and has params/canExecute/execute blocks. Compilation is validated in Phase 6.
117
+
118
+ ## Phase 3 — Views (required) + Reports (optional)
119
+
120
+ **Preconditions:** Phase 1 complete.
121
+
122
+ ### 3a. Default grids (required, do FIRST)
123
+
124
+ For every entity in the manifest, call `dforge_view_add` with `viewType: "grid"` and `dataSources: [{ entityCode: <entity>, columns: [...] }]`. Use `viewName: "default"` for the first grid per entity — this is **mandatory**; the platform looks for it.
125
+
126
+ **Do not propose any specialized view until every entity has its default grid.**
127
+
128
+ ### 3b. Specialized views (optional, only after 3a complete)
129
+
130
+ Propose a specialized view (kanban / calendar / list-with-levels / tree-grid / master-detail) **only when one of these objective triggers fires**:
131
+
132
+ - The user explicitly mentioned the visualization ("show leads as a kanban", "we need a calendar for tasks").
133
+ - The entity has a status / stage / kind field with **3 or more discrete values** in a dropdown — kanban candidate.
134
+ - The entity has a required date/time field intended for scheduling — calendar candidate.
135
+ - The entity self-references (parent FK to itself) — tree-grid candidate.
136
+ - The entity has a 1:N detail child with `parentSetField` declared — list-with-levels or master-detail candidate.
137
+
138
+ If none of these fire, skip specialized views for that entity. Read `dforge://schema/data-views` for the `viewConfig` shape of the type you're proposing before calling `dforge_view_add`.
139
+
140
+ ### 3c. Reports (optional)
141
+
142
+ Add reports only when management aggregation/grouping isn't covered by views. `dforge_report_add` with layout + datasets (Query type with entityCd + filter + sort, or Stored Procedure). Pull `dforge://schema/reports` first.
143
+
144
+ **Exit criteria:** every entity has a default grid; every specialized view has a stated trigger; reports cover the stated reporting use cases.
145
+
146
+ ## Phase 4 — Polish: settings, translations, seed (mostly optional)
147
+
148
+ **Preconditions:** Phase 3 complete.
149
+
150
+ - **Settings**: `dforge_setting_add` per configurable value the user requested.
151
+ - **Translations** (required if intake declared non-English locales): files under `translations/<locale>.json`. **If the user defers translation authoring, append to `_brief/changelog.md`: "Translation files for [locales] are incomplete. Phase 6 install will fail translation completeness validation until added." Remind the user before calling `dforge_module_pack`.**
152
+ - **Seed data**: only when the module needs reference data on install.
153
+
154
+ **Exit criteria:** any configurable value the user requested is exposed as a setting; if non-English locales were declared, translations exist OR the deferral warning is logged.
155
+
156
+ ## Phase 5 — Security
157
+
158
+ **Preconditions:** Phases 1, 3 complete (you need entity/view/action/report codes to grant rights on).
159
+
160
+ ### 5a. Roles + rights matrix (required)
161
+
162
+ 1. Inventory roles. Default for simple modules: one `<code>.admin` role with full rights on everything. If intake mentioned multiple user groups, propose one role per group.
163
+ 2. Show the rights matrix as a table (rows = entities/actions/reports, columns = roles, cells = rights string). Get user sign-off.
164
+ 3. Call `dforge_role_add` per role. Use `dforge_role_right_set` for one-off edits.
165
+
166
+ **Rights semantics** (additive — multiple roles UNION, never revoke):
167
+ - Entities: any subset of `SIUDC` (Select / Insert / Update / Delete / Clone)
168
+ - Actions / reports: `E` (Execute), or omit to deny
169
+
170
+ ### 5b. Security folders (optional)
171
+
172
+ Only if intake said data must be partitioned per folder (multi-warehouse, multi-region, multi-tenant-like). Default: root only.
173
+
174
+ If needed: `dforge_folder_add` per sub-folder, passing `entities` with `rowFilter` (SQL string OR canonical `{c,o,v}` / `{g,i:[]}` filter).
175
+
176
+ **Exit criteria:** every entity is reachable to at least one role; if folders declared, every folder has security mapped.
177
+
178
+ ## Phase 6 — Verify (required, non-skippable)
179
+
180
+ **Preconditions:** all **required** prior phases complete. Optional phases (2, 3c reports, 4 settings/seed, 5b folders) are not preconditions — explicitly skipped optional phases do not block Phase 6.
181
+
182
+ **Steps:**
183
+
184
+ 1. `dforge_module_pack` → produces `.dforge` tarball.
185
+ 2. `dforge_module_install` with `DFORGE_URL` / `DFORGE_TOKEN`. Runs the full server-side validator.
186
+ 3. **If install fails on a module defect** (schema, FK, missing translation, etc.), the error message tells you which phase to backtrack to. Use the backtrack protocol.
187
+ 4. **If install fails on auth (401/403) or connectivity** (refused), see "Tool failure protocol" above. Do not backtrack — fix credentials.
188
+
189
+ **Exit criteria:** install exits 0 against a real tenant.
190
+
191
+ ## Backtrack protocol
192
+
193
+ When a later phase exposes a problem in an earlier phase, follow steps 1–6 IN ORDER:
194
+
195
+ **Multi-trigger rule (deterministic):** If multiple phases simultaneously expose gaps in earlier phases (e.g. Phase 3 needs a field; Phase 5 needs an action), resolve the **earliest-phase gap first**, complete its full backtrack, run `dforge_module_inspect`, then evaluate remaining gaps.
196
+
197
+ 1. **Stop the current phase.** Don't paper over or improvise.
198
+ 2. **Name the issue precisely.** "Phase 3 wants a kanban grouped by `lead_status`, but Phase 1 didn't define `lead_status` on entity `lead`."
199
+ 3. **Identify the target phase + decision.** "Backtrack to Phase 1: add field `lead_status` to entity `lead`."
200
+ 4. **Get user sign-off.** Describe the change including any cascading impacts.
201
+ 5. **Apply the smallest tool that fits.** This rule overrides the "typical phase" labels in the tool reference table. Prefer `entity_field_add` over `entity_add`; `role_right_set` over `role_add`; `view_modify` over `view_add` + remove.
202
+ 6. **Run `dforge_module_inspect` again** to surface knock-on impacts. Fix in order. Resume the original phase.
203
+
204
+ **Entity rename or deletion specifically requires cascade discovery:**
205
+
206
+ Before applying:
207
+ 1. Run `dforge_module_inspect` to enumerate every reference: views' `dataSources.entityCode`, role `rights` keys, action `entity`, report dataset `entityCd`, seed-data files, formula/DSL bodies.
208
+ 2. List every affected artifact to the user. Require explicit confirmation.
209
+ 3. Apply in **reverse dependency order**: roles → reports → views → actions → entity itself.
210
+ 4. Re-inspect; verify no dangling references remain.
211
+
212
+ **After every backtrack** append to `_brief/changelog.md`:
213
+
214
+ ```markdown
215
+ ## <YYYY-MM-DD> — Phase N → Phase M backtrack
216
+ - Trigger: <what later phase tried to do>
217
+ - Change: <what was patched>
218
+ - Affected files: <list>
219
+ ```
220
+
221
+ ## Final hygiene
222
+
223
+ After Phase 6 install succeeds:
111
224
 
112
- ## Sanity check before declaring done
225
+ **Ask the user**: "Delete `_brief/` (session scratch) or move it to `docs/` for committed design rationale?". Wait for their answer; do not act unilaterally.
113
226
 
114
- - `dforge_module_pack` succeeded archive size is non-trivial (>100 KB usually)
115
- - `dforge_module_install --code <tenant>` exited 0 → server validated everything
116
- - The user confirms the install log looks right (entities created, no warnings about missing translations / orphan rights)
227
+ Suggest a `git commit` summarizing the module. Do not commit unless the user asks.