@tailor-platform/erp-kit 0.1.0 → 0.1.2

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +77 -50
  4. package/dist/cli.js +693 -553
  5. package/package.json +4 -2
  6. package/schemas/module/command.yml +1 -0
  7. package/schemas/module/model.yml +9 -0
  8. package/schemas/module/query.yml +53 -0
  9. package/skills/1-module-docs/SKILL.md +4 -0
  10. package/{rules/module-development → skills/1-module-docs/references}/structure.md +2 -7
  11. package/skills/2-module-feature-breakdown/SKILL.md +6 -0
  12. package/{rules/module-development → skills/2-module-feature-breakdown/references}/commands.md +0 -6
  13. package/{rules/module-development → skills/2-module-feature-breakdown/references}/models.md +0 -5
  14. package/skills/2-module-feature-breakdown/references/structure.md +22 -0
  15. package/skills/3-module-doc-review/SKILL.md +6 -0
  16. package/skills/3-module-doc-review/references/commands.md +54 -0
  17. package/skills/3-module-doc-review/references/models.md +29 -0
  18. package/{rules/module-development → skills/3-module-doc-review/references}/testing.md +0 -6
  19. package/skills/4-module-tdd-implementation/SKILL.md +24 -6
  20. package/skills/4-module-tdd-implementation/references/commands.md +45 -0
  21. package/{rules/sdk-best-practices → skills/4-module-tdd-implementation/references}/db-relations.md +0 -5
  22. package/{rules/module-development → skills/4-module-tdd-implementation/references}/errors.md +0 -5
  23. package/{rules/module-development → skills/4-module-tdd-implementation/references}/exports.md +0 -5
  24. package/skills/4-module-tdd-implementation/references/models.md +30 -0
  25. package/skills/4-module-tdd-implementation/references/structure.md +22 -0
  26. package/skills/4-module-tdd-implementation/references/testing.md +37 -0
  27. package/skills/5-module-implementation-review/SKILL.md +8 -0
  28. package/skills/5-module-implementation-review/references/commands.md +45 -0
  29. package/skills/5-module-implementation-review/references/errors.md +7 -0
  30. package/skills/5-module-implementation-review/references/exports.md +8 -0
  31. package/skills/5-module-implementation-review/references/models.md +30 -0
  32. package/skills/5-module-implementation-review/references/testing.md +29 -0
  33. package/skills/app-compose-1-requirement-analysis/SKILL.md +4 -0
  34. package/{rules/app-compose → skills/app-compose-1-requirement-analysis/references}/structure.md +0 -5
  35. package/skills/app-compose-2-requirements-breakdown/SKILL.md +7 -0
  36. package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-detailview.md +0 -6
  37. package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-form.md +0 -6
  38. package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-listview.md +0 -6
  39. package/skills/app-compose-2-requirements-breakdown/references/structure.md +27 -0
  40. package/skills/app-compose-3-doc-review/SKILL.md +4 -0
  41. package/skills/app-compose-3-doc-review/references/structure.md +27 -0
  42. package/skills/app-compose-4-design-mock/SKILL.md +8 -0
  43. package/{rules/app-compose/frontend → skills/app-compose-4-design-mock/references}/component.md +0 -5
  44. package/skills/app-compose-4-design-mock/references/screen-detailview.md +106 -0
  45. package/skills/app-compose-4-design-mock/references/screen-form.md +139 -0
  46. package/skills/app-compose-4-design-mock/references/screen-listview.md +153 -0
  47. package/skills/app-compose-4-design-mock/references/structure.md +27 -0
  48. package/skills/app-compose-5-design-mock-review/SKILL.md +7 -0
  49. package/skills/app-compose-5-design-mock-review/references/component.md +50 -0
  50. package/skills/app-compose-5-design-mock-review/references/screen-detailview.md +106 -0
  51. package/skills/app-compose-5-design-mock-review/references/screen-form.md +139 -0
  52. package/skills/app-compose-5-design-mock-review/references/screen-listview.md +153 -0
  53. package/skills/app-compose-6-implementation-spec/SKILL.md +5 -0
  54. package/{rules/app-compose/backend → skills/app-compose-6-implementation-spec/references}/auth.md +0 -6
  55. package/skills/app-compose-6-implementation-spec/references/structure.md +27 -0
  56. package/src/cli.ts +8 -90
  57. package/src/commands/app/index.ts +74 -0
  58. package/src/commands/check.test.ts +2 -1
  59. package/src/commands/check.ts +1 -0
  60. package/src/commands/init.test.ts +30 -19
  61. package/src/commands/init.ts +76 -43
  62. package/src/commands/module/index.ts +85 -0
  63. package/src/commands/module/list.test.ts +62 -0
  64. package/src/commands/module/list.ts +64 -0
  65. package/src/commands/scaffold.test.ts +5 -0
  66. package/src/commands/scaffold.ts +2 -1
  67. package/src/commands/sync-check.test.ts +28 -0
  68. package/src/commands/sync-check.ts +6 -0
  69. package/src/integration.test.ts +6 -8
  70. package/src/module.ts +4 -3
  71. package/src/modules/primitives/docs/models/Currency.md +4 -0
  72. package/src/modules/primitives/docs/models/ExchangeRate.md +4 -1
  73. package/src/modules/primitives/docs/models/Unit.md +4 -1
  74. package/src/modules/primitives/docs/models/UoMCategory.md +2 -0
  75. package/src/modules/primitives/index.ts +2 -2
  76. package/src/modules/primitives/module.ts +5 -3
  77. package/src/modules/primitives/permissions.ts +0 -2
  78. package/src/modules/primitives/{command → query}/convertAmount.test.ts +2 -19
  79. package/src/modules/primitives/query/convertAmount.ts +122 -0
  80. package/src/modules/primitives/{command → query}/convertQuantity.test.ts +2 -13
  81. package/src/modules/primitives/{command → query}/convertQuantity.ts +4 -6
  82. package/src/modules/shared/defineQuery.test.ts +28 -0
  83. package/src/modules/shared/defineQuery.ts +16 -0
  84. package/src/modules/shared/internal.ts +2 -1
  85. package/src/modules/shared/types.ts +8 -0
  86. package/src/modules/user-management/docs/models/AuditEvent.md +2 -0
  87. package/src/modules/user-management/docs/models/Permission.md +2 -0
  88. package/src/modules/user-management/docs/models/Role.md +2 -0
  89. package/src/modules/user-management/docs/models/RolePermission.md +2 -0
  90. package/src/modules/user-management/docs/models/User.md +2 -0
  91. package/src/modules/user-management/docs/models/UserRole.md +2 -0
  92. package/src/schemas.ts +1 -0
  93. package/rules/app-compose/frontend/auth.md +0 -55
  94. package/rules/app-compose/frontend/page.md +0 -86
  95. package/rules/module-development/cross-module-type-injection.md +0 -28
  96. package/rules/module-development/dependency-modules.md +0 -24
  97. package/rules/module-development/executors.md +0 -67
  98. package/rules/module-development/sync-vs-async-operations.md +0 -83
  99. package/rules/sdk-best-practices/sdk-docs.md +0 -14
  100. package/src/modules/primitives/command/convertAmount.ts +0 -126
  101. /package/src/modules/primitives/docs/{commands → queries}/ConvertAmount.md +0 -0
  102. /package/src/modules/primitives/docs/{commands → queries}/ConvertQuantity.md +0 -0
package/dist/cli.js CHANGED
@@ -1,421 +1,113 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { z as z2 } from "zod";
5
- import { defineCommand as defineCommand2, runMain, arg as arg2 } from "politty";
4
+ import { z as z4 } from "zod";
5
+ import { defineCommand as defineCommand4, runMain, arg as arg4 } from "politty";
6
6
 
7
- // src/mdschema.ts
8
- import path from "path";
7
+ // src/commands/init.ts
9
8
  import fs from "fs";
10
- import { execFile } from "child_process";
11
- import { createRequire } from "module";
12
- var require2 = createRequire(import.meta.url);
13
- function getMdschemaBin() {
14
- const pkgPath = require2.resolve("@jackchuka/mdschema/package.json");
15
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
16
- const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
17
- if (!bin) {
18
- throw new Error("Could not resolve mdschema binary from package.json bin field");
19
- }
20
- return path.join(path.dirname(pkgPath), bin);
21
- }
22
- function runMdschema(args, cwd2) {
23
- return new Promise((resolve4) => {
24
- execFile(
25
- getMdschemaBin(),
26
- args,
27
- { encoding: "utf-8", cwd: cwd2, timeout: 3e4 },
28
- (error, stdout, stderr) => {
29
- if (error) {
30
- const execError = error;
31
- resolve4({
32
- exitCode: execError.code === "ENOENT" ? 127 : execError.status ?? 1,
33
- stdout: stdout ?? "",
34
- stderr: stderr ?? ""
35
- });
36
- } else {
37
- resolve4({ exitCode: 0, stdout: stdout ?? "", stderr: stderr ?? "" });
38
- }
39
- }
40
- );
41
- });
42
- }
43
-
44
- // src/schemas.ts
45
- import path3 from "path";
46
-
47
- // src/util.ts
48
9
  import path2 from "path";
49
- var PACKAGE_ROOT = path2.resolve(import.meta.dirname, "..");
10
+ import chalk from "chalk";
50
11
 
51
- // src/schemas.ts
52
- var SCHEMAS_ROOT = path3.join(PACKAGE_ROOT, "schemas");
53
- var MODULE_SCHEMAS = {
54
- module: path3.join(SCHEMAS_ROOT, "module", "module.yml"),
55
- command: path3.join(SCHEMAS_ROOT, "module", "command.yml"),
56
- model: path3.join(SCHEMAS_ROOT, "module", "model.yml"),
57
- feature: path3.join(SCHEMAS_ROOT, "module", "feature.yml")
58
- };
59
- var APP_COMPOSE_SCHEMAS = {
60
- requirements: path3.join(SCHEMAS_ROOT, "app-compose", "requirements.yml"),
61
- actors: path3.join(SCHEMAS_ROOT, "app-compose", "actors.yml"),
62
- "business-flow": path3.join(SCHEMAS_ROOT, "app-compose", "business-flow.yml"),
63
- story: path3.join(SCHEMAS_ROOT, "app-compose", "story.yml"),
64
- screen: path3.join(SCHEMAS_ROOT, "app-compose", "screen.yml"),
65
- resolver: path3.join(SCHEMAS_ROOT, "app-compose", "resolver.yml")
66
- };
67
- var ALL_SCHEMAS = {
68
- ...MODULE_SCHEMAS,
69
- ...APP_COMPOSE_SCHEMAS
70
- };
12
+ // src/util.ts
13
+ import path from "path";
14
+ var PACKAGE_ROOT = path.resolve(import.meta.dirname, "..");
71
15
 
72
- // src/commands/check.ts
73
- function buildCheckTargets(config) {
74
- const targets = [];
75
- if (config.modulesRoot) {
76
- const m = config.modulesRoot;
77
- targets.push(
78
- { glob: `${m}/[a-zA-Z]*/docs/features/*.md`, schemaKey: "feature" },
79
- { glob: `${m}/[a-zA-Z]*/docs/commands/*.md`, schemaKey: "command" },
80
- { glob: `${m}/[a-zA-Z]*/docs/models/*.md`, schemaKey: "model" },
81
- { glob: `${m}/[a-zA-Z]*/README.md`, schemaKey: "module" }
82
- );
83
- }
84
- if (config.appRoot) {
85
- const a = config.appRoot;
86
- targets.push(
87
- { glob: `${a}/[a-zA-Z]*/README.md`, schemaKey: "requirements" },
88
- { glob: `${a}/[a-zA-Z]*/docs/actors/*.md`, schemaKey: "actors" },
89
- { glob: `${a}/[a-zA-Z]*/docs/business-flow/*/README.md`, schemaKey: "business-flow" },
90
- { glob: `${a}/[a-zA-Z]*/docs/business-flow/*/story/*.md`, schemaKey: "story" },
91
- { glob: `${a}/[a-zA-Z]*/docs/screen/*.md`, schemaKey: "screen" },
92
- { glob: `${a}/[a-zA-Z]*/docs/resolver/*.md`, schemaKey: "resolver" }
93
- );
94
- }
95
- return targets;
96
- }
97
- async function runCheck(config, cwd2) {
98
- const targets = buildCheckTargets(config);
99
- const results = await Promise.all(
100
- targets.map(async (target) => {
101
- const schemaPath = ALL_SCHEMAS[target.schemaKey];
102
- if (!schemaPath) {
103
- console.error(`Unknown schema key: ${target.schemaKey}`);
104
- return 2;
16
+ // src/commands/init.ts
17
+ var SKILLS_SRC = path2.join(PACKAGE_ROOT, "skills");
18
+ var FRAMEWORK_SKILLS = [
19
+ "1-module-docs",
20
+ "2-module-feature-breakdown",
21
+ "3-module-doc-review",
22
+ "4-module-tdd-implementation",
23
+ "5-module-implementation-review",
24
+ "app-compose-1-requirement-analysis",
25
+ "app-compose-2-requirements-breakdown",
26
+ "app-compose-3-doc-review",
27
+ "app-compose-4-design-mock",
28
+ "app-compose-5-design-mock-review",
29
+ "app-compose-6-implementation-spec",
30
+ "mock-scenario"
31
+ ];
32
+ function copyDirectoryRecursive(srcDir, destDir, force) {
33
+ let copied = 0;
34
+ let skipped = 0;
35
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
36
+ const srcPath = path2.join(srcDir, entry.name);
37
+ const destPath = path2.join(destDir, entry.name);
38
+ if (entry.isDirectory()) {
39
+ const sub = copyDirectoryRecursive(srcPath, destPath, force);
40
+ copied += sub.copied;
41
+ skipped += sub.skipped;
42
+ } else {
43
+ if (!force && fs.existsSync(destPath)) {
44
+ skipped++;
45
+ continue;
105
46
  }
106
- const { exitCode, stdout, stderr } = await runMdschema(
107
- ["check", target.glob, "--schema", schemaPath],
108
- cwd2
109
- );
110
- if (stdout.trim()) console.log(stdout);
111
- if (stderr.trim()) console.error(stderr);
112
- return exitCode;
113
- })
114
- );
115
- if (results.includes(2)) return 2;
116
- return results.some((code) => code !== 0) ? 1 : 0;
117
- }
118
-
119
- // src/commands/sync-check.ts
120
- import path4 from "path";
121
- import fg from "fast-glob";
122
- import chalk from "chalk";
123
- function moduleCategories(root) {
124
- return [
125
- {
126
- name: "command",
127
- sourcePattern: `${root}/*/command/*.ts`,
128
- docPattern: `${root}/*/docs/commands/*.md`,
129
- exclusions: [/\.test\.ts$/]
130
- },
131
- {
132
- name: "model",
133
- sourcePattern: `${root}/*/db/*.ts`,
134
- docPattern: `${root}/*/docs/models/*.md`,
135
- exclusions: [/\.test\.ts$/, /^index\.ts$/]
47
+ fs.mkdirSync(destDir, { recursive: true });
48
+ fs.copyFileSync(srcPath, destPath);
49
+ copied++;
136
50
  }
137
- ];
51
+ }
52
+ return { copied, skipped };
138
53
  }
139
- function appComposeCategories(root) {
140
- return [
141
- {
142
- name: "resolver",
143
- sourcePattern: `${root}/*/backend/src/modules/**/resolvers/*.ts`,
144
- docPattern: `${root}/*/docs/resolver/*.md`,
145
- exclusions: [/\.test\.ts$/, /^index\.ts$/]
54
+ function runInit(cwd4, force) {
55
+ console.log(chalk.bold("erp-kit init\n"));
56
+ const skillsDest = path2.join(cwd4, ".agents", "skills");
57
+ let copiedCount = 0;
58
+ let skippedCount = 0;
59
+ for (const skill of FRAMEWORK_SKILLS) {
60
+ const srcSkillDir = path2.join(SKILLS_SRC, skill);
61
+ if (!fs.existsSync(srcSkillDir)) continue;
62
+ const destDir = path2.join(skillsDest, skill);
63
+ const result = copyDirectoryRecursive(srcSkillDir, destDir, force);
64
+ copiedCount += result.copied;
65
+ if (result.skipped > 0) {
66
+ console.log(chalk.yellow(` Skipped ${skill}/ (${result.skipped} existing files)`));
67
+ skippedCount += result.skipped;
146
68
  }
147
- ];
148
- }
149
- function shouldExclude(fileName, exclusions) {
150
- return exclusions.some((pattern) => pattern.test(fileName));
151
- }
152
- async function runSyncCheck(config, cwd2) {
153
- const errors = [];
154
- let totalSources = 0;
155
- let totalDocs = 0;
156
- const allCategories = [];
157
- if (config.modulesRoot) {
158
- allCategories.push(...moduleCategories(config.modulesRoot));
159
- }
160
- if (config.appRoot) {
161
- allCategories.push(...appComposeCategories(config.appRoot));
162
69
  }
163
- for (const category of allCategories) {
164
- const sources = await fg(category.sourcePattern, { cwd: cwd2 });
165
- const docs = await fg(category.docPattern, { cwd: cwd2 });
166
- const sourceBasenames = /* @__PURE__ */ new Map();
167
- const docBasenames = /* @__PURE__ */ new Map();
168
- for (const sourcePath of sources) {
169
- const fileName = path4.basename(sourcePath);
170
- if (shouldExclude(fileName, category.exclusions)) continue;
171
- const basename2 = path4.basename(sourcePath, path4.extname(sourcePath));
172
- sourceBasenames.set(basename2.toLowerCase(), sourcePath);
173
- }
174
- for (const docPath of docs) {
175
- const basename2 = path4.basename(docPath, path4.extname(docPath));
176
- docBasenames.set(basename2.toLowerCase(), docPath);
177
- }
178
- for (const [basename2, sourcePath] of sourceBasenames) {
179
- if (!docBasenames.has(basename2)) {
180
- errors.push({
181
- type: "missing-doc",
182
- category: category.name,
183
- sourcePath,
184
- expectedBasename: basename2
185
- });
186
- }
187
- }
188
- for (const [basename2, docPath] of docBasenames) {
189
- if (!sourceBasenames.has(basename2)) {
190
- errors.push({
191
- type: "orphaned-doc",
192
- category: category.name,
193
- docPath,
194
- expectedBasename: basename2
195
- });
196
- }
197
- }
198
- totalSources += sourceBasenames.size;
199
- totalDocs += docBasenames.size;
70
+ console.log(chalk.green(` Copied ${copiedCount} skill files to .agents/skills/`));
71
+ if (skippedCount > 0) {
72
+ console.log(
73
+ chalk.yellow(` Skipped ${skippedCount} existing files (use --force to overwrite)`)
74
+ );
200
75
  }
201
- return {
202
- exitCode: errors.length > 0 ? 1 : 0,
203
- errors,
204
- summary: {
205
- categoriesChecked: allCategories.length,
206
- totalSources,
207
- totalDocs
208
- }
209
- };
210
- }
211
- function formatSyncCheckReport(result) {
212
- const lines = [];
213
- lines.push(chalk.bold("docs-sync-check: Checking source-documentation correspondence...\n"));
214
- if (result.errors.length > 0) {
215
- lines.push(chalk.red.bold("Errors:\n"));
216
- const byCategory = /* @__PURE__ */ new Map();
217
- for (const error of result.errors) {
218
- const existing = byCategory.get(error.category) ?? [];
219
- existing.push(error);
220
- byCategory.set(error.category, existing);
76
+ const claudeSkills = path2.join(cwd4, ".claude", "skills");
77
+ const relTarget = path2.relative(path2.dirname(claudeSkills), skillsDest);
78
+ const claudeSkillsExists = (() => {
79
+ try {
80
+ fs.lstatSync(claudeSkills);
81
+ return true;
82
+ } catch {
83
+ return false;
221
84
  }
222
- for (const [category, categoryErrors] of byCategory) {
223
- lines.push(chalk.cyan(` Category: ${category}
224
- `));
225
- for (const error of categoryErrors) {
226
- if (error.type === "missing-doc") {
227
- lines.push(` ${chalk.red(error.sourcePath)}`);
228
- lines.push(` ${chalk.yellow("Missing documentation for:")} ${error.expectedBasename}`);
229
- } else {
230
- lines.push(` ${chalk.red(error.docPath)}`);
231
- lines.push(
232
- ` ${chalk.yellow("Orphaned documentation:")} no source file for ${error.expectedBasename}`
233
- );
234
- }
235
- lines.push("");
85
+ })();
86
+ if (claudeSkillsExists) {
87
+ const stat2 = fs.lstatSync(claudeSkills);
88
+ if (stat2.isSymbolicLink()) {
89
+ const current = fs.readlinkSync(claudeSkills);
90
+ if (current === relTarget) {
91
+ console.log(chalk.green(" .claude/skills -> .agents/skills/ (already linked)"));
92
+ } else if (force) {
93
+ fs.unlinkSync(claudeSkills);
94
+ fs.symlinkSync(relTarget, claudeSkills);
95
+ console.log(chalk.green(" .claude/skills -> .agents/skills/ (relinked)"));
96
+ } else {
97
+ console.log(
98
+ chalk.yellow(` Skipped .claude/skills (symlink exists -> ${current}, use --force)`)
99
+ );
236
100
  }
101
+ } else {
102
+ console.log(chalk.yellow(" Skipped .claude/skills (directory exists, not a symlink)"));
237
103
  }
238
104
  } else {
239
- lines.push(chalk.green("All source files have corresponding documentation.\n"));
240
- }
241
- lines.push(chalk.bold("Summary:"));
242
- lines.push(` Categories checked: ${result.summary.categoriesChecked}`);
243
- lines.push(
244
- ` Source files: ${result.summary.totalSources}, Doc files: ${result.summary.totalDocs}`
245
- );
246
- if (result.errors.length > 0) {
247
- lines.push(chalk.red(` Errors: ${result.errors.length}`));
248
- lines.push("");
249
- lines.push(chalk.red.bold(`docs-sync-check failed with ${result.errors.length} error(s).`));
250
- } else {
251
- lines.push(chalk.green(" Errors: 0"));
252
- lines.push("");
253
- lines.push(chalk.green.bold("docs-sync-check passed."));
105
+ fs.mkdirSync(path2.dirname(claudeSkills), { recursive: true });
106
+ fs.symlinkSync(relTarget, claudeSkills);
107
+ console.log(chalk.green(" .claude/skills -> .agents/skills/ (linked)"));
254
108
  }
255
- return lines.join("\n");
256
- }
257
-
258
- // src/commands/scaffold.ts
259
- import path5 from "path";
260
- import fs2 from "fs";
261
- var MODULE_TYPES = ["module", "feature", "command", "model"];
262
- var APP_TYPES = [
263
- "requirements",
264
- "actors",
265
- "business-flow",
266
- "story",
267
- "screen",
268
- "resolver"
269
- ];
270
- var ALL_TYPES = [...MODULE_TYPES, ...APP_TYPES];
271
- var MODULE_DIR_MAP = {
272
- feature: "docs/features",
273
- command: "docs/commands",
274
- model: "docs/models"
275
- };
276
- var APP_DIR_MAP = {
277
- actors: "docs/actors",
278
- "business-flow": "docs/business-flow",
279
- screen: "docs/screen",
280
- resolver: "docs/resolver"
281
- };
282
- function isModuleType(type) {
283
- return MODULE_TYPES.includes(type);
284
- }
285
- function resolveScaffoldPath(type, parentName, name, root) {
286
- if (type === "module" || type === "requirements") {
287
- return path5.join(root, parentName, "README.md");
288
- }
289
- if (!name) {
290
- throw new Error(`Name is required for scaffold type "${type}"`);
291
- }
292
- if (type === "business-flow") {
293
- return path5.join(root, parentName, "docs/business-flow", name, "README.md");
294
- }
295
- if (type === "story") {
296
- const parts = name.split("/");
297
- if (parts.length !== 2) {
298
- throw new Error(
299
- `Story name must be "<flow>/<story>" (e.g., "onboarding/admin--create-user")`
300
- );
301
- }
302
- return path5.join(root, parentName, "docs/business-flow", parts[0], "story", `${parts[1]}.md`);
303
- }
304
- if (MODULE_DIR_MAP[type]) {
305
- return path5.join(root, parentName, MODULE_DIR_MAP[type], `${name}.md`);
306
- }
307
- if (APP_DIR_MAP[type]) {
308
- return path5.join(root, parentName, APP_DIR_MAP[type], `${name}.md`);
309
- }
310
- throw new Error(`Unknown scaffold type: ${type}`);
311
- }
312
- async function runScaffold(type, parentName, name, root, cwd2) {
313
- const outputPath = resolveScaffoldPath(type, parentName, name, root);
314
- const absoluteOutput = path5.resolve(cwd2, outputPath);
315
- if (fs2.existsSync(absoluteOutput)) {
316
- console.error(`File already exists: ${outputPath}`);
317
- return 1;
318
- }
319
- const schemaPath = ALL_SCHEMAS[type];
320
- if (!schemaPath) {
321
- console.error(`No schema found for type: ${type}`);
322
- return 2;
323
- }
324
- try {
325
- fs2.mkdirSync(path5.dirname(absoluteOutput), { recursive: true });
326
- } catch (err) {
327
- console.error(
328
- `Failed to create directory: ${err instanceof Error ? err.message : String(err)}`
329
- );
330
- return 1;
331
- }
332
- const { exitCode, stdout, stderr } = await runMdschema(
333
- ["generate", "--schema", schemaPath, "--output", absoluteOutput],
334
- cwd2
335
- );
336
- if (stdout.trim()) console.log(stdout);
337
- if (stderr.trim()) console.error(stderr);
338
- return exitCode;
339
- }
340
-
341
- // src/commands/init.ts
342
- import fs3 from "fs";
343
- import path6 from "path";
344
- import chalk2 from "chalk";
345
- var SKILLS_SRC = path6.join(PACKAGE_ROOT, "skills");
346
- var RULES_SRC = path6.join(PACKAGE_ROOT, "rules");
347
- var FRAMEWORK_SKILLS = [
348
- "1-module-docs",
349
- "2-module-feature-breakdown",
350
- "3-module-doc-review",
351
- "4-module-tdd-implementation",
352
- "5-module-implementation-review",
353
- "app-compose-1-requirement-analysis",
354
- "app-compose-2-requirements-breakdown",
355
- "app-compose-3-doc-review",
356
- "app-compose-4-design-mock",
357
- "app-compose-5-design-mock-review",
358
- "app-compose-6-implementation-spec",
359
- "mock-scenario"
360
- ];
361
- function runInit(cwd2, force) {
362
- console.log(chalk2.bold("erp-kit init\n"));
363
- const skillsDest = path6.join(cwd2, ".agents", "skills");
364
- let copiedCount = 0;
365
- let skippedCount = 0;
366
- for (const skill of FRAMEWORK_SKILLS) {
367
- const srcSkill = path6.join(SKILLS_SRC, skill, "SKILL.md");
368
- const destDir = path6.join(skillsDest, skill);
369
- const destSkill = path6.join(destDir, "SKILL.md");
370
- if (!fs3.existsSync(srcSkill)) continue;
371
- if (!force && fs3.existsSync(destSkill)) {
372
- console.log(chalk2.yellow(` Skipped ${skill}/SKILL.md (already exists)`));
373
- skippedCount++;
374
- continue;
375
- }
376
- fs3.mkdirSync(destDir, { recursive: true });
377
- fs3.copyFileSync(srcSkill, destSkill);
378
- copiedCount++;
379
- }
380
- console.log(chalk2.green(` Copied ${copiedCount} framework skills to .agents/skills/`));
381
- if (skippedCount > 0) {
382
- console.log(
383
- chalk2.yellow(` Skipped ${skippedCount} existing skills (use --force to overwrite)`)
384
- );
385
- }
386
- const rulesDest = path6.join(cwd2, ".agents", "rules");
387
- let rulesCount = 0;
388
- let rulesSkipped = 0;
389
- if (fs3.existsSync(RULES_SRC)) {
390
- const copyRulesRecursive = (srcDir, destDir) => {
391
- for (const entry of fs3.readdirSync(srcDir, { withFileTypes: true })) {
392
- const srcPath = path6.join(srcDir, entry.name);
393
- const destPath = path6.join(destDir, entry.name);
394
- if (entry.isDirectory()) {
395
- copyRulesRecursive(srcPath, destPath);
396
- } else {
397
- if (!force && fs3.existsSync(destPath)) {
398
- const rel = path6.relative(rulesDest, destPath);
399
- console.log(chalk2.yellow(` Skipped rule ${rel} (already exists)`));
400
- rulesSkipped++;
401
- continue;
402
- }
403
- fs3.mkdirSync(destDir, { recursive: true });
404
- fs3.copyFileSync(srcPath, destPath);
405
- rulesCount++;
406
- }
407
- }
408
- };
409
- copyRulesRecursive(RULES_SRC, rulesDest);
410
- }
411
- console.log(chalk2.green(` Copied ${rulesCount} framework rules to .agents/rules/`));
412
- if (rulesSkipped > 0) {
413
- console.log(
414
- chalk2.yellow(` Skipped ${rulesSkipped} existing rules (use --force to overwrite)`)
415
- );
416
- }
417
- console.log(chalk2.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
418
- return 0;
109
+ console.log(chalk.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
110
+ return 0;
419
111
  }
420
112
 
421
113
  // src/commands/mock/index.ts
@@ -600,12 +292,12 @@ Reverse proxy listening on http://localhost:${port}`);
600
292
  // src/commands/mock/validate.ts
601
293
  import { readdir, readFile, stat } from "fs/promises";
602
294
  import { join as join2, resolve as resolve3, relative as relative2, dirname as dirname2, basename } from "path";
603
- import chalk3 from "chalk";
295
+ import chalk2 from "chalk";
604
296
  function pass(msg) {
605
- console.log(chalk3.green(`\u2713 ${msg}`));
297
+ console.log(chalk2.green(`\u2713 ${msg}`));
606
298
  }
607
299
  function fail(ctx, msg) {
608
- console.log(chalk3.red(`\u2717 ${msg}`));
300
+ console.log(chalk2.red(`\u2717 ${msg}`));
609
301
  ctx.failures++;
610
302
  }
611
303
  async function subdirs(dir) {
@@ -663,232 +355,680 @@ function checkResponseQuality(ctx, data, label) {
663
355
  }
664
356
  }
665
357
  }
666
- function checkDatabucketRefs(ctx, data, label) {
667
- const bucketIds = new Set((data.data ?? []).map((d) => d.id));
668
- for (const route of data.routes ?? []) {
669
- for (const resp of route.responses ?? []) {
670
- if (resp.bodyType === "DATABUCKET") {
671
- const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
672
- if (resp.databucketID && bucketIds.has(resp.databucketID)) {
673
- pass(`${respLabel}: databucket "${resp.databucketID}" exists`);
358
+ function checkDatabucketRefs(ctx, data, label) {
359
+ const bucketIds = new Set((data.data ?? []).map((d) => d.id));
360
+ for (const route of data.routes ?? []) {
361
+ for (const resp of route.responses ?? []) {
362
+ if (resp.bodyType === "DATABUCKET") {
363
+ const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
364
+ if (resp.databucketID && bucketIds.has(resp.databucketID)) {
365
+ pass(`${respLabel}: databucket "${resp.databucketID}" exists`);
366
+ } else {
367
+ fail(ctx, `${respLabel}: databucketID "${resp.databucketID}" not found in data array`);
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ async function discoverAllScenarios(mocksDir) {
374
+ const scenarios = [];
375
+ const providers = await subdirs(mocksDir);
376
+ for (const provider of providers) {
377
+ const providerDir = join2(mocksDir, provider);
378
+ for (const scenario of await subdirs(providerDir)) {
379
+ scenarios.push(`${provider}/${scenario}`);
380
+ }
381
+ }
382
+ return scenarios;
383
+ }
384
+ function resolveScenarioDir(arg5) {
385
+ const abs = resolve3(arg5);
386
+ if (basename(abs) === "mock.json") return dirname2(abs);
387
+ return abs;
388
+ }
389
+ async function validateScenario(ctx, scenarioDir, mocksDir) {
390
+ const label = relative2(mocksDir, scenarioDir);
391
+ console.log(chalk2.bold(`
392
+ \u2500\u2500 ${label} \u2500\u2500`));
393
+ let entries;
394
+ try {
395
+ entries = await readdir(scenarioDir);
396
+ } catch {
397
+ fail(ctx, `${label}: directory not found`);
398
+ return;
399
+ }
400
+ const hasMock = checkStructure(ctx, entries, label);
401
+ if (!hasMock) return;
402
+ const mockPath = join2(scenarioDir, "mock.json");
403
+ let data;
404
+ try {
405
+ data = JSON.parse(await readFile(mockPath, "utf-8"));
406
+ } catch (err) {
407
+ const errMsg = err instanceof Error ? err.message : String(err);
408
+ fail(ctx, `${label}: invalid JSON \u2014 ${errMsg}`);
409
+ return;
410
+ }
411
+ await checkSchema(ctx, data, label);
412
+ checkResponseQuality(ctx, data, label);
413
+ checkDatabucketRefs(ctx, data, label);
414
+ }
415
+ async function runMockValidate(mocksRoot, paths) {
416
+ const ctx = { failures: 0 };
417
+ const mocksDir = resolve3(mocksRoot);
418
+ const targets = paths.length > 0 ? paths.map(resolveScenarioDir) : (await discoverAllScenarios(mocksDir)).map((s) => join2(mocksDir, s));
419
+ if (targets.length === 0) {
420
+ fail(ctx, "No scenarios found under mocks/");
421
+ return 1;
422
+ }
423
+ console.log(chalk2.bold("\nValidating mock configs...\n"));
424
+ for (const target of targets) {
425
+ await validateScenario(ctx, target, mocksDir);
426
+ }
427
+ console.log(chalk2.bold("\n\u2500\u2500 summary \u2500\u2500"));
428
+ if (ctx.failures === 0) {
429
+ console.log(chalk2.green("\u2713 All checks passed"));
430
+ } else {
431
+ console.log(chalk2.red(`\u2717 ${ctx.failures} check(s) failed`));
432
+ }
433
+ return ctx.failures === 0 ? 0 : 1;
434
+ }
435
+
436
+ // src/commands/mock/index.ts
437
+ var startCommand = defineCommand({
438
+ name: "start",
439
+ description: "Start mock API servers with reverse proxy",
440
+ args: z.object({
441
+ mocksRoot: arg(z.string().default("./mocks"), {
442
+ description: "Path to mocks directory"
443
+ }),
444
+ port: arg(z.coerce.number().default(3e3), {
445
+ alias: "p",
446
+ description: "Reverse proxy port"
447
+ }),
448
+ filter: arg(z.array(z.string()).default([]), {
449
+ positional: true,
450
+ description: "Filter by provider or provider/scenario"
451
+ })
452
+ }),
453
+ run: async (args) => {
454
+ const exitCode = await runMockStart(args.mocksRoot, args.filter, args.port);
455
+ process.exit(exitCode);
456
+ }
457
+ });
458
+ var validateCommand = defineCommand({
459
+ name: "validate",
460
+ description: "Validate mock scenario configs",
461
+ args: z.object({
462
+ mocksRoot: arg(z.string().default("./mocks"), {
463
+ description: "Path to mocks directory"
464
+ }),
465
+ paths: arg(z.array(z.string()).default([]), {
466
+ positional: true,
467
+ description: "Specific scenario paths to validate"
468
+ })
469
+ }),
470
+ run: async (args) => {
471
+ const exitCode = await runMockValidate(args.mocksRoot, args.paths);
472
+ process.exit(exitCode);
473
+ }
474
+ });
475
+ var mockCommand = defineCommand({
476
+ name: "mock",
477
+ description: "Mock API server management",
478
+ subCommands: {
479
+ start: startCommand,
480
+ validate: validateCommand
481
+ }
482
+ });
483
+
484
+ // src/commands/module/index.ts
485
+ import { z as z2 } from "zod";
486
+ import { defineCommand as defineCommand2, arg as arg2 } from "politty";
487
+
488
+ // src/mdschema.ts
489
+ import path3 from "path";
490
+ import fs2 from "fs";
491
+ import { execFile } from "child_process";
492
+ import { createRequire } from "module";
493
+ var require2 = createRequire(import.meta.url);
494
+ function getMdschemaBin() {
495
+ const pkgPath = require2.resolve("@jackchuka/mdschema/package.json");
496
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
497
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
498
+ if (!bin) {
499
+ throw new Error("Could not resolve mdschema binary from package.json bin field");
500
+ }
501
+ return path3.join(path3.dirname(pkgPath), bin);
502
+ }
503
+ function runMdschema(args, cwd4) {
504
+ return new Promise((resolve4) => {
505
+ execFile(
506
+ getMdschemaBin(),
507
+ args,
508
+ { encoding: "utf-8", cwd: cwd4, timeout: 3e4 },
509
+ (error, stdout, stderr) => {
510
+ if (error) {
511
+ const execError = error;
512
+ resolve4({
513
+ exitCode: execError.code === "ENOENT" ? 127 : execError.status ?? 1,
514
+ stdout: stdout ?? "",
515
+ stderr: stderr ?? ""
516
+ });
517
+ } else {
518
+ resolve4({ exitCode: 0, stdout: stdout ?? "", stderr: stderr ?? "" });
519
+ }
520
+ }
521
+ );
522
+ });
523
+ }
524
+
525
+ // src/schemas.ts
526
+ import path4 from "path";
527
+ var SCHEMAS_ROOT = path4.join(PACKAGE_ROOT, "schemas");
528
+ var MODULE_SCHEMAS = {
529
+ module: path4.join(SCHEMAS_ROOT, "module", "module.yml"),
530
+ command: path4.join(SCHEMAS_ROOT, "module", "command.yml"),
531
+ model: path4.join(SCHEMAS_ROOT, "module", "model.yml"),
532
+ feature: path4.join(SCHEMAS_ROOT, "module", "feature.yml"),
533
+ query: path4.join(SCHEMAS_ROOT, "module", "query.yml")
534
+ };
535
+ var APP_COMPOSE_SCHEMAS = {
536
+ requirements: path4.join(SCHEMAS_ROOT, "app-compose", "requirements.yml"),
537
+ actors: path4.join(SCHEMAS_ROOT, "app-compose", "actors.yml"),
538
+ "business-flow": path4.join(SCHEMAS_ROOT, "app-compose", "business-flow.yml"),
539
+ story: path4.join(SCHEMAS_ROOT, "app-compose", "story.yml"),
540
+ screen: path4.join(SCHEMAS_ROOT, "app-compose", "screen.yml"),
541
+ resolver: path4.join(SCHEMAS_ROOT, "app-compose", "resolver.yml")
542
+ };
543
+ var ALL_SCHEMAS = {
544
+ ...MODULE_SCHEMAS,
545
+ ...APP_COMPOSE_SCHEMAS
546
+ };
547
+
548
+ // src/commands/check.ts
549
+ function buildCheckTargets(config) {
550
+ const targets = [];
551
+ if (config.modulesRoot) {
552
+ const m = config.modulesRoot;
553
+ targets.push(
554
+ { glob: `${m}/[a-zA-Z]*/docs/features/*.md`, schemaKey: "feature" },
555
+ { glob: `${m}/[a-zA-Z]*/docs/commands/*.md`, schemaKey: "command" },
556
+ { glob: `${m}/[a-zA-Z]*/docs/models/*.md`, schemaKey: "model" },
557
+ { glob: `${m}/[a-zA-Z]*/docs/queries/*.md`, schemaKey: "query" },
558
+ { glob: `${m}/[a-zA-Z]*/README.md`, schemaKey: "module" }
559
+ );
560
+ }
561
+ if (config.appRoot) {
562
+ const a = config.appRoot;
563
+ targets.push(
564
+ { glob: `${a}/[a-zA-Z]*/README.md`, schemaKey: "requirements" },
565
+ { glob: `${a}/[a-zA-Z]*/docs/actors/*.md`, schemaKey: "actors" },
566
+ { glob: `${a}/[a-zA-Z]*/docs/business-flow/*/README.md`, schemaKey: "business-flow" },
567
+ { glob: `${a}/[a-zA-Z]*/docs/business-flow/*/story/*.md`, schemaKey: "story" },
568
+ { glob: `${a}/[a-zA-Z]*/docs/screen/*.md`, schemaKey: "screen" },
569
+ { glob: `${a}/[a-zA-Z]*/docs/resolver/*.md`, schemaKey: "resolver" }
570
+ );
571
+ }
572
+ return targets;
573
+ }
574
+ async function runCheck(config, cwd4) {
575
+ const targets = buildCheckTargets(config);
576
+ const results = await Promise.all(
577
+ targets.map(async (target) => {
578
+ const schemaPath = ALL_SCHEMAS[target.schemaKey];
579
+ if (!schemaPath) {
580
+ console.error(`Unknown schema key: ${target.schemaKey}`);
581
+ return 2;
582
+ }
583
+ const { exitCode, stdout, stderr } = await runMdschema(
584
+ ["check", target.glob, "--schema", schemaPath],
585
+ cwd4
586
+ );
587
+ if (stdout.trim()) console.log(stdout);
588
+ if (stderr.trim()) console.error(stderr);
589
+ return exitCode;
590
+ })
591
+ );
592
+ if (results.includes(2)) return 2;
593
+ return results.some((code) => code !== 0) ? 1 : 0;
594
+ }
595
+
596
+ // src/commands/sync-check.ts
597
+ import path5 from "path";
598
+ import fg from "fast-glob";
599
+ import chalk3 from "chalk";
600
+ function moduleCategories(root) {
601
+ return [
602
+ {
603
+ name: "command",
604
+ sourcePattern: `${root}/*/command/*.ts`,
605
+ docPattern: `${root}/*/docs/commands/*.md`,
606
+ exclusions: [/\.test\.ts$/]
607
+ },
608
+ {
609
+ name: "model",
610
+ sourcePattern: `${root}/*/db/*.ts`,
611
+ docPattern: `${root}/*/docs/models/*.md`,
612
+ exclusions: [/\.test\.ts$/, /^index\.ts$/]
613
+ },
614
+ {
615
+ name: "query",
616
+ sourcePattern: `${root}/*/query/*.ts`,
617
+ docPattern: `${root}/*/docs/queries/*.md`,
618
+ exclusions: [/\.test\.ts$/]
619
+ }
620
+ ];
621
+ }
622
+ function appComposeCategories(root) {
623
+ return [
624
+ {
625
+ name: "resolver",
626
+ sourcePattern: `${root}/*/backend/src/modules/**/resolvers/*.ts`,
627
+ docPattern: `${root}/*/docs/resolver/*.md`,
628
+ exclusions: [/\.test\.ts$/, /^index\.ts$/]
629
+ }
630
+ ];
631
+ }
632
+ function shouldExclude(fileName, exclusions) {
633
+ return exclusions.some((pattern) => pattern.test(fileName));
634
+ }
635
+ async function runSyncCheck(config, cwd4) {
636
+ const errors = [];
637
+ let totalSources = 0;
638
+ let totalDocs = 0;
639
+ const allCategories = [];
640
+ if (config.modulesRoot) {
641
+ allCategories.push(...moduleCategories(config.modulesRoot));
642
+ }
643
+ if (config.appRoot) {
644
+ allCategories.push(...appComposeCategories(config.appRoot));
645
+ }
646
+ for (const category of allCategories) {
647
+ const sources = await fg(category.sourcePattern, { cwd: cwd4 });
648
+ const docs = await fg(category.docPattern, { cwd: cwd4 });
649
+ const sourceBasenames = /* @__PURE__ */ new Map();
650
+ const docBasenames = /* @__PURE__ */ new Map();
651
+ for (const sourcePath of sources) {
652
+ const fileName = path5.basename(sourcePath);
653
+ if (shouldExclude(fileName, category.exclusions)) continue;
654
+ const basename2 = path5.basename(sourcePath, path5.extname(sourcePath));
655
+ sourceBasenames.set(basename2.toLowerCase(), sourcePath);
656
+ }
657
+ for (const docPath of docs) {
658
+ const basename2 = path5.basename(docPath, path5.extname(docPath));
659
+ docBasenames.set(basename2.toLowerCase(), docPath);
660
+ }
661
+ for (const [basename2, sourcePath] of sourceBasenames) {
662
+ if (!docBasenames.has(basename2)) {
663
+ errors.push({
664
+ type: "missing-doc",
665
+ category: category.name,
666
+ sourcePath,
667
+ expectedBasename: basename2
668
+ });
669
+ }
670
+ }
671
+ for (const [basename2, docPath] of docBasenames) {
672
+ if (!sourceBasenames.has(basename2)) {
673
+ errors.push({
674
+ type: "orphaned-doc",
675
+ category: category.name,
676
+ docPath,
677
+ expectedBasename: basename2
678
+ });
679
+ }
680
+ }
681
+ totalSources += sourceBasenames.size;
682
+ totalDocs += docBasenames.size;
683
+ }
684
+ return {
685
+ exitCode: errors.length > 0 ? 1 : 0,
686
+ errors,
687
+ summary: {
688
+ categoriesChecked: allCategories.length,
689
+ totalSources,
690
+ totalDocs
691
+ }
692
+ };
693
+ }
694
+ function formatSyncCheckReport(result) {
695
+ const lines = [];
696
+ lines.push(chalk3.bold("docs-sync-check: Checking source-documentation correspondence...\n"));
697
+ if (result.errors.length > 0) {
698
+ lines.push(chalk3.red.bold("Errors:\n"));
699
+ const byCategory = /* @__PURE__ */ new Map();
700
+ for (const error of result.errors) {
701
+ const existing = byCategory.get(error.category) ?? [];
702
+ existing.push(error);
703
+ byCategory.set(error.category, existing);
704
+ }
705
+ for (const [category, categoryErrors] of byCategory) {
706
+ lines.push(chalk3.cyan(` Category: ${category}
707
+ `));
708
+ for (const error of categoryErrors) {
709
+ if (error.type === "missing-doc") {
710
+ lines.push(` ${chalk3.red(error.sourcePath)}`);
711
+ lines.push(` ${chalk3.yellow("Missing documentation for:")} ${error.expectedBasename}`);
674
712
  } else {
675
- fail(ctx, `${respLabel}: databucketID "${resp.databucketID}" not found in data array`);
713
+ lines.push(` ${chalk3.red(error.docPath)}`);
714
+ lines.push(
715
+ ` ${chalk3.yellow("Orphaned documentation:")} no source file for ${error.expectedBasename}`
716
+ );
676
717
  }
718
+ lines.push("");
677
719
  }
678
720
  }
721
+ } else {
722
+ lines.push(chalk3.green("All source files have corresponding documentation.\n"));
723
+ }
724
+ lines.push(chalk3.bold("Summary:"));
725
+ lines.push(` Categories checked: ${result.summary.categoriesChecked}`);
726
+ lines.push(
727
+ ` Source files: ${result.summary.totalSources}, Doc files: ${result.summary.totalDocs}`
728
+ );
729
+ if (result.errors.length > 0) {
730
+ lines.push(chalk3.red(` Errors: ${result.errors.length}`));
731
+ lines.push("");
732
+ lines.push(chalk3.red.bold(`docs-sync-check failed with ${result.errors.length} error(s).`));
733
+ } else {
734
+ lines.push(chalk3.green(" Errors: 0"));
735
+ lines.push("");
736
+ lines.push(chalk3.green.bold("docs-sync-check passed."));
679
737
  }
738
+ return lines.join("\n");
680
739
  }
681
- async function discoverAllScenarios(mocksDir) {
682
- const scenarios = [];
683
- const providers = await subdirs(mocksDir);
684
- for (const provider of providers) {
685
- const providerDir = join2(mocksDir, provider);
686
- for (const scenario of await subdirs(providerDir)) {
687
- scenarios.push(`${provider}/${scenario}`);
740
+
741
+ // src/commands/scaffold.ts
742
+ import path6 from "path";
743
+ import fs3 from "fs";
744
+ var MODULE_TYPES = ["module", "feature", "command", "model", "query"];
745
+ var APP_TYPES = [
746
+ "requirements",
747
+ "actors",
748
+ "business-flow",
749
+ "story",
750
+ "screen",
751
+ "resolver"
752
+ ];
753
+ var ALL_TYPES = [...MODULE_TYPES, ...APP_TYPES];
754
+ var MODULE_DIR_MAP = {
755
+ feature: "docs/features",
756
+ command: "docs/commands",
757
+ model: "docs/models",
758
+ query: "docs/queries"
759
+ };
760
+ var APP_DIR_MAP = {
761
+ actors: "docs/actors",
762
+ "business-flow": "docs/business-flow",
763
+ screen: "docs/screen",
764
+ resolver: "docs/resolver"
765
+ };
766
+ function resolveScaffoldPath(type, parentName, name, root) {
767
+ if (type === "module" || type === "requirements") {
768
+ return path6.join(root, parentName, "README.md");
769
+ }
770
+ if (!name) {
771
+ throw new Error(`Name is required for scaffold type "${type}"`);
772
+ }
773
+ if (type === "business-flow") {
774
+ return path6.join(root, parentName, "docs/business-flow", name, "README.md");
775
+ }
776
+ if (type === "story") {
777
+ const parts = name.split("/");
778
+ if (parts.length !== 2) {
779
+ throw new Error(
780
+ `Story name must be "<flow>/<story>" (e.g., "onboarding/admin--create-user")`
781
+ );
688
782
  }
783
+ return path6.join(root, parentName, "docs/business-flow", parts[0], "story", `${parts[1]}.md`);
689
784
  }
690
- return scenarios;
691
- }
692
- function resolveScenarioDir(arg3) {
693
- const abs = resolve3(arg3);
694
- if (basename(abs) === "mock.json") return dirname2(abs);
695
- return abs;
696
- }
697
- async function validateScenario(ctx, scenarioDir, mocksDir) {
698
- const label = relative2(mocksDir, scenarioDir);
699
- console.log(chalk3.bold(`
700
- \u2500\u2500 ${label} \u2500\u2500`));
701
- let entries;
702
- try {
703
- entries = await readdir(scenarioDir);
704
- } catch {
705
- fail(ctx, `${label}: directory not found`);
706
- return;
785
+ if (MODULE_DIR_MAP[type]) {
786
+ return path6.join(root, parentName, MODULE_DIR_MAP[type], `${name}.md`);
707
787
  }
708
- const hasMock = checkStructure(ctx, entries, label);
709
- if (!hasMock) return;
710
- const mockPath = join2(scenarioDir, "mock.json");
711
- let data;
712
- try {
713
- data = JSON.parse(await readFile(mockPath, "utf-8"));
714
- } catch (err) {
715
- const errMsg = err instanceof Error ? err.message : String(err);
716
- fail(ctx, `${label}: invalid JSON \u2014 ${errMsg}`);
717
- return;
788
+ if (APP_DIR_MAP[type]) {
789
+ return path6.join(root, parentName, APP_DIR_MAP[type], `${name}.md`);
718
790
  }
719
- await checkSchema(ctx, data, label);
720
- checkResponseQuality(ctx, data, label);
721
- checkDatabucketRefs(ctx, data, label);
791
+ throw new Error(`Unknown scaffold type: ${type}`);
722
792
  }
723
- async function runMockValidate(mocksRoot, paths) {
724
- const ctx = { failures: 0 };
725
- const mocksDir = resolve3(mocksRoot);
726
- const targets = paths.length > 0 ? paths.map(resolveScenarioDir) : (await discoverAllScenarios(mocksDir)).map((s) => join2(mocksDir, s));
727
- if (targets.length === 0) {
728
- fail(ctx, "No scenarios found under mocks/");
793
+ async function runScaffold(type, parentName, name, root, cwd4) {
794
+ const outputPath = resolveScaffoldPath(type, parentName, name, root);
795
+ const absoluteOutput = path6.resolve(cwd4, outputPath);
796
+ if (fs3.existsSync(absoluteOutput)) {
797
+ console.error(`File already exists: ${outputPath}`);
729
798
  return 1;
730
799
  }
731
- console.log(chalk3.bold("\nValidating mock configs...\n"));
732
- for (const target of targets) {
733
- await validateScenario(ctx, target, mocksDir);
800
+ const schemaPath = ALL_SCHEMAS[type];
801
+ if (!schemaPath) {
802
+ console.error(`No schema found for type: ${type}`);
803
+ return 2;
734
804
  }
735
- console.log(chalk3.bold("\n\u2500\u2500 summary \u2500\u2500"));
736
- if (ctx.failures === 0) {
737
- console.log(chalk3.green("\u2713 All checks passed"));
738
- } else {
739
- console.log(chalk3.red(`\u2717 ${ctx.failures} check(s) failed`));
805
+ try {
806
+ fs3.mkdirSync(path6.dirname(absoluteOutput), { recursive: true });
807
+ } catch (err) {
808
+ console.error(
809
+ `Failed to create directory: ${err instanceof Error ? err.message : String(err)}`
810
+ );
811
+ return 1;
740
812
  }
741
- return ctx.failures === 0 ? 0 : 1;
813
+ const { exitCode, stdout, stderr } = await runMdschema(
814
+ ["generate", "--schema", schemaPath, "--output", absoluteOutput],
815
+ cwd4
816
+ );
817
+ if (stdout.trim()) console.log(stdout);
818
+ if (stderr.trim()) console.error(stderr);
819
+ return exitCode;
742
820
  }
743
821
 
744
- // src/commands/mock/index.ts
745
- var startCommand = defineCommand({
746
- name: "start",
747
- description: "Start mock API servers with reverse proxy",
748
- args: z.object({
749
- mocksRoot: arg(z.string().default("./mocks"), {
750
- description: "Path to mocks directory"
751
- }),
752
- port: arg(z.coerce.number().default(3e3), {
753
- alias: "p",
754
- description: "Reverse proxy port"
755
- }),
756
- filter: arg(z.array(z.string()).default([]), {
757
- positional: true,
758
- description: "Filter by provider or provider/scenario"
759
- })
760
- }),
822
+ // src/commands/module/list.ts
823
+ import { readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
824
+ import { join as join3 } from "path";
825
+ import chalk4 from "chalk";
826
+ var MODULES_DIR = join3(PACKAGE_ROOT, "src", "modules");
827
+ var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["shared", "testing"]);
828
+ function countFiles(dir, pattern, exclusions) {
829
+ if (!existsSync2(dir)) return 0;
830
+ return readdirSync2(dir).filter((f) => pattern.test(f) && !exclusions.some((p) => p.test(f))).length;
831
+ }
832
+ function listModules() {
833
+ if (!existsSync2(MODULES_DIR)) return [];
834
+ return readdirSync2(MODULES_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && !EXCLUDED_DIRS.has(d.name)).map((d) => {
835
+ const modDir = join3(MODULES_DIR, d.name);
836
+ return {
837
+ name: d.name,
838
+ commands: countFiles(join3(modDir, "command"), /\.ts$/, [/\.test\.ts$/]),
839
+ models: countFiles(join3(modDir, "db"), /\.ts$/, [/\.test\.ts$/, /^index\.ts$/]),
840
+ features: countFiles(join3(modDir, "docs", "features"), /\.md$/, [])
841
+ };
842
+ }).sort((a, b) => a.name.localeCompare(b.name));
843
+ }
844
+ function formatModuleList(modules) {
845
+ if (modules.length === 0) return "No modules found.";
846
+ const lines = [];
847
+ lines.push(chalk4.bold("Modules:\n"));
848
+ const nameWidth = Math.max(...modules.map((m) => m.name.length), 4);
849
+ for (const mod of modules) {
850
+ const counts = [
851
+ `${mod.commands} commands`,
852
+ `${mod.models} models`,
853
+ `${mod.features} features`
854
+ ].join(", ");
855
+ lines.push(` ${mod.name.padEnd(nameWidth)} ${counts}`);
856
+ }
857
+ lines.push("");
858
+ lines.push(`${modules.length} modules`);
859
+ return lines.join("\n");
860
+ }
861
+ function runModuleList() {
862
+ const modules = listModules();
863
+ console.log(formatModuleList(modules));
864
+ return 0;
865
+ }
866
+
867
+ // src/commands/module/index.ts
868
+ var cwd = process.cwd();
869
+ var rootArgs = z2.object({
870
+ root: arg2(z2.string(), {
871
+ alias: "r",
872
+ description: "Path to modules directory"
873
+ })
874
+ });
875
+ var listCommand = defineCommand2({
876
+ name: "list",
877
+ description: "List available modules",
878
+ run: () => {
879
+ const exitCode = runModuleList();
880
+ process.exit(exitCode);
881
+ }
882
+ });
883
+ var checkCommand = defineCommand2({
884
+ name: "check",
885
+ description: "Validate module docs against schemas",
886
+ args: rootArgs,
761
887
  run: async (args) => {
762
- const exitCode = await runMockStart(args.mocksRoot, args.filter, args.port);
888
+ const exitCode = await runCheck({ modulesRoot: args.root }, cwd);
763
889
  process.exit(exitCode);
764
890
  }
765
891
  });
766
- var validateCommand = defineCommand({
767
- name: "validate",
768
- description: "Validate mock scenario configs",
769
- args: z.object({
770
- mocksRoot: arg(z.string().default("./mocks"), {
771
- description: "Path to mocks directory"
892
+ var syncCheckCommand = defineCommand2({
893
+ name: "sync-check",
894
+ description: "Validate source <-> doc correspondence",
895
+ args: rootArgs,
896
+ run: async (args) => {
897
+ const result = await runSyncCheck({ modulesRoot: args.root }, cwd);
898
+ console.log(formatSyncCheckReport(result));
899
+ process.exit(result.exitCode);
900
+ }
901
+ });
902
+ var scaffoldCommand = defineCommand2({
903
+ name: "scaffold",
904
+ description: "Generate module doc from schema template",
905
+ args: rootArgs.extend({
906
+ type: arg2(z2.enum(MODULE_TYPES), {
907
+ positional: true,
908
+ description: `Scaffold type (${MODULE_TYPES.join(", ")})`
772
909
  }),
773
- paths: arg(z.array(z.string()).default([]), {
910
+ parent: arg2(z2.string(), {
774
911
  positional: true,
775
- description: "Specific scenario paths to validate"
912
+ description: "Module name"
913
+ }),
914
+ name: arg2(z2.string().optional(), {
915
+ positional: true,
916
+ description: "Item name (required for feature, command, model)"
776
917
  })
777
918
  }),
778
919
  run: async (args) => {
779
- const exitCode = await runMockValidate(args.mocksRoot, args.paths);
920
+ const exitCode = await runScaffold(
921
+ args.type,
922
+ args.parent,
923
+ args.name,
924
+ args.root,
925
+ cwd
926
+ );
780
927
  process.exit(exitCode);
781
928
  }
782
929
  });
783
- var mockCommand = defineCommand({
784
- name: "mock",
785
- description: "Mock API server management",
930
+ var moduleCommand = defineCommand2({
931
+ name: "module",
932
+ description: "Module management",
786
933
  subCommands: {
787
- start: startCommand,
788
- validate: validateCommand
934
+ list: listCommand,
935
+ check: checkCommand,
936
+ "sync-check": syncCheckCommand,
937
+ scaffold: scaffoldCommand
789
938
  }
790
939
  });
791
940
 
792
- // src/cli.ts
793
- var cwd = process.cwd();
794
- var rootArgs = z2.object({
795
- modulesRoot: arg2(z2.string().optional(), {
796
- alias: "m",
797
- description: "Path to modules directory"
798
- }),
799
- appRoot: arg2(z2.string().optional(), {
800
- alias: "a",
801
- description: "Path to app-compose directory (apps/ or examples/)"
941
+ // src/commands/app/index.ts
942
+ import { z as z3 } from "zod";
943
+ import { defineCommand as defineCommand3, arg as arg3 } from "politty";
944
+ var cwd2 = process.cwd();
945
+ var rootArgs2 = z3.object({
946
+ root: arg3(z3.string(), {
947
+ alias: "r",
948
+ description: "Path to app-compose directory"
802
949
  })
803
950
  });
804
- function requireRoot(args) {
805
- const paths = { modulesRoot: args.modulesRoot, appRoot: args.appRoot };
806
- if (!paths.modulesRoot && !paths.appRoot) {
807
- console.error("At least one of --modules-root or --app-root is required.");
808
- process.exit(2);
809
- }
810
- return paths;
811
- }
812
- var checkCommand = defineCommand2({
951
+ var checkCommand2 = defineCommand3({
813
952
  name: "check",
814
- description: "Validate docs against schemas",
815
- args: rootArgs,
953
+ description: "Validate app docs against schemas",
954
+ args: rootArgs2,
816
955
  run: async (args) => {
817
- const paths = requireRoot(args);
818
- const exitCode = await runCheck(paths, cwd);
956
+ const exitCode = await runCheck({ appRoot: args.root }, cwd2);
819
957
  process.exit(exitCode);
820
958
  }
821
959
  });
822
- var syncCheckCommand = defineCommand2({
960
+ var syncCheckCommand2 = defineCommand3({
823
961
  name: "sync-check",
824
962
  description: "Validate source <-> doc correspondence",
825
- args: rootArgs,
963
+ args: rootArgs2,
826
964
  run: async (args) => {
827
- const paths = requireRoot(args);
828
- const result = await runSyncCheck(paths, cwd);
965
+ const result = await runSyncCheck({ appRoot: args.root }, cwd2);
829
966
  console.log(formatSyncCheckReport(result));
830
967
  process.exit(result.exitCode);
831
968
  }
832
969
  });
833
- var scaffoldCommand = defineCommand2({
970
+ var scaffoldCommand2 = defineCommand3({
834
971
  name: "scaffold",
835
- description: "Generate doc file from schema template",
836
- args: rootArgs.extend({
837
- type: arg2(z2.enum(ALL_TYPES), {
972
+ description: "Generate app doc from schema template",
973
+ args: rootArgs2.extend({
974
+ type: arg3(z3.enum(APP_TYPES), {
838
975
  positional: true,
839
- description: `Scaffold type (${ALL_TYPES.join(", ")})`
976
+ description: `Scaffold type (${APP_TYPES.join(", ")})`
840
977
  }),
841
- parent: arg2(z2.string(), {
978
+ parent: arg3(z3.string(), {
842
979
  positional: true,
843
- description: "Parent name (module or app name)"
980
+ description: "App name"
844
981
  }),
845
- name: arg2(z2.string().optional(), {
982
+ name: arg3(z3.string().optional(), {
846
983
  positional: true,
847
984
  description: "Item name (required for most types)"
848
985
  })
849
986
  }),
850
987
  run: async (args) => {
851
- const paths = requireRoot(args);
852
- const root = isModuleType(args.type) ? paths.modulesRoot : paths.appRoot;
853
- if (!root) {
854
- console.error(
855
- `--${isModuleType(args.type) ? "modules-root" : "app-root"} is required for scaffold type "${args.type}".`
856
- );
857
- process.exit(2);
858
- }
859
988
  const exitCode = await runScaffold(
860
989
  args.type,
861
990
  args.parent,
862
991
  args.name,
863
- root,
864
- cwd
992
+ args.root,
993
+ cwd2
865
994
  );
866
995
  process.exit(exitCode);
867
996
  }
868
997
  });
869
- var initCommand = defineCommand2({
998
+ var appCommand = defineCommand3({
999
+ name: "app",
1000
+ description: "App-compose management",
1001
+ subCommands: {
1002
+ check: checkCommand2,
1003
+ "sync-check": syncCheckCommand2,
1004
+ scaffold: scaffoldCommand2
1005
+ }
1006
+ });
1007
+
1008
+ // src/cli.ts
1009
+ var cwd3 = process.cwd();
1010
+ var initCommand = defineCommand4({
870
1011
  name: "init",
871
- description: "Set up consumer repo (skills, rules)",
872
- args: z2.object({
873
- force: arg2(z2.boolean().default(false), {
1012
+ description: "Set up consumer repo with framework skills",
1013
+ args: z4.object({
1014
+ force: arg4(z4.boolean().default(false), {
874
1015
  alias: "f",
875
- description: "Overwrite existing skills and rules"
1016
+ description: "Overwrite existing skills"
876
1017
  })
877
1018
  }),
878
1019
  run: (args) => {
879
- const exitCode = runInit(cwd, args.force);
1020
+ const exitCode = runInit(cwd3, args.force);
880
1021
  process.exit(exitCode);
881
1022
  }
882
1023
  });
883
- var main = defineCommand2({
1024
+ var main = defineCommand4({
884
1025
  name: "erp-kit",
885
- description: "Documentation validation and scaffolding tool",
1026
+ description: "ERP module framework CLI",
886
1027
  subCommands: {
887
- check: checkCommand,
888
- "sync-check": syncCheckCommand,
889
- scaffold: scaffoldCommand,
890
- init: initCommand,
891
- mock: mockCommand
1028
+ module: moduleCommand,
1029
+ app: appCommand,
1030
+ mock: mockCommand,
1031
+ init: initCommand
892
1032
  }
893
1033
  });
894
1034
  void runMain(main);