@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.
- package/LICENSE +50 -0
- package/README.md +145 -62
- package/package.json +3 -2
- package/template/.cursor/rules/file-exchange-inbox.mdc +1 -1
- package/template/ARCHITECTURE_EXPORT_README.md +30 -0
- package/template/EXPORT_MANIFEST.json +74 -0
- package/template/LICENSE +11 -0
- package/template/NOTICE +12 -0
- package/template/README.md +36 -25
- package/template/backend/package.json +1 -1
- package/template/backend/src/modules/model-condenser/services/modelCondenser.service.js +36 -465
- package/template/backend/src/modules/model-condenser/tests/unit/modelCondenser.service.test.js +2 -3
- package/template/backend/src/shared/utils/consolidatedExport.js +155 -11
- package/template/backend/src/shared/utils/consolidatedExport.test.js +50 -0
- package/template/docs/README.md +0 -1
- package/template/docs/architecture/EVAL_AND_CI.md +68 -41
- package/template/docs/architecture/REPO_ARTIFACT_LAYOUT.md +57 -14
- package/template/docs/architecture/contracts/consolidatedExports.contract.md +14 -10
- package/template/docs/architecture/contracts/manifest.json +17 -0
- package/template/file-exchange/README.md +20 -15
- package/template/frontend/package.json +1 -1
- package/template/package.json +3 -3
- package/template/scripts/condense-all.mjs +52 -0
- package/template/scripts/condense-file-structure.mjs +19 -10
- package/template/scripts/condense-models.mjs +5 -3
- package/template/scripts/condense-prompts.mjs +13 -51
- package/template/scripts/consolidated-output.mjs +22 -10
- package/template/scripts/export-architecture-starter.mjs +389 -0
- package/template/scripts/lib/api-inventory.mjs +16 -61
- package/template/scripts/lib/repo-tree.mjs +26 -4
- package/template/scripts/sync-cli-template.mjs +44 -0
- package/template/scripts/write-pre-push-dev-log.mjs +1 -0
- 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
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
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"
|
|
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
|
-
|
|
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,
|