@pukujan/create-modular-monolith 2.2.0 → 2.2.3

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 (33) hide show
  1. package/LICENSE +50 -0
  2. package/README.md +145 -62
  3. package/package.json +3 -2
  4. package/template/.cursor/rules/file-exchange-inbox.mdc +1 -1
  5. package/template/ARCHITECTURE_EXPORT_README.md +30 -0
  6. package/template/EXPORT_MANIFEST.json +74 -0
  7. package/template/LICENSE +11 -0
  8. package/template/NOTICE +12 -0
  9. package/template/README.md +36 -25
  10. package/template/backend/package.json +1 -1
  11. package/template/backend/src/modules/model-condenser/services/modelCondenser.service.js +36 -465
  12. package/template/backend/src/modules/model-condenser/tests/unit/modelCondenser.service.test.js +2 -3
  13. package/template/backend/src/shared/utils/consolidatedExport.js +155 -11
  14. package/template/backend/src/shared/utils/consolidatedExport.test.js +50 -0
  15. package/template/docs/README.md +0 -1
  16. package/template/docs/architecture/EVAL_AND_CI.md +68 -41
  17. package/template/docs/architecture/REPO_ARTIFACT_LAYOUT.md +57 -14
  18. package/template/docs/architecture/contracts/consolidatedExports.contract.md +14 -10
  19. package/template/docs/architecture/contracts/manifest.json +17 -0
  20. package/template/file-exchange/README.md +20 -15
  21. package/template/frontend/package.json +1 -1
  22. package/template/package.json +3 -3
  23. package/template/scripts/condense-all.mjs +52 -0
  24. package/template/scripts/condense-file-structure.mjs +19 -10
  25. package/template/scripts/condense-models.mjs +5 -3
  26. package/template/scripts/condense-prompts.mjs +13 -51
  27. package/template/scripts/consolidated-output.mjs +22 -10
  28. package/template/scripts/export-architecture-starter.mjs +389 -0
  29. package/template/scripts/lib/api-inventory.mjs +16 -61
  30. package/template/scripts/lib/repo-tree.mjs +26 -4
  31. package/template/scripts/sync-cli-template.mjs +44 -0
  32. package/template/scripts/write-pre-push-dev-log.mjs +1 -0
  33. package/template/file-exchange/exports/consolidated-models.json +0 -625
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Export architecture-only starter (no domain modules, no node_modules/dist/build).
4
+ *
5
+ * Usage:
6
+ * npm run export:architecture-starter
7
+ * npm run export:architecture-starter -- --to packages/create-modular-monolith/template
8
+ */
9
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync } from "fs";
10
+ import { join, dirname, resolve, isAbsolute } from "path";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
14
+ const templatesRoot = join(repoRoot, "export-templates");
15
+
16
+ const EXCLUDE_DIRS = new Set([
17
+ "node_modules",
18
+ ".git",
19
+ "dist",
20
+ "build",
21
+ "coverage",
22
+ "packages",
23
+ "export",
24
+ "data",
25
+ "eval-bundles",
26
+ "case-exports",
27
+ "evals"
28
+ ]);
29
+
30
+ const BACKEND_MODULES_KEEP = new Set(["_reference", "model-condenser"]);
31
+ const FRONTEND_MODULES_KEEP = new Set(["_reference"]);
32
+
33
+ const SCRIPTS_KEEP = new Set([
34
+ "new-module.mjs",
35
+ "sync-cli-template.mjs",
36
+ "export-architecture-starter.mjs",
37
+ "check-api-docs.mjs",
38
+ "lint-contracts.mjs",
39
+ "lint-repo-artifacts.mjs",
40
+ "condense-models.mjs",
41
+ "condense-prompts.mjs",
42
+ "condense-file-structure.mjs",
43
+ "condense-all.mjs",
44
+ "consolidated-output.mjs",
45
+ "write-pre-push-dev-log.mjs",
46
+ "verify-dev-log.mjs",
47
+ "import-to-file-exchange.mjs",
48
+ "resolve-import-stamp.mjs",
49
+ "export-consolidated-models.mjs",
50
+ "run-module-evals.mjs"
51
+ ]);
52
+
53
+ const DOCS_KEEP_FILES = new Set([
54
+ "README.md",
55
+ "STARTER_PACK.md",
56
+ "PUBLISHING.md",
57
+ "DEVLOG_V2.md"
58
+ ]);
59
+
60
+ function parseArgs(argv) {
61
+ let target = join(repoRoot, "export/architecture-starter");
62
+ for (let i = 0; i < argv.length; i += 1) {
63
+ if (argv[i] === "--to" && argv[i + 1]) {
64
+ const raw = argv[++i];
65
+ target = isAbsolute(raw) ? raw : resolve(process.cwd(), raw);
66
+ }
67
+ }
68
+ return { target };
69
+ }
70
+
71
+ function shouldExcludePath(sourcePath) {
72
+ const parts = sourcePath.split(/[/\\]/);
73
+ return parts.some((p) => EXCLUDE_DIRS.has(p));
74
+ }
75
+
76
+ function copyFiltered(src, dest, filterFn) {
77
+ cpSync(src, dest, {
78
+ recursive: true,
79
+ filter: (sourcePath) => {
80
+ if (shouldExcludePath(sourcePath)) return false;
81
+ return filterFn ? filterFn(sourcePath) : true;
82
+ }
83
+ });
84
+ }
85
+
86
+ function copyBackendModules(target) {
87
+ const srcRoot = join(repoRoot, "backend/src/modules");
88
+ const destRoot = join(target, "backend/src/modules");
89
+ mkdirSync(destRoot, { recursive: true });
90
+ writeFileSync(join(destRoot, ".gitkeep"), "");
91
+ for (const name of readdirSync(srcRoot)) {
92
+ if (!BACKEND_MODULES_KEEP.has(name)) continue;
93
+ copyFiltered(join(srcRoot, name), join(destRoot, name));
94
+ console.log(` ✓ backend module ${name}`);
95
+ }
96
+ }
97
+
98
+ function copyScripts(target) {
99
+ const srcRoot = join(repoRoot, "scripts");
100
+ const destRoot = join(target, "scripts");
101
+ mkdirSync(destRoot, { recursive: true });
102
+ for (const name of readdirSync(srcRoot)) {
103
+ if (name === "lib") {
104
+ copyFiltered(join(srcRoot, "lib"), join(destRoot, "lib"));
105
+ console.log(" ✓ scripts/lib/");
106
+ continue;
107
+ }
108
+ if (name === "git-hooks") {
109
+ copyFiltered(join(srcRoot, "git-hooks"), join(destRoot, "git-hooks"));
110
+ console.log(" ✓ scripts/git-hooks/");
111
+ continue;
112
+ }
113
+ if (!SCRIPTS_KEEP.has(name)) continue;
114
+ cpSync(join(srcRoot, name), join(destRoot, name));
115
+ console.log(` ✓ scripts/${name}`);
116
+ }
117
+ }
118
+
119
+ function copyDocs(target) {
120
+ const destDocs = join(target, "docs");
121
+ mkdirSync(destDocs, { recursive: true });
122
+ copyFiltered(join(repoRoot, "docs/architecture"), join(destDocs, "architecture"));
123
+ console.log(" ✓ docs/architecture/");
124
+ for (const name of DOCS_KEEP_FILES) {
125
+ const src = join(repoRoot, "docs", name);
126
+ if (existsSync(src)) {
127
+ cpSync(src, join(destDocs, name));
128
+ console.log(` ✓ docs/${name}`);
129
+ }
130
+ }
131
+ cpSync(join(templatesRoot, "docs/API.starter.md"), join(destDocs, "API.md"));
132
+ mkdirSync(join(destDocs, "model-condenser"), { recursive: true });
133
+ cpSync(
134
+ join(repoRoot, "docs/model-condenser/API.md"),
135
+ join(destDocs, "model-condenser/API.md")
136
+ );
137
+ console.log(" ✓ docs/API.md (starter registry)");
138
+ }
139
+
140
+ function copyWorkLog(target) {
141
+ const dest = join(target, "work-log");
142
+ mkdirSync(dest, { recursive: true });
143
+ for (const name of ["README.md"]) {
144
+ cpSync(join(repoRoot, "work-log", name), join(dest, name));
145
+ }
146
+ writeFileSync(
147
+ join(dest, "INDEX.md"),
148
+ `# Work log — index\n\nSee [README.md](./README.md). Add handoffs, study-docs, and dev-log rows as you work.\n`
149
+ );
150
+ copyFiltered(join(repoRoot, "work-log/dev-logs"), join(dest, "dev-logs"), (p) => {
151
+ const base = p.split(/[/\\]/).pop() || "";
152
+ if (/^\d{3}_\d{4}-\d{2}-\d{2}_.*dev-log/.test(base)) return false;
153
+ if (p.includes("/human/") && p.endsWith(".md")) return !/\/human\/\d{3}_/.test(p);
154
+ if (p.includes("/agent/") && p.endsWith(".json")) return !/\/agent\/\d{3}_/.test(p);
155
+ return true;
156
+ });
157
+ mkdirSync(join(dest, "handoffs"), { recursive: true });
158
+ cpSync(join(repoRoot, "work-log/handoffs/README.md"), join(dest, "handoffs/README.md"));
159
+ mkdirSync(join(dest, "study-docs"), { recursive: true });
160
+ cpSync(join(repoRoot, "work-log/study-docs/README.md"), join(dest, "study-docs/README.md"));
161
+ console.log(" ✓ work-log/ (structure, no domain handoffs)");
162
+ }
163
+
164
+ function copyFileExchange(target) {
165
+ const dest = join(target, "file-exchange");
166
+ mkdirSync(join(dest, "imports"), { recursive: true });
167
+ mkdirSync(join(dest, "exports"), { recursive: true });
168
+ writeFileSync(join(dest, "imports/.gitkeep"), "");
169
+ writeFileSync(join(dest, "exports/.gitkeep"), "");
170
+ cpSync(join(repoRoot, "file-exchange/README.md"), join(dest, "README.md"));
171
+ console.log(" ✓ file-exchange/");
172
+ }
173
+
174
+ function copyCursor(target) {
175
+ copyFiltered(join(repoRoot, ".cursor"), join(target, ".cursor"));
176
+ console.log(" ✓ .cursor/");
177
+ }
178
+
179
+ function copySharedContracts(target) {
180
+ const sharedContracts = join(target, "backend/src/shared/contracts");
181
+ mkdirSync(sharedContracts, { recursive: true });
182
+ console.log(" ✓ backend/src/shared/ (via backend copy)");
183
+ }
184
+
185
+ function writeStarterRootFiles(target) {
186
+ cpSync(join(templatesRoot, "package.starter.json"), join(target, "package.json"));
187
+ cpSync(join(templatesRoot, "AGENTS.starter.md"), join(target, "AGENTS.md"));
188
+ cpSync(join(templatesRoot, "README.starter.md"), join(target, "README.md"));
189
+ cpSync(join(repoRoot, ".gitignore"), join(target, ".gitignore"));
190
+ cpSync(join(templatesRoot, "LICENSE.starter"), join(target, "LICENSE"));
191
+ cpSync(join(templatesRoot, "NOTICE.starter"), join(target, "NOTICE"));
192
+ mkdirSync(join(target, "models"), { recursive: true });
193
+ writeFileSync(join(target, "models/.gitkeep"), "");
194
+ console.log(" ✓ package.json, AGENTS.md, README, LICENSE, NOTICE, models/.gitkeep");
195
+ }
196
+
197
+ function patchStarterScripts(target) {
198
+ cpSync(
199
+ join(templatesRoot, "condense-prompts.starter.mjs"),
200
+ join(target, "scripts/condense-prompts.mjs")
201
+ );
202
+ cpSync(
203
+ join(templatesRoot, "modelCondenser.service.starter.js"),
204
+ join(
205
+ target,
206
+ "backend/src/modules/model-condenser/services/modelCondenser.service.js"
207
+ )
208
+ );
209
+ cpSync(
210
+ join(templatesRoot, "api-inventory.starter.mjs"),
211
+ join(target, "scripts/lib/api-inventory.mjs")
212
+ );
213
+ const repoTreePath = join(target, "scripts/lib/repo-tree.mjs");
214
+ let repoTree = readFileSync(repoTreePath, "utf8");
215
+ repoTree = repoTree.replace(
216
+ /export const TREE_IGNORE_PREFIXES = \[[\s\S]*?\];/,
217
+ 'export const TREE_IGNORE_PREFIXES = ["data"];'
218
+ );
219
+ writeFileSync(repoTreePath, repoTree);
220
+
221
+ const fileStructurePath = join(target, "scripts/condense-file-structure.mjs");
222
+ let fileStructure = readFileSync(fileStructurePath, "utf8");
223
+ fileStructure = fileStructure.replace(
224
+ "and runtime batch/export paths.",
225
+ "and runtime data/ (if present)."
226
+ );
227
+ writeFileSync(fileStructurePath, fileStructure);
228
+ console.log(" ✓ starter patches (prompts, model-condenser, repo-tree)");
229
+ }
230
+
231
+ function writeManifest(target) {
232
+ const manifest = {
233
+ exportedAt: new Date().toISOString(),
234
+ sourceRepo: "legal-prmpt-eng",
235
+ description: "Architecture-only starter — no domain modules, no build artifacts",
236
+ included: {
237
+ backendModules: [...BACKEND_MODULES_KEEP],
238
+ frontendModules: [...FRONTEND_MODULES_KEEP],
239
+ docs: ["docs/architecture/**", "docs/API.md (starter)", "docs/model-condenser/API.md"],
240
+ scripts: [...SCRIPTS_KEEP, "scripts/lib/**", "scripts/git-hooks/**"],
241
+ other: [
242
+ "file-exchange/",
243
+ "work-log/ (structure)",
244
+ ".cursor/",
245
+ "backend/src/core",
246
+ "backend/src/shared",
247
+ "frontend/src/core",
248
+ "frontend/src/shared"
249
+ ]
250
+ },
251
+ excluded: {
252
+ dirs: [...EXCLUDE_DIRS],
253
+ backendModules: "case-filing-ai, court-rules, case-workflow, filing-*, human-review, task-docketing",
254
+ data: "runtime batches, golden fixtures, PDFs",
255
+ scripts: "ingest-golden-*, rerun-batch-evals, run-module-evals"
256
+ },
257
+ nextSteps: [
258
+ "Review export/ARCHITECTURE_EXPORT_README.md",
259
+ "npm install in backend/ and frontend/",
260
+ "npm run lint:contracts && npm run lint:architecture",
261
+ "node scripts/new-module.mjs my-feature --label \"My Feature\"",
262
+ "Copy into packages/create-modular-monolith/template or publish CLI"
263
+ ]
264
+ };
265
+ writeFileSync(join(target, "EXPORT_MANIFEST.json"), JSON.stringify(manifest, null, 2));
266
+ }
267
+
268
+ function writeExportReadme(target) {
269
+ const md = `# Architecture starter export
270
+
271
+ Generated by \`npm run export:architecture-starter\`.
272
+
273
+ ## What this is
274
+
275
+ A **clean modular-monolith boilerplate**: contracts, file-exchange, dev-logs, model-condenser, \`_reference\` module, lint scripts — **without** litigation/case-filing domain modules or runtime data.
276
+
277
+ ## What was excluded
278
+
279
+ - Domain backend/frontend modules (case-filing-ai, court-rules, …)
280
+ - \`node_modules\`, \`dist\`, \`build\`
281
+ - \`data/\`, \`evals/golden\`, batch PDFs, import bundles
282
+ - Legal-specific ingest/eval scripts
283
+
284
+ See \`EXPORT_MANIFEST.json\` for the full list.
285
+
286
+ ## Update npm CLI template
287
+
288
+ \`\`\`bash
289
+ npm run export:architecture-starter -- --to ../create-modular-monolith/packages/template
290
+ # or from this repo:
291
+ npm run export:architecture-starter -- --to packages/create-modular-monolith/template
292
+ cd packages/create-modular-monolith && npm publish --access public
293
+ \`\`\`
294
+
295
+ ## Docs
296
+
297
+ Start at [docs/architecture/CONTRACTS_OVERVIEW.md](docs/architecture/CONTRACTS_OVERVIEW.md).
298
+
299
+ `;
300
+ writeFileSync(join(target, "ARCHITECTURE_EXPORT_README.md"), md);
301
+ }
302
+
303
+ function patchLintRepoArtifacts(target) {
304
+ const path = join(target, "scripts/lint-repo-artifacts.mjs");
305
+ let text = readFileSync(path, "utf8");
306
+ text = text.replace(
307
+ /const requiredPaths = \[[\s\S]*?\];/,
308
+ `const requiredPaths = [
309
+ "docs/architecture/CONTRACTS_OVERVIEW.md",
310
+ "docs/architecture/REPO_ARTIFACT_LAYOUT.md",
311
+ "docs/architecture/contracts/manifest.json",
312
+ "docs/architecture/contracts/changelog.jsonl",
313
+ "docs/architecture/contracts/fileExchange.contract.md",
314
+ "docs/architecture/contracts/consolidatedExports.contract.md",
315
+ "docs/architecture/contracts/prePushDevLog.contract.md",
316
+ "docs/architecture/contracts/apiDocumentationRegistry.contract.md",
317
+ "backend/src/shared/contracts/prePushDevLog.contract.js",
318
+ "backend/src/shared/contracts/consolidatedExports.contract.js",
319
+ "work-log/dev-logs/schemas/dev-log-agent.v1.schema.json",
320
+ "work-log/dev-logs/human",
321
+ "work-log/dev-logs/agent",
322
+ "file-exchange/README.md",
323
+ "file-exchange/imports",
324
+ "file-exchange/exports",
325
+ "docs/API.md",
326
+ "backend/src/modules/_reference/index.js",
327
+ "backend/src/modules/model-condenser/index.js"
328
+ ];`
329
+ );
330
+ writeFileSync(path, text);
331
+ }
332
+
333
+ function main() {
334
+ const { target } = parseArgs(process.argv.slice(2));
335
+ if (existsSync(target)) {
336
+ rmSync(target, { recursive: true, force: true });
337
+ }
338
+ mkdirSync(target, { recursive: true });
339
+
340
+ console.log(`Exporting architecture starter → ${target}\n`);
341
+
342
+ console.log("Backend core + shared:");
343
+ copyFiltered(join(repoRoot, "backend/src/core"), join(target, "backend/src/core"));
344
+ copyFiltered(join(repoRoot, "backend/src/shared"), join(target, "backend/src/shared"));
345
+ copyBackendModules(target);
346
+
347
+ console.log("\nFrontend:");
348
+ for (const f of ["package.json", ".env.example"]) {
349
+ const src = join(repoRoot, "backend", f);
350
+ if (existsSync(src)) cpSync(src, join(target, "backend", f));
351
+ }
352
+ copyFiltered(join(repoRoot, "backend/scripts"), join(target, "backend/scripts"));
353
+ mkdirSync(join(target, "backend/db/migrations"), { recursive: true });
354
+ writeFileSync(join(target, "backend/db/migrations/.gitkeep"), "");
355
+ console.log(" ✓ backend/package.json, scripts/, db/");
356
+
357
+ console.log("\nFrontend shell:");
358
+ for (const f of ["package.json", "index.html", "vite.config.js", ".env.example"]) {
359
+ const src = join(repoRoot, "frontend", f);
360
+ if (existsSync(src)) cpSync(src, join(target, "frontend", f));
361
+ }
362
+ copyFiltered(join(repoRoot, "frontend/src"), join(target, "frontend/src"), (p) => {
363
+ const parts = p.split(/[/\\]/);
364
+ const modIdx = parts.indexOf("modules");
365
+ if (modIdx >= 0 && parts[modIdx + 1]) {
366
+ return FRONTEND_MODULES_KEEP.has(parts[modIdx + 1]);
367
+ }
368
+ return true;
369
+ });
370
+ console.log(" ✓ frontend/");
371
+
372
+ console.log("\nDocs, scripts, exchange, work-log:");
373
+ copyDocs(target);
374
+ copyScripts(target);
375
+ copyFileExchange(target);
376
+ copyWorkLog(target);
377
+ copyCursor(target);
378
+
379
+ console.log("\nRoot:");
380
+ writeStarterRootFiles(target);
381
+ patchStarterScripts(target);
382
+ patchLintRepoArtifacts(target);
383
+ writeManifest(target);
384
+ writeExportReadme(target);
385
+
386
+ console.log(`\nDone. See ${target}/ARCHITECTURE_EXPORT_README.md`);
387
+ }
388
+
389
+ main();
@@ -65,9 +65,6 @@ function classifyRoute(row) {
65
65
  return "active";
66
66
  }
67
67
 
68
- /**
69
- * @param {string} repoRoot
70
- */
71
68
  export async function collectApiInventory(repoRoot) {
72
69
  const masterApiPath = join(repoRoot, "docs/API.md");
73
70
  const masterText = existsSync(masterApiPath) ? readText(masterApiPath) : "";
@@ -84,63 +81,38 @@ export async function collectApiInventory(repoRoot) {
84
81
  });
85
82
  }
86
83
 
87
- const pkg = JSON.parse(readText(join(repoRoot, "package.json")));
88
-
89
- const versioned = {
90
- pipeline: {
91
- app: pkg.version ?? "2.0.0",
92
- note: "Add pipelineVersions.contract.js in domain modules when you introduce batch workflows"
93
- },
94
- prompts: {
95
- defaultEnv: "n/a",
96
- envVar: "MASTER_PROMPT_VERSION",
97
- allowed: [],
98
- specs: {},
99
- notes: ["Add promptVersions.js in your domain module when you introduce LLM workflows"]
100
- },
101
- storage: {},
102
- app: { packageJson: pkg.version }
103
- };
104
-
105
- const deprecated = { http: http.deprecated, cli: [], prompts: [], notes: [] };
106
- const exportDeprecated = join(repoRoot, "scripts/export-consolidated-models.mjs");
107
- if (existsSync(exportDeprecated)) {
108
- const t = readText(exportDeprecated);
109
- if (/@deprecated/i.test(t)) {
110
- deprecated.cli.push({
111
- command: "scripts/export-consolidated-models.mjs",
112
- replacement: "npm run condense-models or POST /api/model-condenser/condense"
113
- });
114
- }
115
- }
116
-
117
84
  const cli = [
118
- { command: "npm run test:ci", purpose: "All CI gates (lint + test + evals)" },
119
85
  { command: "npm run dev-log:pre-push", purpose: "Paired human + agent dev logs" },
120
- { command: "npm run condense:all", purpose: "Snapshots → file-exchange/exports/" },
121
- { command: "npm run import:file-exchange", purpose: "Inbound → file-exchange/imports/{stamp}/" },
122
- { command: "npm run lint:contracts", purpose: "Verify contract manifest paths" },
86
+ {
87
+ command: "npm run condense:all",
88
+ purpose: "consolidated snapshots file-exchange/exports/{stamp}_consolidated/"
89
+ },
90
+ { command: "npm run import:file-exchange", purpose: "Inbound bundle → file-exchange/imports/{stamp}/" },
123
91
  { command: "npm --prefix backend run condense-models", purpose: "Regenerate consolidated-models.json" }
124
92
  ];
125
93
 
126
94
  return {
127
95
  capturedAt: new Date().toISOString(),
128
- sourceDocs: ["docs/API.md", "backend/src/modules/*/routes/"],
96
+ sourceDocs: ["docs/API.md"],
129
97
  http,
130
98
  moduleStatus: moduleIndex.map((m) => ({
131
99
  module: m.module,
132
100
  basePath: m.basePath,
133
101
  status: m.status
134
102
  })),
135
- versioned,
136
- deprecated,
103
+ versioned: {
104
+ pipeline: null,
105
+ prompts: null,
106
+ storage: null,
107
+ app: {
108
+ packageJson: readText(join(repoRoot, "package.json")).match(/"version":\s*"([^"]+)"/)?.[1]
109
+ }
110
+ },
111
+ deprecated: { http: http.deprecated, cli: [], prompts: [], notes: [] },
137
112
  cli
138
113
  };
139
114
  }
140
115
 
141
- /**
142
- * @param {Awaited<ReturnType<typeof collectApiInventory>>} apis
143
- */
144
116
  export function formatApisMarkdown(apis) {
145
117
  const lines = [
146
118
  "### HTTP — active",
@@ -160,23 +132,6 @@ export function formatApisMarkdown(apis) {
160
132
  } else {
161
133
  lines.push("_none_");
162
134
  }
163
- lines.push("", "### HTTP — deprecated", "");
164
- if (apis.http.deprecated.length) {
165
- lines.push("| Method | Path | Module | Description |", "|--------|------|--------|-------------|");
166
- for (const r of apis.http.deprecated) {
167
- lines.push(`| ${r.method} | \`${r.path}\` | ${r.module} | ${r.description} |`);
168
- }
169
- } else {
170
- lines.push("_none registered in docs/API.md_");
171
- }
172
- lines.push("", "### Versioned contracts (platform)", "", "```json");
173
- lines.push(JSON.stringify(apis.versioned.pipeline, null, 2));
174
- lines.push("```");
175
- if (apis.deprecated.cli.length) {
176
- lines.push("", "### Deprecated CLI", "");
177
- for (const d of apis.deprecated.cli) {
178
- lines.push(`- \`${d.command}\` → ${d.replacement}`);
179
- }
180
- }
135
+ lines.push("", "### HTTP — deprecated", "", "_none registered in docs/API.md_");
181
136
  return lines.join("\n");
182
137
  }
@@ -5,9 +5,26 @@ import { join, extname } from "path";
5
5
  export const TREE_IGNORE_DIRS = ["node_modules", ".git", "dist", "build", ".DS_Store"];
6
6
  export const TREE_IGNORE_FILES = [".DS_Store"];
7
7
 
8
+ /** Runtime / large paths (gitignored or handoff noise) — skip entire subtrees. */
9
+ export const TREE_IGNORE_PREFIXES = ["data"];
10
+
8
11
  const EXCLUDE_DIRS = new Set(TREE_IGNORE_DIRS);
9
12
  const EXCLUDE_FILES = new Set(TREE_IGNORE_FILES);
10
13
 
14
+ function normalizeRel(rel) {
15
+ return String(rel || "").replace(/\\/g, "/").replace(/^\.\//, "");
16
+ }
17
+
18
+ function isIgnoredPrefix(relPath, ignorePrefixes) {
19
+ const rel = normalizeRel(relPath);
20
+ if (!rel) return false;
21
+ for (const prefix of ignorePrefixes) {
22
+ const p = normalizeRel(prefix).replace(/\/$/, "");
23
+ if (rel === p || rel.startsWith(`${p}/`)) return true;
24
+ }
25
+ return false;
26
+ }
27
+
11
28
  function renderTreeText(nodes, prefix = "") {
12
29
  const lines = [];
13
30
  for (let i = 0; i < nodes.length; i += 1) {
@@ -24,7 +41,7 @@ function renderTreeText(nodes, prefix = "") {
24
41
  return lines;
25
42
  }
26
43
 
27
- async function walkDir(absDir, relBase = "") {
44
+ async function walkDir(absDir, relBase = "", ignorePrefixes = []) {
28
45
  const entries = await readdir(absDir, { withFileTypes: true });
29
46
  const dirs = [];
30
47
  const files = [];
@@ -32,6 +49,8 @@ async function walkDir(absDir, relBase = "") {
32
49
  for (const ent of entries) {
33
50
  if (ent.isDirectory()) {
34
51
  if (EXCLUDE_DIRS.has(ent.name)) continue;
52
+ const rel = relBase ? `${relBase}/${ent.name}` : ent.name;
53
+ if (isIgnoredPrefix(rel, ignorePrefixes)) continue;
35
54
  dirs.push(ent.name);
36
55
  } else if (ent.isFile()) {
37
56
  if (EXCLUDE_FILES.has(ent.name)) continue;
@@ -60,7 +79,7 @@ async function walkDir(absDir, relBase = "") {
60
79
 
61
80
  for (const name of dirs) {
62
81
  const rel = relBase ? `${relBase}/${name}` : name;
63
- const sub = await walkDir(join(absDir, name), rel);
82
+ const sub = await walkDir(join(absDir, name), rel, ignorePrefixes);
64
83
  flatPaths.push(...sub.flatPaths);
65
84
  children.push({
66
85
  name,
@@ -105,15 +124,18 @@ function countStats(flatPaths, treeChildren) {
105
124
 
106
125
  /**
107
126
  * @param {string} repoRoot
127
+ * @param {{ ignorePrefixes?: string[] }} [options]
108
128
  * @returns {Promise<{ rootName: string, tree: object, treeText: string, stats: object, flatPaths: string[] }>}
109
129
  */
110
- export async function buildRepoTree(repoRoot) {
130
+ export async function buildRepoTree(repoRoot, options = {}) {
131
+ const ignorePrefixes = options.ignorePrefixes ?? TREE_IGNORE_PREFIXES;
111
132
  const rootName = repoRoot.split("/").pop() || "repo";
112
- const walked = await walkDir(repoRoot);
133
+ const walked = await walkDir(repoRoot, "", ignorePrefixes);
113
134
  const stats = countStats(walked.flatPaths, walked.children);
114
135
  const treeText = [rootName + "/", ...renderTreeText(walked.children)].join("\n");
115
136
  return {
116
137
  rootName,
138
+ excludePrefixes: ignorePrefixes,
117
139
  tree: {
118
140
  name: rootName,
119
141
  type: "directory",
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copies the v2 starter into packages/create-modular-monolith/template
4
+ * for publishing with the npm CLI.
5
+ */
6
+ import { cpSync, existsSync, mkdirSync, rmSync } from "fs";
7
+ import { join } from "path";
8
+
9
+ const root = new URL("../", import.meta.url).pathname;
10
+ const target = join(root, "packages/create-modular-monolith/template");
11
+
12
+ const COPY_ROOTS = ["backend", "frontend", "docs", "scripts", "README.md", ".gitignore", "package.json"];
13
+
14
+ const EXCLUDE_DIRS = new Set([
15
+ "node_modules",
16
+ ".git",
17
+ "dist",
18
+ "coverage",
19
+ "packages"
20
+ ]);
21
+
22
+ if (existsSync(target)) {
23
+ rmSync(target, { recursive: true, force: true });
24
+ }
25
+ mkdirSync(target, { recursive: true });
26
+
27
+ for (const item of COPY_ROOTS) {
28
+ const src = join(root, item);
29
+ if (!existsSync(src)) {
30
+ console.warn(`Skip missing: ${item}`);
31
+ continue;
32
+ }
33
+ const dest = join(target, item);
34
+ cpSync(src, dest, {
35
+ recursive: true,
36
+ filter: (sourcePath) => {
37
+ const parts = sourcePath.split(/[/\\]/);
38
+ return !parts.some((part) => EXCLUDE_DIRS.has(part));
39
+ }
40
+ });
41
+ console.log(`✓ ${item}`);
42
+ }
43
+
44
+ console.log(`\nTemplate synced to ${target}`);
@@ -168,6 +168,7 @@ async function main() {
168
168
  repositoryTree: {
169
169
  capturedAt: new Date().toISOString(),
170
170
  excludeDirs: TREE_IGNORE_DIRS,
171
+ excludePrefixes: tree.excludePrefixes,
171
172
  treeIgnoreFlag: 'tree -I "node_modules|.git|dist|build"',
172
173
  stats: tree.stats,
173
174
  treeText: tree.treeText,