@pukujan/create-modular-monolith 2.0.0 → 2.1.0

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 (93) hide show
  1. package/README.md +91 -22
  2. package/index.js +47 -0
  3. package/package.json +16 -19
  4. package/template/.cursor/commands/planning-study-log.md +25 -0
  5. package/template/.cursor/commands/pre-push-dev-log.md +52 -0
  6. package/template/.cursor/rules/api-documentation.mdc +21 -0
  7. package/template/.cursor/rules/file-exchange-inbox.mdc +29 -0
  8. package/template/AGENTS.md +41 -0
  9. package/template/README.md +18 -57
  10. package/template/backend/.env.example +38 -0
  11. package/template/backend/package.json +14 -4
  12. package/template/backend/src/modules/model-condenser/README.md +7 -0
  13. package/template/backend/src/modules/model-condenser/config/index.js +20 -0
  14. package/template/backend/src/modules/model-condenser/events/index.js +1 -0
  15. package/template/backend/src/modules/model-condenser/index.js +12 -0
  16. package/template/backend/src/modules/model-condenser/routes/health.routes.js +10 -0
  17. package/template/backend/src/modules/model-condenser/routes/index.js +10 -0
  18. package/template/backend/src/modules/model-condenser/routes/modelCondenser.routes.js +44 -0
  19. package/template/backend/src/modules/model-condenser/services/health.service.js +8 -0
  20. package/template/backend/src/modules/model-condenser/services/modelCondenser.facade.js +58 -0
  21. package/template/backend/src/modules/model-condenser/services/modelCondenser.service.js +513 -0
  22. package/template/backend/src/modules/model-condenser/tests/integration/modelCondenser.routes.test.js +40 -0
  23. package/template/backend/src/modules/model-condenser/tests/unit/modelCondenser.service.test.js +31 -0
  24. package/template/backend/src/modules/model-condenser/utils/index.js +1 -0
  25. package/template/backend/src/shared/contracts/consolidatedExports.contract.js +19 -0
  26. package/template/backend/src/shared/contracts/prePushDevLog.contract.js +28 -0
  27. package/template/backend/src/shared/domain/case-filing/core-models.js +117 -0
  28. package/template/backend/src/shared/http/errors.js +8 -0
  29. package/template/backend/src/shared/utils/consolidatedExport.js +30 -0
  30. package/template/backend/src/shared/utils/formatExchangeTimestamp.js +47 -0
  31. package/template/backend/src/shared/utils/formatExchangeTimestamp.test.js +30 -0
  32. package/template/docs/API.md +42 -0
  33. package/template/docs/PUBLISHING.md +13 -1
  34. package/template/docs/README.md +4 -0
  35. package/template/docs/STARTER_PACK.md +4 -0
  36. package/template/docs/architecture/API_DOCUMENTATION_CONTRACT.md +112 -0
  37. package/template/docs/architecture/CONTRACTS_OVERVIEW.md +168 -0
  38. package/template/docs/architecture/MODULE_INTERNAL_CONTRACT.md +2 -0
  39. package/template/docs/architecture/PLATFORM_ARCHITECTURE.md +221 -0
  40. package/template/docs/architecture/REPO_ARTIFACT_LAYOUT.md +76 -0
  41. package/template/docs/architecture/contracts/apiDocumentationRegistry.contract.md +40 -0
  42. package/template/docs/architecture/contracts/changelog.jsonl +12 -0
  43. package/template/docs/architecture/contracts/consolidatedExports.contract.md +58 -0
  44. package/template/docs/architecture/contracts/fileExchange.contract.md +47 -0
  45. package/template/docs/architecture/contracts/manifest.json +56 -0
  46. package/template/docs/architecture/contracts/prePushDevLog.contract.md +69 -0
  47. package/template/docs/model-condenser/API.md +102 -0
  48. package/template/file-exchange/README.md +41 -0
  49. package/template/file-exchange/exports/.gitkeep +0 -0
  50. package/template/file-exchange/imports/.gitkeep +0 -0
  51. package/template/frontend/.env.example +2 -0
  52. package/template/frontend/package.json +1 -1
  53. package/template/frontend/src/index.css +311 -0
  54. package/template/frontend/src/modules/_reference/services/health-api.js +1 -1
  55. package/template/frontend/src/shared/api/client.js +67 -5
  56. package/template/models/.gitkeep +0 -0
  57. package/template/package.json +11 -4
  58. package/template/scripts/check-api-docs.mjs +183 -0
  59. package/template/scripts/condense-file-structure.mjs +44 -0
  60. package/template/scripts/condense-models.mjs +70 -0
  61. package/template/scripts/condense-prompts.mjs +161 -0
  62. package/template/scripts/consolidated-output.mjs +49 -0
  63. package/template/scripts/export-consolidated-models.mjs +11 -0
  64. package/template/scripts/git-hooks/pre-push.sample +15 -0
  65. package/template/scripts/import-to-file-exchange.mjs +43 -0
  66. package/template/scripts/lib/api-inventory.mjs +189 -0
  67. package/template/scripts/lib/dev-log-human-format.mjs +360 -0
  68. package/template/scripts/lib/git-snapshot.mjs +46 -0
  69. package/template/scripts/lib/module-scaffold.mjs +37 -1
  70. package/template/scripts/lib/repo-tree.mjs +127 -0
  71. package/template/scripts/lib/run-tests.mjs +60 -0
  72. package/template/scripts/lint-contracts.mjs +57 -0
  73. package/template/scripts/lint-repo-artifacts.mjs +37 -0
  74. package/template/scripts/new-module.mjs +7 -0
  75. package/template/scripts/resolve-import-stamp.mjs +50 -0
  76. package/template/scripts/verify-dev-log.mjs +50 -0
  77. package/template/scripts/write-pre-push-dev-log.mjs +220 -0
  78. package/template/work-log/INDEX.md +3 -0
  79. package/template/work-log/README.md +40 -0
  80. package/template/work-log/dev-logs/README.md +97 -0
  81. package/template/work-log/dev-logs/schemas/dev-log-agent.v1.schema.json +119 -0
  82. package/template/work-log/dev-logs/templates/dev-log-human.template.md +10 -0
  83. package/template/work-log/handoffs/README.md +36 -0
  84. package/template/work-log/study-docs/README.md +13 -0
  85. package/bin/create-modular-monolith.js +0 -132
  86. package/template/backend/package-lock.json +0 -882
  87. package/template/backend/src/modules/_reference/evals/README.md +0 -6
  88. package/template/backend/src/modules/_reference/evals/datasets/example.cases.json +0 -12
  89. package/template/backend/src/modules/_reference/evals/runners/example.eval.mjs +0 -25
  90. package/template/frontend/package-lock.json +0 -1724
  91. package/template/scripts/run-module-evals.mjs +0 -43
  92. package/template/scripts/sync-cli-template.mjs +0 -44
  93. /package/template/{frontend/src/modules → backend/db/migrations}/.gitkeep +0 -0
@@ -0,0 +1,44 @@
1
+ import { Router } from "express";
2
+ import { AppError } from "../../../shared/http/errors.js";
3
+
4
+ export function createModelCondenserRoutes({ config, modelCondenser }) {
5
+ const router = Router();
6
+
7
+ router.post("/condense", async (req, res, next) => {
8
+ try {
9
+ const includePayload = Boolean(req.body?.includePayload);
10
+ const result = await modelCondenser.condense({
11
+ includePayload
12
+ });
13
+ res.status(201).json(result);
14
+ } catch (error) {
15
+ next(error);
16
+ }
17
+ });
18
+
19
+ router.get("/consolidated", async (req, res, next) => {
20
+ try {
21
+ const includePayload = req.query.includePayload === "true";
22
+ const summary = await modelCondenser.getStatus();
23
+
24
+ if (!summary.exists) {
25
+ throw new AppError(
26
+ "Consolidated models file not found. POST /api/model-condenser/condense first.",
27
+ 404
28
+ );
29
+ }
30
+
31
+ if (!includePayload) {
32
+ res.json(summary);
33
+ return;
34
+ }
35
+
36
+ const consolidated = await modelCondenser.readConsolidated();
37
+ res.json({ ...summary, consolidated });
38
+ } catch (error) {
39
+ next(error);
40
+ }
41
+ });
42
+
43
+ return router;
44
+ }
@@ -0,0 +1,8 @@
1
+ export function getHealth(config) {
2
+ return {
3
+ status: "ok",
4
+ module: config.name,
5
+ label: config.label,
6
+ outputRelativePath: `models/${config.consolidatedFileName}`
7
+ };
8
+ }
@@ -0,0 +1,58 @@
1
+ import { access } from "fs/promises";
2
+ import { join } from "path";
3
+ import {
4
+ buildConsolidatedModels,
5
+ condenseModels,
6
+ readConsolidatedModels
7
+ } from "./modelCondenser.service.js";
8
+
9
+ export function createModelCondenserFacade({ config }) {
10
+ async function condense({ includePayload = false } = {}) {
11
+ return condenseModels({
12
+ repoRoot: config.repoRoot,
13
+ modelsDir: config.modelsDir,
14
+ consolidatedFileName: config.consolidatedFileName,
15
+ includePayload
16
+ });
17
+ }
18
+
19
+ async function readConsolidated() {
20
+ return readConsolidatedModels({
21
+ modelsDir: config.modelsDir,
22
+ consolidatedFileName: config.consolidatedFileName
23
+ });
24
+ }
25
+
26
+ async function getStatus() {
27
+ const outputPath = join(config.modelsDir, config.consolidatedFileName);
28
+ let exists = false;
29
+ let generatedAt = null;
30
+ let modelCount = null;
31
+
32
+ try {
33
+ await access(outputPath);
34
+ exists = true;
35
+ const data = await readConsolidated();
36
+ generatedAt = data.meta?.generatedAt ?? null;
37
+ modelCount = data.inventory?.length ?? null;
38
+ } catch {
39
+ exists = false;
40
+ }
41
+
42
+ return {
43
+ status: exists ? "ready" : "missing",
44
+ exists,
45
+ outputPath,
46
+ outputRelativePath: `models/${config.consolidatedFileName}`,
47
+ generatedAt,
48
+ modelCount
49
+ };
50
+ }
51
+
52
+ return {
53
+ condense,
54
+ readConsolidated,
55
+ getStatus,
56
+ buildConsolidatedModels: () => buildConsolidatedModels({ repoRoot: config.repoRoot })
57
+ };
58
+ }
@@ -0,0 +1,513 @@
1
+ import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { join, relative } from "path";
3
+
4
+ /**
5
+ * Scan the repo and build the consolidated model document (in memory).
6
+ * @param {{ repoRoot: string }} options
7
+ */
8
+ export async function buildConsolidatedModels({ repoRoot }) {
9
+ const ROOT = repoRoot;
10
+
11
+ async function readJson(relPath) {
12
+ const abs = join(ROOT, relPath);
13
+ const raw = await readFile(abs, "utf8");
14
+ return JSON.parse(raw);
15
+ }
16
+
17
+ async function tryReadJson(relPath) {
18
+ try {
19
+ return await readJson(relPath);
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ const models = {
26
+ meta: {
27
+ generatedAt: new Date().toISOString(),
28
+ repositoryRoot: ROOT,
29
+ condensedBy: "model-condenser",
30
+ description:
31
+ "Consolidated inventory of JSON shapes used by the legal-prmpt-eng case filing pipeline."
32
+ },
33
+ inventory: [],
34
+ definitions: {}
35
+ };
36
+
37
+ function addModel({
38
+ id,
39
+ name,
40
+ category,
41
+ sourcePaths,
42
+ description,
43
+ schema,
44
+ example,
45
+ exampleSourcePath
46
+ }) {
47
+ models.inventory.push({
48
+ id,
49
+ name,
50
+ category,
51
+ sourcePaths,
52
+ description,
53
+ hasSchema: Boolean(schema),
54
+ hasExample: Boolean(example),
55
+ exampleSourcePath: exampleSourcePath ?? null
56
+ });
57
+ models.definitions[id] = {
58
+ name,
59
+ category,
60
+ sourcePaths,
61
+ description,
62
+ schema: schema ?? null,
63
+ example: example ?? null
64
+ };
65
+ }
66
+
67
+ // --- Core domain (from handoffs/.../core-models.ts + shared JS typedefs) ---
68
+ addModel({
69
+ id: "CaseModel",
70
+ name: "CaseModel",
71
+ category: "core_domain",
72
+ sourcePaths: [
73
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/models/typescript/core-models.ts",
74
+ "backend/src/shared/domain/case-filing/core-models.js"
75
+ ],
76
+ description: "Top-level case identity and phase tracking.",
77
+ schema: {
78
+ caseId: "string",
79
+ county: "string | null",
80
+ court: "string | null",
81
+ indexNumber: "string | null",
82
+ caseName: "string | null",
83
+ caseType: "string | null",
84
+ judgeName: "string | null",
85
+ partName: "string | null",
86
+ currentPhase: "string | null",
87
+ currentMiniPhase: "string | null",
88
+ confidence: "high | medium | low"
89
+ },
90
+ example: await readJson("data/case-filing-ai/examples/case.json"),
91
+ exampleSourcePath: "data/case-filing-ai/examples/case.json"
92
+ });
93
+
94
+ addModel({
95
+ id: "DocumentModel",
96
+ name: "DocumentModel",
97
+ category: "core_domain",
98
+ sourcePaths: [
99
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/models/typescript/core-models.ts",
100
+ "backend/src/shared/domain/case-filing/core-models.js",
101
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/db/migrations/001_case_filing_ai_schema.sql"
102
+ ],
103
+ description: "A filing document linked to a case (NYSCEF-style metadata).",
104
+ schema: {
105
+ documentId: "string",
106
+ caseId: "string",
107
+ nyscefDocNo: "number | null",
108
+ title: "string | null",
109
+ documentType: "string | null",
110
+ filedDateTime: "string | null",
111
+ filedBy: "string | null",
112
+ sourceFileName: "string",
113
+ pageCount: "number | null",
114
+ extractionStatus: "string",
115
+ textReviewStatus: "unreviewed | partially_reviewed | reviewed | rejected"
116
+ }
117
+ });
118
+
119
+ addModel({
120
+ id: "DocumentTextVersionModel",
121
+ name: "DocumentTextVersionModel",
122
+ category: "core_domain",
123
+ sourcePaths: [
124
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/models/typescript/core-models.ts",
125
+ "backend/src/shared/domain/case-filing/core-models.js"
126
+ ],
127
+ description: "Versioned extracted or reviewed text for a document (filing-text-vault).",
128
+ schema: {
129
+ id: "string",
130
+ caseId: "string",
131
+ documentId: "string",
132
+ versionType: "embedded_text | ocr_text | ai_parsed_text | human_reviewed_text",
133
+ textContent: "string (optional)",
134
+ structuredJson: "unknown (optional)",
135
+ extractionMethod: "pdf_text | ocr | llm | human_review",
136
+ reviewStatus: "unreviewed | partially_reviewed | reviewed | rejected",
137
+ createdBy: "system | ai | human",
138
+ createdAt: "string (ISO datetime)"
139
+ }
140
+ });
141
+
142
+ addModel({
143
+ id: "TaskModel",
144
+ name: "TaskModel",
145
+ category: "core_domain",
146
+ sourcePaths: [
147
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/models/typescript/core-models.ts",
148
+ "backend/src/shared/domain/case-filing/core-models.js"
149
+ ],
150
+ description: "Docketing task with due date and workflow status.",
151
+ schema: {
152
+ taskId: "string",
153
+ caseId: "string",
154
+ documentId: "string (optional)",
155
+ taskDescription: "string",
156
+ taskType: "string",
157
+ responsibleParty: "string | null",
158
+ dueDate: "string | null",
159
+ dueDateStatus: "fixed | calculated | no_fixed_due_date | needs_review",
160
+ status:
161
+ "ai_extracted_unreviewed | source_supported_auto_saved | conditional | needs_ocr_review | corrected_later | superseded | human_verified",
162
+ sourcePage: "number (optional)",
163
+ confidence: "high | medium | low",
164
+ docketingNote: "string (optional)"
165
+ }
166
+ });
167
+
168
+ addModel({
169
+ id: "HumanReviewItemModel",
170
+ name: "HumanReviewItemModel",
171
+ category: "core_domain",
172
+ sourcePaths: [
173
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/models/typescript/core-models.ts",
174
+ "backend/src/shared/domain/case-filing/core-models.js"
175
+ ],
176
+ description: "Mandatory human review queue item (OCR, handwriting, stamps, etc.).",
177
+ schema: {
178
+ itemId: "string",
179
+ caseId: "string",
180
+ documentId: "string",
181
+ pageNumber: "number",
182
+ location: "string",
183
+ issue: "string",
184
+ reason: "string",
185
+ suggestedAction: "string",
186
+ cropFilePath: "string (optional)",
187
+ blocking: "boolean",
188
+ status: "pending | reviewed | resolved"
189
+ }
190
+ });
191
+
192
+ const emptySnapshot = {
193
+ snapshotId: null,
194
+ caseId: null,
195
+ afterDocNo: null,
196
+ currentPhase: null,
197
+ currentMiniPhase: null,
198
+ confirmedFacts: [],
199
+ carriedForwardContext: [],
200
+ openTasks: [],
201
+ completedTasks: [],
202
+ conditionalTasks: [],
203
+ deadlines: [],
204
+ supersededDeadlines: [],
205
+ unresolvedHumanReviewItems: [],
206
+ conflicts: [],
207
+ auditNotes: []
208
+ };
209
+
210
+ addModel({
211
+ id: "CaseStateSnapshotModel",
212
+ name: "CaseStateSnapshotModel",
213
+ category: "core_domain",
214
+ sourcePaths: [
215
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/models/typescript/core-models.ts",
216
+ "backend/src/shared/domain/case-filing/core-models.js",
217
+ "backend/src/modules/case-filing-ai/services/localJsonStore.service.js"
218
+ ],
219
+ description: "Rolling case state after each processed document.",
220
+ schema: {
221
+ snapshotId: "string | null",
222
+ caseId: "string | null",
223
+ afterDocNo: "number | null",
224
+ currentPhase: "string | null",
225
+ currentMiniPhase: "string | null",
226
+ confirmedFacts: "unknown[]",
227
+ carriedForwardContext: "unknown[]",
228
+ openTasks: "TaskModel[] | string[] (runtime varies)",
229
+ completedTasks: "TaskModel[] | string[]",
230
+ conditionalTasks: "TaskModel[] | string[]",
231
+ deadlines: "unknown[]",
232
+ supersededDeadlines: "unknown[]",
233
+ unresolvedHumanReviewItems: "HumanReviewItemModel[]",
234
+ conflicts: "unknown[]",
235
+ auditNotes: "string[]"
236
+ },
237
+ example: await readJson("data/case-filing-ai/examples/case-snapshot.json"),
238
+ exampleSourcePath: "data/case-filing-ai/examples/case-snapshot.json"
239
+ });
240
+
241
+ addModel({
242
+ id: "EmptyCaseStateSnapshot",
243
+ name: "EmptyCaseStateSnapshot",
244
+ category: "runtime_default",
245
+ sourcePaths: ["backend/src/modules/case-filing-ai/services/localJsonStore.service.js"],
246
+ description: "Initial snapshot written when a new batch is created.",
247
+ schema: emptySnapshot,
248
+ example: emptySnapshot
249
+ });
250
+
251
+ // --- Prompt output schemas ---
252
+ addModel({
253
+ id: "MasterCaseFilingPromptOutput",
254
+ name: "MasterCaseFilingPromptOutput",
255
+ category: "prompt_output",
256
+ sourcePaths: [
257
+ "backend/src/modules/case-filing-ai/prompts/master-case-filing.prompt.md",
258
+ "work-log/handoffs/002_2026-05-23_00-42_handoff_second.md"
259
+ ],
260
+ description: "Strict JSON returned by the master case-filing LLM prompt per document.",
261
+ schema: {
262
+ documentMetadata: "object",
263
+ extractionQuality: "object",
264
+ docketEntry: "object",
265
+ caseUpdates: "object",
266
+ parties: "array",
267
+ witnesses: "array",
268
+ tasks: "array",
269
+ deadlines: "array",
270
+ humanReviewItems: "array",
271
+ updatedCaseSnapshot: "object",
272
+ auditNotes: "array",
273
+ inferredPartRuleText: "string",
274
+ partRuleExtracts: "array"
275
+ }
276
+ });
277
+
278
+ addModel({
279
+ id: "PartRuleParsed",
280
+ name: "PartRuleParsed",
281
+ category: "prompt_output",
282
+ sourcePaths: ["backend/src/modules/case-filing-ai/prompts/rule-parse.prompt.md"],
283
+ description: "Structured part/judge rules after parsing user-supplied rule text.",
284
+ schema: {
285
+ partName: "string | null",
286
+ judgeName: "string | null",
287
+ county: "string | null",
288
+ court: "string | null",
289
+ rules: "string[]",
290
+ schedulingNotes: "string[]",
291
+ deadlinePolicies: "string[]",
292
+ sourceSummary: "string",
293
+ confidence: "high | medium | low"
294
+ },
295
+ example: await tryReadJson("data/case-filing-ai/batches/batch-002/rule/part-rules-parsed.json"),
296
+ exampleSourcePath: "data/case-filing-ai/batches/batch-002/rule/part-rules-parsed.json"
297
+ });
298
+
299
+ addModel({
300
+ id: "PartRuleStoredRecord",
301
+ name: "PartRuleStoredRecord",
302
+ category: "pipeline_storage",
303
+ sourcePaths: ["backend/src/modules/case-filing-ai/services/uploadBatch.service.js"],
304
+ description: "part-rules-parsed.json on disk: PartRuleParsed plus batch metadata.",
305
+ schema: {
306
+ source: "user_paste | user_upload | inferred_from_filings | pending_inference | none",
307
+ savedAt: "string (ISO datetime)",
308
+ extraction: "PartRuleExtraction | null",
309
+ inferredFromDocs: "array (optional)",
310
+ "...PartRuleParsed fields": "see PartRuleParsed"
311
+ }
312
+ });
313
+
314
+ addModel({
315
+ id: "PartRuleExtraction",
316
+ name: "PartRuleExtraction",
317
+ category: "pipeline_storage",
318
+ sourcePaths: ["backend/src/modules/case-filing-ai/services/uploadBatch.service.js"],
319
+ description: "part-rules-extraction.json: file upload metadata for a part rule file.",
320
+ schema: {
321
+ storedName: "string",
322
+ originalName: "string",
323
+ fileKind: "pdf | text | image | office | binary",
324
+ extractionQuality: "ExtractionQuality",
325
+ mimeType: "string",
326
+ sizeBytes: "number"
327
+ },
328
+ example: await tryReadJson("data/case-filing-ai/batches/batch-002/rule/part-rules-extraction.json"),
329
+ exampleSourcePath: "data/case-filing-ai/batches/batch-002/rule/part-rules-extraction.json"
330
+ });
331
+
332
+ addModel({
333
+ id: "ExtractionQuality",
334
+ name: "ExtractionQuality",
335
+ category: "pipeline_runtime",
336
+ sourcePaths: ["backend/src/modules/case-filing-ai/services/documentText.service.js"],
337
+ description: "Text extraction quality flags for uploads and filings.",
338
+ schema: {
339
+ method: "string",
340
+ fileKind: "string",
341
+ textLength: "number",
342
+ ocr_needed: "boolean",
343
+ ocr_used: "boolean",
344
+ ocr_model: "string | null",
345
+ ocr_pages: "number[] | null",
346
+ reviewStatus: "ai_extracted_unreviewed | ...",
347
+ note: "string | null"
348
+ }
349
+ });
350
+
351
+ addModel({
352
+ id: "DocumentProcessingOutput",
353
+ name: "DocumentProcessingOutput",
354
+ category: "pipeline_storage",
355
+ sourcePaths: [
356
+ "backend/src/modules/case-filing-ai/services/uploadBatch.service.js",
357
+ "data/case-filing-ai/batches/batch-002/outputs/doc-001.json"
358
+ ],
359
+ description: "Per-document JSON saved under data/.../outputs/doc-NNN.json after LLM processing.",
360
+ schema: {
361
+ docKey: "string",
362
+ docIndex: "number",
363
+ storedName: "string",
364
+ originalName: "string",
365
+ fileKind: "string",
366
+ model: "string",
367
+ usage: "object",
368
+ documentMetadata: "object",
369
+ extractionQuality: "object",
370
+ docketEntry: "object",
371
+ caseUpdates: "object",
372
+ parties: "array",
373
+ witnesses: "array",
374
+ tasks: "array",
375
+ deadlines: "array",
376
+ humanReviewItems: "array",
377
+ auditNotes: "array"
378
+ },
379
+ example: await tryReadJson("data/case-filing-ai/batches/batch-002/outputs/doc-001.json"),
380
+ exampleSourcePath: "data/case-filing-ai/batches/batch-002/outputs/doc-001.json"
381
+ });
382
+
383
+ addModel({
384
+ id: "BatchProcessingResult",
385
+ name: "BatchProcessingResult",
386
+ category: "api_response",
387
+ sourcePaths: ["backend/src/modules/case-filing-ai/services/uploadBatch.service.js"],
388
+ description: "POST /process-batch and GET /batches/:id/results response body.",
389
+ schema: {
390
+ batchId: "string",
391
+ caseSnapshot: "CaseStateSnapshotModel",
392
+ documents: "DocumentProcessingOutput[]",
393
+ tasks: "array (aggregated)",
394
+ deadlines: "array (aggregated)",
395
+ humanReviewItems: "array (aggregated)",
396
+ partRule: "PartRuleStoredRecord | null"
397
+ }
398
+ });
399
+
400
+ addModel({
401
+ id: "BatchStatus",
402
+ name: "BatchStatus",
403
+ category: "api_response",
404
+ sourcePaths: ["backend/src/modules/case-filing-ai/services/uploadBatch.service.js"],
405
+ description: "GET /batches/:id/status response body.",
406
+ schema: {
407
+ batchId: "string",
408
+ status: "pending | processing | completed",
409
+ currentStep: "string",
410
+ currentDocument: "string | null",
411
+ processedCount: "number",
412
+ totalCount: "number"
413
+ }
414
+ });
415
+
416
+ addModel({
417
+ id: "RuleTextExtractResponse",
418
+ name: "RuleTextExtractResponse",
419
+ category: "api_response",
420
+ sourcePaths: [
421
+ "backend/src/modules/case-filing-ai/services/ruleText.service.js",
422
+ "backend/src/modules/case-filing-ai/routes/caseFiling.routes.js"
423
+ ],
424
+ description: "POST /extract-rule-text response body.",
425
+ schema: {
426
+ text: "string",
427
+ fileKind: "string",
428
+ extractionQuality: "ExtractionQuality",
429
+ originalName: "string"
430
+ }
431
+ });
432
+
433
+ // --- Example JSON files (instances, also listed) ---
434
+ const exampleJsonPaths = [
435
+ "data/case-filing-ai/examples/case.json",
436
+ "data/case-filing-ai/examples/case-snapshot.json",
437
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/examples/local-json/case.json",
438
+ "work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/examples/local-json/case-snapshot.json",
439
+ "data/case-filing-ai/batches/batch-001/case-snapshot.json",
440
+ "data/case-filing-ai/batches/batch-002/case-snapshot.json",
441
+ "data/case-filing-ai/batches/batch-002/outputs/doc-002.json"
442
+ ];
443
+
444
+ models.exampleInstances = {};
445
+ for (const rel of exampleJsonPaths) {
446
+ const data = await tryReadJson(rel);
447
+ if (data) {
448
+ models.exampleInstances[relative(ROOT, rel)] = data;
449
+ }
450
+ }
451
+
452
+ // Module eval datasets (health-check fixtures, not domain models)
453
+ models.evalDatasets = {
454
+ description: "Per-module eval fixture files (API health shape tests, not case domain models).",
455
+ paths: [
456
+ "backend/src/modules/_reference/evals/datasets/example.cases.json",
457
+ "backend/src/modules/case-filing-ai/evals/datasets/example.cases.json",
458
+ "backend/src/modules/case-workflow/evals/datasets/example.cases.json",
459
+ "backend/src/modules/court-rules/evals/datasets/example.cases.json",
460
+ "backend/src/modules/filing-pipeline/evals/datasets/example.cases.json",
461
+ "backend/src/modules/filing-text-vault/evals/datasets/example.cases.json",
462
+ "backend/src/modules/human-review/evals/datasets/example.cases.json",
463
+ "backend/src/modules/task-docketing/evals/datasets/example.cases.json"
464
+ ],
465
+ sharedShape: await tryReadJson("backend/src/modules/task-docketing/evals/datasets/example.cases.json")
466
+ };
467
+
468
+ return models;
469
+ }
470
+
471
+ /**
472
+ * Model condenser: writes models/consolidated-models.json and returns a summary.
473
+ * @param {{ repoRoot: string, modelsDir: string, consolidatedFileName?: string, writeFile?: boolean, includePayload?: boolean }} options
474
+ */
475
+ export async function condenseModels({
476
+ repoRoot,
477
+ modelsDir,
478
+ consolidatedFileName = "consolidated-models.json",
479
+ writeFile: shouldWrite = true,
480
+ includePayload = false
481
+ }) {
482
+ const consolidated = await buildConsolidatedModels({ repoRoot });
483
+ const outputPath = join(modelsDir, consolidatedFileName);
484
+
485
+ if (shouldWrite) {
486
+ await mkdir(modelsDir, { recursive: true });
487
+ const jsonText = `${JSON.stringify(consolidated, null, 2)}\n`;
488
+ await writeFile(outputPath, jsonText, "utf8");
489
+ const { writeConsolidatedExport } = await import(
490
+ "../../../shared/utils/consolidatedExport.js"
491
+ );
492
+ await writeConsolidatedExport(repoRoot, consolidatedFileName, jsonText);
493
+ }
494
+
495
+ const exampleInstanceCount = Object.keys(consolidated.exampleInstances ?? {}).length;
496
+
497
+ return {
498
+ status: "condensed",
499
+ outputPath,
500
+ outputRelativePath: relative(repoRoot, outputPath),
501
+ modelCount: consolidated.inventory.length,
502
+ exampleInstanceCount,
503
+ generatedAt: consolidated.meta.generatedAt,
504
+ inventory: consolidated.inventory,
505
+ ...(includePayload ? { consolidated } : {})
506
+ };
507
+ }
508
+
509
+ export async function readConsolidatedModels({ modelsDir, consolidatedFileName = "consolidated-models.json" }) {
510
+ const outputPath = join(modelsDir, consolidatedFileName);
511
+ const raw = await readFile(outputPath, "utf8");
512
+ return JSON.parse(raw);
513
+ }
@@ -0,0 +1,40 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, rm } from "fs/promises";
4
+ import { join } from "path";
5
+ import { tmpdir } from "os";
6
+ import { createTestApp } from "../../../../shared/testing/create-test-app.js";
7
+
8
+ test("POST /api/model-condenser/condense regenerates consolidated file", async () => {
9
+ const modelsDir = await mkdtemp(join(tmpdir(), "model-condenser-api-"));
10
+ process.env.MODEL_CONDENSER_OUTPUT_DIR = modelsDir;
11
+
12
+ const { register } = await import("../../index.js");
13
+ const app = createTestApp(register);
14
+ const server = app.listen(0);
15
+ const { port } = server.address();
16
+
17
+ try {
18
+ const res = await fetch(`http://127.0.0.1:${port}/api/model-condenser/condense`, {
19
+ method: "POST",
20
+ headers: { "Content-Type": "application/json" },
21
+ body: JSON.stringify({ includePayload: false })
22
+ });
23
+
24
+ assert.equal(res.status, 201);
25
+ const body = await res.json();
26
+ assert.equal(body.status, "condensed");
27
+ assert.ok(body.modelCount >= 16);
28
+ assert.ok(body.outputPath.includes("consolidated-models.json"));
29
+
30
+ const getRes = await fetch(`http://127.0.0.1:${port}/api/model-condenser/consolidated`);
31
+ assert.equal(getRes.status, 200);
32
+ const summary = await getRes.json();
33
+ assert.equal(summary.status, "ready");
34
+ assert.equal(summary.modelCount, body.modelCount);
35
+ } finally {
36
+ server.close();
37
+ delete process.env.MODEL_CONDENSER_OUTPUT_DIR;
38
+ await rm(modelsDir, { recursive: true, force: true });
39
+ }
40
+ });
@@ -0,0 +1,31 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, readFile, rm } from "fs/promises";
4
+ import { join } from "path";
5
+ import { tmpdir } from "os";
6
+ import { condenseModels } from "../../services/modelCondenser.service.js";
7
+ import { getModuleConfig } from "../../config/index.js";
8
+
9
+ const { repoRoot } = getModuleConfig();
10
+
11
+ test("condenseModels writes consolidated-models.json", async () => {
12
+ const modelsDir = await mkdtemp(join(tmpdir(), "model-condenser-"));
13
+ try {
14
+ const result = await condenseModels({
15
+ repoRoot,
16
+ modelsDir,
17
+ consolidatedFileName: "consolidated-models.json"
18
+ });
19
+
20
+ assert.equal(result.status, "condensed");
21
+ assert.ok(result.modelCount >= 16);
22
+ assert.ok(result.exampleInstanceCount >= 4);
23
+
24
+ const raw = await readFile(join(modelsDir, "consolidated-models.json"), "utf8");
25
+ const parsed = JSON.parse(raw);
26
+ assert.equal(parsed.meta.condensedBy, "model-condenser");
27
+ assert.ok(parsed.inventory.some((entry) => entry.id === "CaseModel"));
28
+ } finally {
29
+ await rm(modelsDir, { recursive: true, force: true });
30
+ }
31
+ });