@dforge-core/dforge-mcp 0.2.44 → 0.2.46-next
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 +73 -0
- package/README.md +3 -2
- package/dist/server.js +109 -84
- package/package.json +48 -46
- package/resources/docs/conventions.md +17 -14
- package/resources/docs/dsl-reference.md +15 -5
- package/resources/schemas/entity.schema.json +1 -1
- package/resources/schemas/registers.schema.json +8 -3
- package/scripts/install-skills.mjs +1 -0
- package/skills/dforge-1c-import/SKILL.md +374 -0
- package/skills/dforge-1c-import/references/environment.md +97 -0
- package/skills/dforge-1c-import/references/loading.md +161 -0
- package/skills/dforge-1c-import/references/measurements.md +210 -0
- package/skills/dforge-1c-import/references/print-forms.md +148 -0
- package/skills/dforge-1c-import/scripts/1c.py +178 -0
- package/skills/dforge-1c-import/scripts/README.md +110 -0
- package/skills/dforge-1c-import/scripts/lib/analyze.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/attrs.py +278 -0
- package/skills/dforge-1c-import/scripts/lib/autospec.py +964 -0
- package/skills/dforge-1c-import/scripts/lib/binding.py +257 -0
- package/skills/dforge-1c-import/scripts/lib/bsl.py +811 -0
- package/skills/dforge-1c-import/scripts/lib/callgraph.py +717 -0
- package/skills/dforge-1c-import/scripts/lib/cli.py +53 -0
- package/skills/dforge-1c-import/scripts/lib/columnmap.py +346 -0
- package/skills/dforge-1c-import/scripts/lib/coverage.py +242 -0
- package/skills/dforge-1c-import/scripts/lib/custom.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/delta.py +741 -0
- package/skills/dforge-1c-import/scripts/lib/deps.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/docusage.py +105 -0
- package/skills/dforge-1c-import/scripts/lib/dump1c.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/explore.py +55 -0
- package/skills/dforge-1c-import/scripts/lib/external.py +201 -0
- package/skills/dforge-1c-import/scripts/lib/extract_code.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/forms.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/genmodule.py +1432 -0
- package/skills/dforge-1c-import/scripts/lib/handlers.py +202 -0
- package/skills/dforge-1c-import/scripts/lib/handmap.py +142 -0
- package/skills/dforge-1c-import/scripts/lib/i18n_coverage.py +87 -0
- package/skills/dforge-1c-import/scripts/lib/idioms.py +273 -0
- package/skills/dforge-1c-import/scripts/lib/integrity.py +264 -0
- package/skills/dforge-1c-import/scripts/lib/inventory.py +535 -0
- package/skills/dforge-1c-import/scripts/lib/layers.py +106 -0
- package/skills/dforge-1c-import/scripts/lib/meta.py +315 -0
- package/skills/dforge-1c-import/scripts/lib/migrate.py +1299 -0
- package/skills/dforge-1c-import/scripts/lib/mxl.py +368 -0
- package/skills/dforge-1c-import/scripts/lib/naming.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/paths.py +90 -0
- package/skills/dforge-1c-import/scripts/lib/peek.py +70 -0
- package/skills/dforge-1c-import/scripts/lib/posted.py +218 -0
- package/skills/dforge-1c-import/scripts/lib/posting.py +655 -0
- package/skills/dforge-1c-import/scripts/lib/printgen.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/rebuild.py +194 -0
- package/skills/dforge-1c-import/scripts/lib/recorded.py +170 -0
- package/skills/dforge-1c-import/scripts/lib/recorders.py +94 -0
- package/skills/dforge-1c-import/scripts/lib/reftargets.py +95 -0
- package/skills/dforge-1c-import/scripts/lib/reportgen.py +157 -0
- package/skills/dforge-1c-import/scripts/lib/roles.py +33 -0
- package/skills/dforge-1c-import/scripts/lib/seedstate.py +488 -0
- package/skills/dforge-1c-import/scripts/lib/sizing.py +184 -0
- package/skills/dforge-1c-import/scripts/lib/spec.py +400 -0
- package/skills/dforge-1c-import/scripts/lib/statements.py +774 -0
- package/skills/dforge-1c-import/scripts/lib/subsystems.py +480 -0
- package/skills/dforge-1c-import/scripts/lib/terms.py +451 -0
- package/skills/dforge-1c-import/scripts/lib/terms_uk.py +555 -0
- package/skills/dforge-1c-import/scripts/lib/totals.py +387 -0
- package/skills/dforge-1c-import/scripts/lib/typemap.py +73 -0
- package/skills/dforge-1c-import/scripts/lib/unpack.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/validate.py +508 -0
- package/skills/dforge-1c-import/scripts/project-template/contours.py +47 -0
- package/skills/dforge-1c-import/scripts/project-template/sources.py +14 -0
- package/skills/dforge-mcp-author/SKILL.md +8 -2
- package/skills/dforge-mcp-author/examples/column-security/entities/product.json +1 -1
- package/skills/dforge-mcp-author/examples/column-security/manifest.json +0 -1
- package/skills/dforge-mcp-author/examples/column-security/security/roles.json +2 -2
- package/skills/dforge-mcp-author/references/action-dsl.md +20 -7
- package/skills/dforge-mcp-author/references/conventions.md +7 -1
- package/skills/dforge-mcp-author/references/field-types.md +3 -2
- package/skills/dforge-mcp-author/references/formulas.md +5 -1
- package/skills/dforge-mcp-author/references/print-templates.md +5 -2
- package/skills/dforge-mcp-author/references/queries.md +1 -1
- package/skills/dforge-mcp-author/references/registers.md +18 -3
- package/skills/dforge-mcp-author/references/reports.md +4 -1
- package/skills/dforge-mcp-author/references/schema-import.md +1 -1
- package/skills/dforge-mcp-author/references/security.md +1 -1
- package/skills/dforge-mcp-author/references/translations.md +38 -15
- package/skills/dforge-mcp-author/references/validation-checklist.md +2 -3
- package/skills/dforge-module-build/SKILL.md +10 -1
- package/skills/dforge-module-ship/SKILL.md +31 -3
- package/skills/dforge-mcp-author/examples/column-security/translations/en-US.json +0 -35
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,79 @@ 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
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
**`dforge-1c-import`: porting a 1C infobase.** A fifth skill. It carries the decisions a 1C port turns on, each with the
|
|
10
|
+
measurement that settles it: a register's kind, where a posting takes each value, what to carry, physical columns through
|
|
11
|
+
DBNames, vendor against client. It adds `references/loading.md` (copying the rows: swapped uuid halves, the empty reference
|
|
12
|
+
and the empty date as values in register keys and periods, the year offset, enums by `_EnumOrder`, composite references,
|
|
13
|
+
register state, triggers, dangling references, verification) and `references/print-forms.md` (MXL templates, the print
|
|
14
|
+
procedure, `Строка`/`Формат` in SQL, reproducing 1C's slips, datasets as queries). `scripts/` ships the Python toolkit
|
|
15
|
+
from a real port: it reads the configuration and DBNames from MS SQL without 1C, generates and validates modules, copies and
|
|
16
|
+
reconciles the rows, checks postings and reads print templates, pointed at a port by `DF1C_ROOT` / `DF1C_PROJECT` and
|
|
17
|
+
started from `scripts/project-template/`. `install-skills` installs it with the other four.
|
|
18
|
+
|
|
19
|
+
## 0.2.45 — 2026-10-25
|
|
20
|
+
|
|
21
|
+
**English is the base locale, and it lives in the definitions.** Install never reads
|
|
22
|
+
`translations/en.json` / `en-*.json`, so the English file the tools wrote was dead weight: its labels
|
|
23
|
+
were never applied, and the English UI kept showing whatever the definitions said. `dforge_module_create`
|
|
24
|
+
(preset `full`) no longer writes `translations/en-US.json`; `dforge_translation_sync` defaults to the
|
|
25
|
+
`supportedLocales` entries only, refuses an English locale, and warns about a leftover English file;
|
|
26
|
+
`dforge_module_validate` warns about one instead of requiring role labels in it; `dforge_module_plan`
|
|
27
|
+
defaults to no extra locales. The sync seeds a setting from its `description` and a view, menu, folder or
|
|
28
|
+
action from `label`, else `description` — what install shows. `references/translations.md` gains the
|
|
29
|
+
table of where each object's English text lives, and the column-security example drops its
|
|
30
|
+
`en-US.json` for short labels in the definitions.
|
|
31
|
+
|
|
32
|
+
**File map keys use `/` on Windows too.** Every tool that builds a `files` key from a path (field,
|
|
33
|
+
entity, view, menu, action, role-right, seed, diagram and translation tools, the refactors) returned
|
|
34
|
+
`translations\de-DE.json`-style keys on Windows; they now return `translations/de-DE.json` on every OS.
|
|
35
|
+
|
|
36
|
+
**DSL builtins.** `attachText(content, fileName, encoding?, mimeType?)` / `attachBase64(base64, fileName, mimeType?)` build a file for `sendEmail()`'s fourth argument, keeping its own type (an `.ics` as `text/calendar`); a scheduled job can attach them, unlike a `printPdf()` file. Follows metadata 0.0.45; `dforge_action_check` knows both. **Needs `@dforge-core/dforge-cli` with them** (0.2.36).
|
|
37
|
+
|
|
38
|
+
**Schemas follow metadata 0.0.46.** `dforge://schema/registers` accepts `importedKeyField` on an accounting register; `dforge://schema/entity` accepts the `filesize` field type.
|
|
39
|
+
|
|
40
|
+
**Registers and actions docs.** `getAccountBalance` / `getAccountTurnover` take `exclude` as one document or an array, and an accounting register's `importedKeyField` leaves out postings loaded beside a document. An action in `ui/actions.json` can ship `paramSets` (saved parameter values; a `=` formula or a template reads the record) and a `previewTemplate` (a PDF beside the run dialog's params). **Needs `@dforge-core/dforge-cli` 0.2.36.**
|
|
41
|
+
|
|
42
|
+
**DSL docs.** `[owner_id].[email]` / `[owner_id].[full_name]` read the user behind a `user` column (platform #1499); `dforge://reference/dsl` says so.
|
|
43
|
+
|
|
44
|
+
**Registers reference.** Which kind folds rows per dimension tuple (only `balance`); the account-read
|
|
45
|
+
`options` in the reads table; the balance at a document's moment within one second (`before` plus
|
|
46
|
+
`exclude`, the caller ordering what the platform does not); `importedKeyField` marked as being for a
|
|
47
|
+
history loaded from another system; how long a cache rebuild at install can take. Common mistakes gain an
|
|
48
|
+
empty dimension (no `allowEmpty` on any kind; `=COALESCE(...)` or `skipPostingWhenEmpty`), `headerMap`
|
|
49
|
+
taking columns only and why each must be locked, one constant for two resources, a month missing from the
|
|
50
|
+
period calendar counting as closed (`DF003`), and the `registers/column-not-numeric` warning. The engine
|
|
51
|
+
writes an analytic slot's `entity` as `module.entity` (platform #1544), and a loader writes the same; a
|
|
52
|
+
filter matches either spelling, `groupBy` splits old bare slots from new ones until a migration and
|
|
53
|
+
`rebuild-totals`.
|
|
54
|
+
|
|
55
|
+
**DSL entity codes.** A data built-in's `module.entity` is looked up by module code, hyphen included, and
|
|
56
|
+
otherwise as a schema (platform #1543), so `insert('cdy-tax.x', …)` and `insert('cdy_tax.x', …)` both
|
|
57
|
+
work. The conventions doc notes that install refuses a schema or code another module already holds.
|
|
58
|
+
Roles and settings are translated (the conventions doc called roles ignored); a print template's name is
|
|
59
|
+
its `description`.
|
|
60
|
+
|
|
61
|
+
**DSL, formulas, fields.** An inline action stops after 10 seconds; an assignment inside a one-line block
|
|
62
|
+
fails; indexing after a call or an index compiles since platform #1537. `TODAY()` / `NOW()` follow the
|
|
63
|
+
request's time zone, a `datetime` column may pin `params.timeZone`, a number-literal formula becomes a
|
|
64
|
+
column `DEFAULT`, and a bare `$[setting]` formula fills a column an insert leaves out. A zero divisor
|
|
65
|
+
fails wherever a formula runs in SQL.
|
|
66
|
+
|
|
67
|
+
**Reports and print.** A stored procedure's result-column `baseDatatypeCd` codes (`timestamp`, not
|
|
68
|
+
`datetime`); `ROWS 1` on a one-row table function, since install's `SET search_path` stops inlining; every
|
|
69
|
+
`logic/reports/*.sql` runs at every install (and "must be owner of function"); casting columns for
|
|
70
|
+
`RETURN QUERY`. Print: `%B` prints English names, `format_date` returns a string, timestamps follow the
|
|
71
|
+
column's or the request's zone, Scriban's `blank` on a date, stray spaces from an empty reference hop.
|
|
72
|
+
|
|
73
|
+
**Seed, demo and ship.** Seed rows are re-applied on every install and ship in the package (fictional
|
|
74
|
+
data only); `demo-data/` inserts but does not post, so an accounting demo runs the module's actions; a
|
|
75
|
+
same-version install is skipped yet reports success. `dforge-module-ship` covers an install blocked by an
|
|
76
|
+
open transaction (`pg_stat_activity`), a client timeout during a cache rebuild, and the local dev stack
|
|
77
|
+
(restart `dforge-api`, the `<tenant>._.localhost` URL, an expired CLI token, a platform binary behind the
|
|
78
|
+
tenant). Option labels: plain English in the definition, translated as strings.
|
|
79
|
+
|
|
7
80
|
## 0.2.44 — 2026-10-21
|
|
8
81
|
|
|
9
82
|
**Schemas follow metadata 0.0.44.** `dforge://schema/translations` describes `storedProcedures`
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for dForge module authoring. Exposes 35 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 **
|
|
5
|
+
Ships with **five Claude Skills** (`skills/`): a router plus one skill per lifecycle stage — design (Phase 0), build (Phases 1-5), and ship (Phase 6) — and `dforge-1c-import` for porting a 1C infobase, with its Python toolkit. 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
|
|
|
@@ -143,7 +143,7 @@ The field/entity tools validate against the `@dforge-core/metadata` registry: an
|
|
|
143
143
|
| Tool | Behavior |
|
|
144
144
|
|---|---|
|
|
145
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
|
|
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 every `supportedLocales` entry; writes no English file (install skips `en*.json` — English lives in the definitions) |
|
|
147
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 |
|
|
148
148
|
|
|
149
149
|
**Security (Phase 5)**
|
|
@@ -206,6 +206,7 @@ Authoring guidance ships as **four** skills, split by lifecycle stage. Each stag
|
|
|
206
206
|
| `dforge-module-design` | 0a–0d | Identity, intake guardrails, design doc, pre-scaffold validation. Ends at `readyToScaffold: true`. |
|
|
207
207
|
| `dforge-module-build` | 1–5 | Entities, behavior, views/menus, polish, security. Loading-policy table, core-rules cheat sheet, backtrack protocol. |
|
|
208
208
|
| `dforge-module-ship` | 6 | Validate → pre-pack review → version audit → pack → install-fix loop. |
|
|
209
|
+
| `dforge-1c-import` | — | Porting a 1C:Enterprise infobase: register kinds, posting bindings, what to carry, the row copy and its conversions, print forms. `scripts/` holds the Python toolkit (read the infobase from MS SQL without 1C, generate modules, load, reconcile, check postings, read MXL templates). |
|
|
209
210
|
|
|
210
211
|
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).
|
|
211
212
|
|
package/dist/server.js
CHANGED
|
@@ -31002,7 +31002,7 @@ var import_node_crypto = require("crypto");
|
|
|
31002
31002
|
var fs2 = __toESM(require("fs"));
|
|
31003
31003
|
var path2 = __toESM(require("path"));
|
|
31004
31004
|
|
|
31005
|
-
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.
|
|
31005
|
+
// node_modules/.pnpm/@dforge-core+dforge-cli@0.2.36/node_modules/@dforge-core/dforge-cli/dist/lib.mjs
|
|
31006
31006
|
function buildManifest(opts, moduleId) {
|
|
31007
31007
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31008
31008
|
const manifest = {
|
|
@@ -31132,53 +31132,6 @@ function buildActions() {
|
|
|
31132
31132
|
function buildSettings() {
|
|
31133
31133
|
return {};
|
|
31134
31134
|
}
|
|
31135
|
-
function buildTranslations(opts) {
|
|
31136
|
-
const entities = {};
|
|
31137
|
-
for (const e of opts.entities) {
|
|
31138
|
-
const fields = {
|
|
31139
|
-
// identity trait → primary key column
|
|
31140
|
-
[`${e.name}_id`]: { label: `${e.label} ID` },
|
|
31141
|
-
[NAME_FIELD]: { label: "Name" }
|
|
31142
|
-
};
|
|
31143
|
-
if (e.traits === "identity+audit") {
|
|
31144
|
-
fields.created_date = { label: "Created Date" };
|
|
31145
|
-
fields.last_updated = { label: "Last Updated" };
|
|
31146
|
-
}
|
|
31147
|
-
const entityEntry = { label: e.label };
|
|
31148
|
-
if (opts.description && opts.entities.length === 1) {
|
|
31149
|
-
entityEntry.desc = opts.description;
|
|
31150
|
-
}
|
|
31151
|
-
entityEntry.fields = fields;
|
|
31152
|
-
if (e.constraints?.length) {
|
|
31153
|
-
entityEntry.constraints = Object.fromEntries(
|
|
31154
|
-
e.constraints.map((c) => [c.name, { message: c.message }])
|
|
31155
|
-
);
|
|
31156
|
-
}
|
|
31157
|
-
entities[e.name] = entityEntry;
|
|
31158
|
-
}
|
|
31159
|
-
return {
|
|
31160
|
-
entities,
|
|
31161
|
-
folders: {
|
|
31162
|
-
// folders.json root folder key is the module code (see buildFolders).
|
|
31163
|
-
[opts.code]: { label: opts.displayName }
|
|
31164
|
-
},
|
|
31165
|
-
roles: {
|
|
31166
|
-
// Role labels are completeness-enforced in every locale incl. en-US.
|
|
31167
|
-
[`${opts.code}.admin`]: { label: `${opts.displayName} Administrator` }
|
|
31168
|
-
},
|
|
31169
|
-
views: Object.fromEntries(
|
|
31170
|
-
opts.entities.map((e) => [e.name, { label: e.label }])
|
|
31171
|
-
),
|
|
31172
|
-
menus: {
|
|
31173
|
-
[`${opts.code}_menu`]: {
|
|
31174
|
-
label: opts.displayName,
|
|
31175
|
-
items: Object.fromEntries(
|
|
31176
|
-
opts.entities.map((e) => [e.name, { label: e.label }])
|
|
31177
|
-
)
|
|
31178
|
-
}
|
|
31179
|
-
}
|
|
31180
|
-
};
|
|
31181
|
-
}
|
|
31182
31135
|
function buildSeedData(entity) {
|
|
31183
31136
|
const entityCode = typeof entity === "string" ? entity : entity?.name ?? "";
|
|
31184
31137
|
return { entityCode, records: [] };
|
|
@@ -31290,7 +31243,7 @@ function buildZedTasks() {
|
|
|
31290
31243
|
var fs = __toESM(require("fs"));
|
|
31291
31244
|
var path = __toESM(require("path"));
|
|
31292
31245
|
|
|
31293
|
-
// node_modules/.pnpm/@dforge-core+metadata@0.0.
|
|
31246
|
+
// node_modules/.pnpm/@dforge-core+metadata@0.0.46/node_modules/@dforge-core/metadata/dist/index.js
|
|
31294
31247
|
var AggType = {
|
|
31295
31248
|
Sum: "sum",
|
|
31296
31249
|
Count: "count",
|
|
@@ -31310,6 +31263,7 @@ var fieldTypes = [
|
|
|
31310
31263
|
{ cd: "number", name: "Number", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
|
|
31311
31264
|
{ cd: "currency", name: "Currency", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: 2, useStringInFilter: false },
|
|
31312
31265
|
{ cd: "percent", name: "Percent", columnType: "D", baseDatatypeCd: "number", supportedAgg: "CAM", maxLen: null, precision: 2, useStringInFilter: false },
|
|
31266
|
+
{ cd: "filesize", name: "File Size", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
|
|
31313
31267
|
{ cd: "checkbox", name: "Checkbox", columnType: "D", baseDatatypeCd: "bool", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
|
|
31314
31268
|
{ cd: "date", name: "Date", columnType: "D", baseDatatypeCd: "date", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
|
|
31315
31269
|
{ cd: "datetime", name: "Date & Time", columnType: "D", baseDatatypeCd: "timestamp", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
|
|
@@ -31509,7 +31463,8 @@ var NUMERIC_TOTAL = 18;
|
|
|
31509
31463
|
var TEXT_BACKED = /* @__PURE__ */ new Set(["textarea", "markdown", "richtext", "code"]);
|
|
31510
31464
|
var DB_DATATYPE_OVERRIDE = {
|
|
31511
31465
|
file: "jsonb",
|
|
31512
|
-
image: "jsonb"
|
|
31466
|
+
image: "jsonb",
|
|
31467
|
+
filesize: "int8"
|
|
31513
31468
|
};
|
|
31514
31469
|
function deriveDbDatatype(fieldTypeCd, options = {}) {
|
|
31515
31470
|
const ft = getFieldType(fieldTypeCd);
|
|
@@ -31541,7 +31496,8 @@ var ACCOUNTING_PROPERTIES = [
|
|
|
31541
31496
|
"creditAccount",
|
|
31542
31497
|
"debitAnalyticFields",
|
|
31543
31498
|
"creditAnalyticFields",
|
|
31544
|
-
"sideDimensions"
|
|
31499
|
+
"sideDimensions",
|
|
31500
|
+
"importedKeyField"
|
|
31545
31501
|
];
|
|
31546
31502
|
var BALANCE_PROPERTIES = ["allowNegative", "autoCreate"];
|
|
31547
31503
|
var REFUSED = {
|
|
@@ -31550,6 +31506,40 @@ var REFUSED = {
|
|
|
31550
31506
|
info: ["journal", "journalDirection", "journalDate", ...ACCOUNTING_PROPERTIES, "totals", ...BALANCE_PROPERTIES],
|
|
31551
31507
|
accounting: ["entity", ...INFO_PROPERTIES, "journalDirection", ...BALANCE_PROPERTIES]
|
|
31552
31508
|
};
|
|
31509
|
+
var SUMMED = ["balance", "turnover", "accounting"];
|
|
31510
|
+
var slot = (path21, on, what, summed = false) => ({
|
|
31511
|
+
path: path21.split("."),
|
|
31512
|
+
on,
|
|
31513
|
+
what,
|
|
31514
|
+
...summed ? { numericFor: SUMMED } : {}
|
|
31515
|
+
});
|
|
31516
|
+
var REGISTER_COLUMN_SLOTS = [
|
|
31517
|
+
slot("journalDirection", "journal", "Journal direction column"),
|
|
31518
|
+
slot("journalDate", "journal", "Journal date column"),
|
|
31519
|
+
slot("period", "entity", "Period column"),
|
|
31520
|
+
slot("recorder", "entity", "Recorder column"),
|
|
31521
|
+
slot("dimensions.*", "carrier", "Dimension"),
|
|
31522
|
+
slot("resources.*.field", "carrier", "Resource '{0}' column", true),
|
|
31523
|
+
slot("resources.*.debitField", "carrier", "Resource '{0}' column", true),
|
|
31524
|
+
slot("resources.*.creditField", "carrier", "Resource '{0}' column", true),
|
|
31525
|
+
slot("account", "journal", "Account column"),
|
|
31526
|
+
slot("debitAccount", "journal", "Debit account column"),
|
|
31527
|
+
slot("creditAccount", "journal", "Credit account column"),
|
|
31528
|
+
slot("analyticFields.*", "journal", "Analytic slot column"),
|
|
31529
|
+
slot("debitAnalyticFields.*", "journal", "Analytic slot column"),
|
|
31530
|
+
slot("creditAnalyticFields.*", "journal", "Analytic slot column"),
|
|
31531
|
+
slot("sideDimensions.*.debitField", "journal", "Dimension '{0}' debit column"),
|
|
31532
|
+
slot("sideDimensions.*.creditField", "journal", "Dimension '{0}' credit column"),
|
|
31533
|
+
slot("composition.account", "composition", "Composition 'account' column"),
|
|
31534
|
+
slot("composition.slot", "composition", "Composition 'slot' column"),
|
|
31535
|
+
slot("composition.analytic", "composition", "Composition 'analytic' column"),
|
|
31536
|
+
slot("totals.periodField", "totals", "Totals period column"),
|
|
31537
|
+
slot("totals.accountField", "totals", "Totals account column"),
|
|
31538
|
+
slot("totals.resources.*.inField", "totals", "Totals '{0}' inField", true),
|
|
31539
|
+
slot("totals.resources.*.outField", "totals", "Totals '{0}' outField", true),
|
|
31540
|
+
slot("totals.resources.*.debitField", "totals", "Totals '{0}' debitField", true),
|
|
31541
|
+
slot("totals.resources.*.creditField", "totals", "Totals '{0}' creditField", true)
|
|
31542
|
+
];
|
|
31553
31543
|
|
|
31554
31544
|
// src/tools/_helpers.ts
|
|
31555
31545
|
function modulePaths(moduleDir) {
|
|
@@ -31648,7 +31638,7 @@ function jsonText(obj) {
|
|
|
31648
31638
|
return JSON.stringify(obj, null, " ") + "\n";
|
|
31649
31639
|
}
|
|
31650
31640
|
function rel(root, abs) {
|
|
31651
|
-
return path.relative(root, abs);
|
|
31641
|
+
return path.relative(root, abs).split(path.sep).join("/");
|
|
31652
31642
|
}
|
|
31653
31643
|
function loadManifest(moduleDir) {
|
|
31654
31644
|
const paths = modulePaths(moduleDir);
|
|
@@ -31955,7 +31945,7 @@ var createModuleSchema = {
|
|
|
31955
31945
|
version: external_exports.string().regex(/^\d+\.\d+\.\d+(-[\w.]+)?$/).default("0.1.0"),
|
|
31956
31946
|
dbSchemaVersion: external_exports.string().regex(/^\d+\.\d+\.\d+(-[\w.]+)?$/).default("0.0.1"),
|
|
31957
31947
|
dependencies: external_exports.array(external_exports.string()).default([]).describe("Other modules this one requires, by code (e.g. ['parties']). Leave empty unless it genuinely depends on another module. Do NOT list 'admin' or 'metadata' \u2014 they're system modules present in every tenant, so depending on them is redundant (the check always passes and never affects install order)."),
|
|
31958
|
-
preset: external_exports.enum(["minimal", "minimal-plus", "full"]).default("minimal").describe("'minimal' = manifest+entity+UI+role only. 'full' = also settings/
|
|
31948
|
+
preset: external_exports.enum(["minimal", "minimal-plus", "full"]).default("minimal").describe("'minimal' = manifest+entity+UI+role only. 'full' = also settings/seed-data/logic stubs. No translation file: English lives in the definitions, other locales come from dforge_translation_sync. 'minimal-plus' behaves like 'minimal' here since MCP can't loop; pass all entities up-front."),
|
|
31959
31949
|
entities: external_exports.array(
|
|
31960
31950
|
external_exports.object({
|
|
31961
31951
|
name: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("Entity code, e.g. 'customer'. Lowercase, digits, underscore; first char a letter."),
|
|
@@ -32039,7 +32029,6 @@ function createModuleFiles(args) {
|
|
|
32039
32029
|
if (buildZedTasks2) write(".zed/tasks.json", buildZedTasks2());
|
|
32040
32030
|
if (opts.preset === "full") {
|
|
32041
32031
|
write("settings.json", buildSettings());
|
|
32042
|
-
write("translations/en-US.json", buildTranslations(opts));
|
|
32043
32032
|
for (const e of opts.entities) {
|
|
32044
32033
|
write(`seed-data/01-${e.name}.json`, buildSeedData());
|
|
32045
32034
|
}
|
|
@@ -32132,9 +32121,9 @@ var planModuleSchema = {
|
|
|
32132
32121
|
dependencies: external_exports.array(external_exports.string()).optional().describe(
|
|
32133
32122
|
"[write_identity] Other dForge module codes this module depends on. admin + metadata are always implicit."
|
|
32134
32123
|
),
|
|
32135
|
-
locales: external_exports.array(external_exports.string()).optional().describe("[write_identity]
|
|
32124
|
+
locales: external_exports.array(external_exports.string()).optional().describe("[write_identity] Locales besides English, e.g. ['uk-UA', 'de-DE'] \u2014 recorded in CLAUDE.md only; declare them in manifest.supportedLocales and run dforge_translation_sync to create the files. English is the base: its text lives in the definitions and needs no file. Default: [] (English only)."),
|
|
32136
32125
|
preset: external_exports.enum(["minimal", "minimal-plus", "full"]).optional().describe(
|
|
32137
|
-
"[write_identity] Scaffold depth: minimal = entities + views + role; full = + settings +
|
|
32126
|
+
"[write_identity] Scaffold depth: minimal = entities + views + role; full = + settings + seed-data stubs."
|
|
32138
32127
|
),
|
|
32139
32128
|
// ── write_requirements / write_design fields (Phase 0b / 0c) ────────
|
|
32140
32129
|
userConfirmed: external_exports.boolean().optional().describe(
|
|
@@ -32364,8 +32353,8 @@ function handleCheck(root) {
|
|
|
32364
32353
|
`What's the module's display name? (e.g. "Purchase Orders", "HR Leave Requests")`,
|
|
32365
32354
|
"What code should it use? (snake_case, letters + digits + underscores \u2014 becomes the DB schema name; suggest a default derived from the display name)",
|
|
32366
32355
|
"Does this module depend on any other dForge modules \u2014 e.g. needing entities from 'crm' or 'parties'? (admin and metadata are platform-implicit; list any others, or say None)",
|
|
32367
|
-
"English only, or
|
|
32368
|
-
"How complete should the initial scaffold be? (minimal = entities + default views + single admin role; minimal-plus = + settings
|
|
32356
|
+
"English only, or which other locales should the module ship translations for (e.g. uk-UA)? English is the base and needs no translation file.",
|
|
32357
|
+
"How complete should the initial scaffold be? (minimal = entities + default views + single admin role; minimal-plus = + settings stubs; full = + logic stubs + seed data structure)"
|
|
32369
32358
|
],
|
|
32370
32359
|
writeAction: "Once all answers are collected and reflected back, call dforge_module_plan({ action: 'write_identity', moduleDir, displayName, code, dependencies, locales, preset })."
|
|
32371
32360
|
};
|
|
@@ -32439,11 +32428,12 @@ function handleCheck(root) {
|
|
|
32439
32428
|
};
|
|
32440
32429
|
}
|
|
32441
32430
|
function handleWriteIdentity(root, args) {
|
|
32442
|
-
const { displayName, code: code2, dependencies = [],
|
|
32431
|
+
const { displayName, code: code2, dependencies = [], preset = "minimal" } = args;
|
|
32432
|
+
const locales = (args.locales ?? []).filter((l) => !/^en(-|$)/i.test(l.trim()));
|
|
32443
32433
|
if (!displayName) throw new Error("displayName is required for write_identity.");
|
|
32444
32434
|
if (!code2) throw new Error("code is required for write_identity.");
|
|
32445
32435
|
const depsText = dependencies.length > 0 ? dependencies.join(", ") : "None";
|
|
32446
|
-
const localesText = locales.join(", ");
|
|
32436
|
+
const localesText = ["English (base, in the definitions)", ...locales].join(", ");
|
|
32447
32437
|
const claudeMd = buildClaudeMd({ root, displayName, code: code2, depsText, localesText, preset });
|
|
32448
32438
|
return {
|
|
32449
32439
|
summary: `Phase 0a complete \u2014 CLAUDE.md prepared for module '${code2}'.`,
|
|
@@ -34622,7 +34612,7 @@ var path10 = __toESM(require("path"));
|
|
|
34622
34612
|
var fs7 = __toESM(require("fs"));
|
|
34623
34613
|
var path9 = __toESM(require("path"));
|
|
34624
34614
|
|
|
34625
|
-
// node_modules/.pnpm/@dforge-core+metadata@0.0.
|
|
34615
|
+
// node_modules/.pnpm/@dforge-core+metadata@0.0.46/node_modules/@dforge-core/metadata/dist/dsl/index.js
|
|
34626
34616
|
var PUNCT3 = ["===", "!=="];
|
|
34627
34617
|
var PUNCT2 = [
|
|
34628
34618
|
"==",
|
|
@@ -35476,7 +35466,7 @@ var BUILTINS = [
|
|
|
35476
35466
|
name: "sendEmail",
|
|
35477
35467
|
signature: "sendEmail(to, subjectOrTemplate, dataOrBody?, attachments?)",
|
|
35478
35468
|
returns: "void",
|
|
35479
|
-
doc: "Queue an outbound email. Three arguments, and the **second** picks the mode: `sendEmail(to, 'order.shipped', { ref: [code] })` sends a template, `sendEmail(to, 'Your order shipped', '<p>\u2026</p>')` sends raw HTML. A second argument with no spaces that contains `.` or `_` is read as a template code \u2014 so a raw subject like `'Shipped.'` silently becomes one; give a raw subject spaces. The optional **fourth** argument attaches files: a `printPdf()` result, or an array of them (`[a, b]`; pass a single file as itself, because a one-name `[a]` reads as a field reference in the DSL).
|
|
35469
|
+
doc: "Queue an outbound email. Three arguments, and the **second** picks the mode: `sendEmail(to, 'order.shipped', { ref: [code] })` sends a template, `sendEmail(to, 'Your order shipped', '<p>\u2026</p>')` sends raw HTML. A second argument with no spaces that contains `.` or `_` is read as a template code \u2014 so a raw subject like `'Shipped.'` silently becomes one; give a raw subject spaces. The optional **fourth** argument attaches files: a `printPdf()`, `attachText()` or `attachBase64()` result, or an array of them (`[a, b]`; pass a single file as itself, because a one-name `[a]` reads as a field reference in the DSL). A `printPdf()` file can be attached only by the user who generated it (or a workspace admin) and not in a scheduled job; a file built with `attachText()` / `attachBase64()` has no owner and works everywhere, a scheduled job included. At most 5 files and 10 MB in all per email (`Email:MaxAttachments`, `Email:MaxAttachmentBytes`). A file the sender may not attach fails the action."
|
|
35480
35470
|
},
|
|
35481
35471
|
// External calls
|
|
35482
35472
|
{
|
|
@@ -35529,6 +35519,18 @@ var BUILTINS = [
|
|
|
35529
35519
|
returns: "string",
|
|
35530
35520
|
doc: "`downloadText()` for bytes: decodes `base64` and hands it to the user as `fileName` (`application/octet-stream` unless `mimeType` says otherwise). Returns the signed download URL."
|
|
35531
35521
|
},
|
|
35522
|
+
{
|
|
35523
|
+
name: "attachText",
|
|
35524
|
+
signature: "attachText(content, fileName, encoding?, mimeType?)",
|
|
35525
|
+
returns: "object",
|
|
35526
|
+
doc: "Build a text file to attach to an email: encodes `content` in `encoding` (`utf-8` by default) and returns a descriptor for `sendEmail()`'s fourth argument, e.g. `sendEmail(to, 'Interview', body, attachText(ics, 'invite.ics', 'utf-8', 'text/calendar'))`. The file keeps `mimeType` (`text/plain` by default). It is the script's own output, so no user's rights are involved and it works in a scheduled job. Only the emails of this run can name it. Max 10 MB per file and 10 files per run, shared with `downloadText()` / `downloadBase64()`."
|
|
35527
|
+
},
|
|
35528
|
+
{
|
|
35529
|
+
name: "attachBase64",
|
|
35530
|
+
signature: "attachBase64(base64, fileName, mimeType?)",
|
|
35531
|
+
returns: "object",
|
|
35532
|
+
doc: "`attachText()` for bytes given as base64 (`application/octet-stream` unless `mimeType` says otherwise)."
|
|
35533
|
+
},
|
|
35532
35534
|
{
|
|
35533
35535
|
name: "getSetting",
|
|
35534
35536
|
signature: "getSetting(settingCd)",
|
|
@@ -36389,10 +36391,17 @@ function checkConstraintTranslations(ctx) {
|
|
|
36389
36391
|
function checkTranslationCompleteness(ctx) {
|
|
36390
36392
|
const { paths, err, warn, roles, supportedLocales } = ctx;
|
|
36391
36393
|
const roleCodes = Object.keys(roles);
|
|
36392
|
-
const
|
|
36394
|
+
const allFiles = fs7.existsSync(paths.translationsDir) ? fs7.readdirSync(paths.translationsDir).filter((f) => f.toLowerCase().endsWith(".json")).sort() : [];
|
|
36395
|
+
const localeFiles = allFiles.filter((f) => !isEnglishLocale(f.slice(0, -5)));
|
|
36396
|
+
for (const file2 of allFiles.filter((f) => isEnglishLocale(f.slice(0, -5)))) {
|
|
36397
|
+
warn(
|
|
36398
|
+
`translations/${file2}`,
|
|
36399
|
+
"install never reads an English translation file \u2014 the English UI shows the definitions' own text (an entity's or field's `description`, a menu's, action's or report's `label`, a role's or setting's `description`). Move any wording you want from this file into the definitions, then delete it."
|
|
36400
|
+
);
|
|
36401
|
+
}
|
|
36393
36402
|
for (const raw of supportedLocales) {
|
|
36394
36403
|
const locale = raw.trim();
|
|
36395
|
-
if (!locale) continue;
|
|
36404
|
+
if (!locale || isEnglishLocale(locale)) continue;
|
|
36396
36405
|
if (!resolveTranslationFile(paths.translationsDir, locale)) {
|
|
36397
36406
|
err(
|
|
36398
36407
|
"translations",
|
|
@@ -36400,12 +36409,6 @@ function checkTranslationCompleteness(ctx) {
|
|
|
36400
36409
|
);
|
|
36401
36410
|
}
|
|
36402
36411
|
}
|
|
36403
|
-
if (roleCodes.length > 0 && localeFiles.length === 0) {
|
|
36404
|
-
warn(
|
|
36405
|
-
"translations",
|
|
36406
|
-
`no translations/ files \u2014 ship at least translations/en-US.json with a roles block (a 'label' for each of: ${roleCodes.join(", ")}); role labels are completeness-enforced at install.`
|
|
36407
|
-
);
|
|
36408
|
-
}
|
|
36409
36412
|
for (const file2 of localeFiles) {
|
|
36410
36413
|
let tx;
|
|
36411
36414
|
try {
|
|
@@ -36422,7 +36425,7 @@ function checkTranslationCompleteness(ctx) {
|
|
|
36422
36425
|
if (missing.length > 0) {
|
|
36423
36426
|
err(
|
|
36424
36427
|
`translations/${file2}`,
|
|
36425
|
-
`missing roles.<code>.label for: ${missing.join(", ")} \u2014 completeness is enforced in every locale
|
|
36428
|
+
`missing roles.<code>.label for: ${missing.join(", ")} \u2014 completeness is enforced in every non-English locale file; install fails with "Label for role '<code>'."`
|
|
36426
36429
|
);
|
|
36427
36430
|
}
|
|
36428
36431
|
}
|
|
@@ -36891,7 +36894,7 @@ function str(obj, key) {
|
|
|
36891
36894
|
var translationSyncSchema = {
|
|
36892
36895
|
moduleDir: external_exports.string().describe("Path to the module root."),
|
|
36893
36896
|
locales: external_exports.array(external_exports.string().regex(/^[a-z]{2}(-[A-Z]{2})?$/)).optional().describe(
|
|
36894
|
-
"
|
|
36897
|
+
"Non-English locales to sync. Default: every entry in manifest.supportedLocales \u2014 exactly the set install requires. An English locale is refused: install never reads translations/en*.json, English text belongs in the definitions."
|
|
36895
36898
|
),
|
|
36896
36899
|
prune: external_exports.boolean().default(false).describe(
|
|
36897
36900
|
"Remove keys that no longer correspond to anything in the module (e.g. after an entity delete). Off by default \u2014 pruning discards translated text."
|
|
@@ -36907,8 +36910,8 @@ function buildSkeleton(moduleDir) {
|
|
|
36907
36910
|
`traits.json \u2014 ${traitsError} Its traits can't be overlaid, so the entities that use one would be skipped as declaring an unknown trait and their columns would go unlabelled \u2014 which fails the install. Fix the file and sync again.`
|
|
36908
36911
|
);
|
|
36909
36912
|
}
|
|
36910
|
-
const push = (section, pathParts, english, required2 = false, legacyParts
|
|
36911
|
-
entries.push({ pathParts, english, required: required2, legacyParts
|
|
36913
|
+
const push = (section, pathParts, english, required2 = false, legacyParts) => {
|
|
36914
|
+
entries.push({ pathParts, english, required: required2, legacyParts });
|
|
36912
36915
|
counts[section] = (counts[section] ?? 0) + 1;
|
|
36913
36916
|
};
|
|
36914
36917
|
for (const [name, relPath] of Object.entries(manifest.entities ?? {})) {
|
|
@@ -36990,10 +36993,10 @@ function buildSkeleton(moduleDir) {
|
|
|
36990
36993
|
}
|
|
36991
36994
|
const settings = readJsonOrDefault(paths.settings, {});
|
|
36992
36995
|
for (const [code2, s] of Object.entries(settings)) {
|
|
36993
|
-
push("settings", ["settings", code2, "label"], str(s, "
|
|
36996
|
+
push("settings", ["settings", code2, "label"], str(s, "description") ?? str(s, "label") ?? str(s, "desc") ?? titleize(code2));
|
|
36994
36997
|
}
|
|
36995
36998
|
for (const text of actionMessageTexts(paths.logicDir, actions).keys()) {
|
|
36996
|
-
push("messages", ["messages", text], text
|
|
36999
|
+
push("messages", ["messages", text], text);
|
|
36997
37000
|
}
|
|
36998
37001
|
return { entries, counts };
|
|
36999
37002
|
}
|
|
@@ -37022,9 +37025,20 @@ function pruneTo(existing, keep, trail = []) {
|
|
|
37022
37025
|
function translationSync(args) {
|
|
37023
37026
|
const { paths, manifest } = loadManifest(args.moduleDir);
|
|
37024
37027
|
const declared = Array.isArray(manifest.supportedLocales) ? manifest.supportedLocales.filter((l) => typeof l === "string") : [];
|
|
37025
|
-
const
|
|
37028
|
+
const isEnglish = (l) => l.toLowerCase() === "en" || l.toLowerCase().startsWith("en-");
|
|
37029
|
+
const english = (args.locales ?? []).filter(isEnglish);
|
|
37030
|
+
if (english.length > 0) {
|
|
37031
|
+
throw new Error(
|
|
37032
|
+
`Refusing to sync ${english.join(", ")}: install never reads translations/en*.json. English is the base locale and lives in the definitions \u2014 an entity's or field's \`description\`, a menu's, action's or report's \`label\`, a role's or setting's \`description\`. Edit the text there.`
|
|
37033
|
+
);
|
|
37034
|
+
}
|
|
37035
|
+
const ignored = fs9.existsSync(paths.translationsDir) ? fs9.readdirSync(paths.translationsDir).filter((f) => f.toLowerCase().endsWith(".json") && isEnglish(f.slice(0, -5))) : [];
|
|
37036
|
+
const staleEnglish = ignored.length > 0 ? ` Leftover ${ignored.map((f) => `translations/${f}`).join(", ")} is never read by install \u2014 move any wording you want from it into the definitions, then delete it.` : "";
|
|
37037
|
+
const locales = args.locales ?? declared.filter((l) => !isEnglish(l));
|
|
37026
37038
|
if (locales.length === 0) {
|
|
37027
|
-
throw new Error(
|
|
37039
|
+
throw new Error(
|
|
37040
|
+
'No locales to sync. An English-only module needs no translation files: its English text lives in the definitions. To translate, declare the other locales in manifest.supportedLocales (e.g. ["uk-UA"]).' + staleEnglish
|
|
37041
|
+
);
|
|
37028
37042
|
}
|
|
37029
37043
|
const { entries, counts } = buildSkeleton(args.moduleDir);
|
|
37030
37044
|
if (entries.length === 0) {
|
|
@@ -37033,11 +37047,11 @@ function translationSync(args) {
|
|
|
37033
37047
|
);
|
|
37034
37048
|
}
|
|
37035
37049
|
const keep = new Set(entries.map((e) => compositeKey(...e.pathParts)));
|
|
37036
|
-
const isEnglish = (l) => l.toLowerCase() === "en" || l.toLowerCase().startsWith("en-");
|
|
37037
37050
|
const files = {};
|
|
37038
37051
|
const report = [];
|
|
37039
37052
|
let totalAdded = 0;
|
|
37040
37053
|
let missingRequired = 0;
|
|
37054
|
+
const seeded = [];
|
|
37041
37055
|
for (const locale of locales) {
|
|
37042
37056
|
const abs = path12.join(paths.translationsDir, `${locale}.json`);
|
|
37043
37057
|
let existing = {};
|
|
@@ -37051,7 +37065,6 @@ function translationSync(args) {
|
|
|
37051
37065
|
const next = args.prune ? pruneTo(existing, keep) : JSON.parse(JSON.stringify(existing));
|
|
37052
37066
|
let added = 0;
|
|
37053
37067
|
for (const entry of entries) {
|
|
37054
|
-
if (entry.nonEnglishOnly && isEnglish(locale)) continue;
|
|
37055
37068
|
const current = getIn(next, entry.pathParts);
|
|
37056
37069
|
if (typeof current === "string" && current.trim() !== "") continue;
|
|
37057
37070
|
const legacy = entry.legacyParts && getIn(existing, entry.legacyParts);
|
|
@@ -37061,10 +37074,21 @@ function translationSync(args) {
|
|
|
37061
37074
|
}
|
|
37062
37075
|
files[rel(paths.root, abs)] = jsonText(next);
|
|
37063
37076
|
totalAdded += added;
|
|
37077
|
+
if (added > 0) seeded.push(locale);
|
|
37064
37078
|
report.push(`${locale}: +${added} key(s)${added === 0 ? " (already complete)" : ""}`);
|
|
37065
37079
|
}
|
|
37066
|
-
const
|
|
37067
|
-
|
|
37080
|
+
const warnings = [];
|
|
37081
|
+
if (totalAdded > 0) {
|
|
37082
|
+
warnings.push(
|
|
37083
|
+
`Newly-added keys in ${seeded.join(", ")} are seeded with the ENGLISH source text so the file is installable immediately \u2014 they still need real translation.`
|
|
37084
|
+
);
|
|
37085
|
+
}
|
|
37086
|
+
if (ignored.length > 0) {
|
|
37087
|
+
warnings.push(
|
|
37088
|
+
`${ignored.map((f) => `translations/${f}`).join(", ")} is never read by install \u2014 the English UI shows the definitions' own \`description\` / \`label\`. Move any wording you want from it into the definitions, then delete it.`
|
|
37089
|
+
);
|
|
37090
|
+
}
|
|
37091
|
+
const warning = warnings.length > 0 ? warnings.join(" ") : void 0;
|
|
37068
37092
|
const sections = Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ");
|
|
37069
37093
|
return makeResult(
|
|
37070
37094
|
`Synced ${locales.length} locale file(s) from ${entries.length} translatable keys (${sections}). ${report.join("; ")}.${missingRequired > 0 ? ` Filled ${missingRequired} install-blocking role label(s).` : ""}`,
|
|
@@ -38971,7 +38995,8 @@ var resources = [
|
|
|
38971
38995
|
reference("registers", "Registers: accumulation (balance / turnover / info) and accounting (classic / compound) registers, registers.json, binding a document from its A / L columns (map, per-side debit/credit objects), and the reads. Load before authoring registers.json or an A / L column."),
|
|
38972
38996
|
reference("reactions", "Field-change reactions: `reactions` on an entity (formula rules and server `call` rules) that suggest a stored, editable value when the user edits another field; a bridge module's reactions on its host's entity (`reaction:<code>` in deps); dependent references. Load before authoring any reaction."),
|
|
38973
38997
|
reference("data-migration", "Migrating data from a legacy database."),
|
|
38974
|
-
reference("manifest", "manifest.json shape: moduleId UUID, semver, entities map,
|
|
38998
|
+
reference("manifest", "manifest.json shape: moduleId UUID, semver, entities map, supportedLocales (non-English only), security block."),
|
|
38999
|
+
reference("conventions", "Cross-cutting module rules: naming, structure, translations, upgrade migrations."),
|
|
38975
39000
|
reference("validation-checklist", "Final pre-pack self-review checklist covering every file type."),
|
|
38976
39001
|
// ── Canonical example module (skills/.../examples/simple-todo) ────────
|
|
38977
39002
|
example("manifest.json", "Canonical manifest.json."),
|
|
@@ -39334,7 +39359,7 @@ server.registerTool(
|
|
|
39334
39359
|
"dforge_translation_sync",
|
|
39335
39360
|
{
|
|
39336
39361
|
title: "Sync translation files",
|
|
39337
|
-
description: "PHASE 4: Generate or refresh translations/<locale>.json from what the module actually contains \u2014 entity + field labels (including trait-provided columns), views, menus, roles, actions, folders, settings. Existing translated text is NEVER overwritten; missing keys are seeded with the English source so the file is immediately installable. Defaults to
|
|
39362
|
+
description: "PHASE 4: Generate or refresh translations/<locale>.json from what the module actually contains \u2014 entity + field labels (including trait-provided columns), views, menus, roles, actions, folders, settings. Existing translated text is NEVER overwritten; missing keys are seeded with the English source so the file is immediately installable. Defaults to every manifest.supportedLocales entry \u2014 exactly the set install requires. Writes no English file: install never reads translations/en*.json, English text lives in the definitions (entity/field/role/setting `description`, menu/action/report `label`). Run it after any entity/view/role change: role labels are completeness-enforced and a missing one FAILS install.",
|
|
39338
39363
|
inputSchema: { ...translationSyncSchema, ...applyInput }
|
|
39339
39364
|
},
|
|
39340
39365
|
envelope(translationSync)
|
package/package.json
CHANGED
|
@@ -1,47 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
2
|
+
"name": "@dforge-core/dforge-mcp",
|
|
3
|
+
"version": "0.2.46-next",
|
|
4
|
+
"description": "MCP server for dForge module authoring. Exposes 35 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/iash44/dForge-core",
|
|
7
|
+
"packageManager": "pnpm@12.14.0+sha512.c18ba6d87993d3d164cb5e8948f32b2c06ee854487e06cd8d86781342fb3e5bb4634367034711e86802baa9c37bea906ce1929ad08d70884c97d801df13389a6",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dforge-core/dforge-mcp.git"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"dforge-mcp": "dist/server.js",
|
|
14
|
+
"dforge-install-skills": "scripts/install-skills.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/",
|
|
18
|
+
"docs/",
|
|
19
|
+
"resources/",
|
|
20
|
+
"skills/",
|
|
21
|
+
"scripts/install-skills.mjs",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"main": "dist/server.js",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"sync-schemas": "node scripts/vendor-schemas.cjs",
|
|
32
|
+
"prepublishOnly": "node scripts/vendor-schemas.cjs && tsup",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"install-skills": "node scripts/install-skills.mjs"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@dforge-core/dforge-cli": "^0.2.36",
|
|
39
|
+
"@dforge-core/metadata": "^0.0.46",
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
41
|
+
"zod": "^4.4.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^26.2.0",
|
|
45
|
+
"tsup": "^8.5.1",
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"vitest": "^4.1.10"
|
|
48
|
+
}
|
|
49
|
+
}
|