@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.
- package/README.md +91 -22
- package/index.js +47 -0
- package/package.json +16 -19
- package/template/.cursor/commands/planning-study-log.md +25 -0
- package/template/.cursor/commands/pre-push-dev-log.md +52 -0
- package/template/.cursor/rules/api-documentation.mdc +21 -0
- package/template/.cursor/rules/file-exchange-inbox.mdc +29 -0
- package/template/AGENTS.md +41 -0
- package/template/README.md +18 -57
- package/template/backend/.env.example +38 -0
- package/template/backend/package.json +14 -4
- package/template/backend/src/modules/model-condenser/README.md +7 -0
- package/template/backend/src/modules/model-condenser/config/index.js +20 -0
- package/template/backend/src/modules/model-condenser/events/index.js +1 -0
- package/template/backend/src/modules/model-condenser/index.js +12 -0
- package/template/backend/src/modules/model-condenser/routes/health.routes.js +10 -0
- package/template/backend/src/modules/model-condenser/routes/index.js +10 -0
- package/template/backend/src/modules/model-condenser/routes/modelCondenser.routes.js +44 -0
- package/template/backend/src/modules/model-condenser/services/health.service.js +8 -0
- package/template/backend/src/modules/model-condenser/services/modelCondenser.facade.js +58 -0
- package/template/backend/src/modules/model-condenser/services/modelCondenser.service.js +513 -0
- package/template/backend/src/modules/model-condenser/tests/integration/modelCondenser.routes.test.js +40 -0
- package/template/backend/src/modules/model-condenser/tests/unit/modelCondenser.service.test.js +31 -0
- package/template/backend/src/modules/model-condenser/utils/index.js +1 -0
- package/template/backend/src/shared/contracts/consolidatedExports.contract.js +19 -0
- package/template/backend/src/shared/contracts/prePushDevLog.contract.js +28 -0
- package/template/backend/src/shared/domain/case-filing/core-models.js +117 -0
- package/template/backend/src/shared/http/errors.js +8 -0
- package/template/backend/src/shared/utils/consolidatedExport.js +30 -0
- package/template/backend/src/shared/utils/formatExchangeTimestamp.js +47 -0
- package/template/backend/src/shared/utils/formatExchangeTimestamp.test.js +30 -0
- package/template/docs/API.md +42 -0
- package/template/docs/PUBLISHING.md +13 -1
- package/template/docs/README.md +4 -0
- package/template/docs/STARTER_PACK.md +4 -0
- package/template/docs/architecture/API_DOCUMENTATION_CONTRACT.md +112 -0
- package/template/docs/architecture/CONTRACTS_OVERVIEW.md +168 -0
- package/template/docs/architecture/MODULE_INTERNAL_CONTRACT.md +2 -0
- package/template/docs/architecture/PLATFORM_ARCHITECTURE.md +221 -0
- package/template/docs/architecture/REPO_ARTIFACT_LAYOUT.md +76 -0
- package/template/docs/architecture/contracts/apiDocumentationRegistry.contract.md +40 -0
- package/template/docs/architecture/contracts/changelog.jsonl +12 -0
- package/template/docs/architecture/contracts/consolidatedExports.contract.md +58 -0
- package/template/docs/architecture/contracts/fileExchange.contract.md +47 -0
- package/template/docs/architecture/contracts/manifest.json +56 -0
- package/template/docs/architecture/contracts/prePushDevLog.contract.md +69 -0
- package/template/docs/model-condenser/API.md +102 -0
- package/template/file-exchange/README.md +41 -0
- package/template/file-exchange/exports/.gitkeep +0 -0
- package/template/file-exchange/imports/.gitkeep +0 -0
- package/template/frontend/.env.example +2 -0
- package/template/frontend/package.json +1 -1
- package/template/frontend/src/index.css +311 -0
- package/template/frontend/src/modules/_reference/services/health-api.js +1 -1
- package/template/frontend/src/shared/api/client.js +67 -5
- package/template/models/.gitkeep +0 -0
- package/template/package.json +11 -4
- package/template/scripts/check-api-docs.mjs +183 -0
- package/template/scripts/condense-file-structure.mjs +44 -0
- package/template/scripts/condense-models.mjs +70 -0
- package/template/scripts/condense-prompts.mjs +161 -0
- package/template/scripts/consolidated-output.mjs +49 -0
- package/template/scripts/export-consolidated-models.mjs +11 -0
- package/template/scripts/git-hooks/pre-push.sample +15 -0
- package/template/scripts/import-to-file-exchange.mjs +43 -0
- package/template/scripts/lib/api-inventory.mjs +189 -0
- package/template/scripts/lib/dev-log-human-format.mjs +360 -0
- package/template/scripts/lib/git-snapshot.mjs +46 -0
- package/template/scripts/lib/module-scaffold.mjs +37 -1
- package/template/scripts/lib/repo-tree.mjs +127 -0
- package/template/scripts/lib/run-tests.mjs +60 -0
- package/template/scripts/lint-contracts.mjs +57 -0
- package/template/scripts/lint-repo-artifacts.mjs +37 -0
- package/template/scripts/new-module.mjs +7 -0
- package/template/scripts/resolve-import-stamp.mjs +50 -0
- package/template/scripts/verify-dev-log.mjs +50 -0
- package/template/scripts/write-pre-push-dev-log.mjs +220 -0
- package/template/work-log/INDEX.md +3 -0
- package/template/work-log/README.md +40 -0
- package/template/work-log/dev-logs/README.md +97 -0
- package/template/work-log/dev-logs/schemas/dev-log-agent.v1.schema.json +119 -0
- package/template/work-log/dev-logs/templates/dev-log-human.template.md +10 -0
- package/template/work-log/handoffs/README.md +36 -0
- package/template/work-log/study-docs/README.md +13 -0
- package/bin/create-modular-monolith.js +0 -132
- package/template/backend/package-lock.json +0 -882
- package/template/backend/src/modules/_reference/evals/README.md +0 -6
- package/template/backend/src/modules/_reference/evals/datasets/example.cases.json +0 -12
- package/template/backend/src/modules/_reference/evals/runners/example.eval.mjs +0 -25
- package/template/frontend/package-lock.json +0 -1724
- package/template/scripts/run-module-evals.mjs +0 -43
- package/template/scripts/sync-cli-template.mjs +0 -44
- /package/template/{frontend/src/modules → backend/db/migrations}/.gitkeep +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Walk the repository and write consolidated-file-structure.json
|
|
4
|
+
* (excludes node_modules, .git, dist, build — same as tree -I).
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/condense-file-structure.mjs
|
|
7
|
+
*/
|
|
8
|
+
import { join, dirname } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { writeConsolidatedArtifact } from "./consolidated-output.mjs";
|
|
11
|
+
import { buildRepoTree, TREE_IGNORE_DIRS, TREE_IGNORE_FILES } from "./lib/repo-tree.mjs";
|
|
12
|
+
|
|
13
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const { rootName, tree, treeText, stats, flatPaths } = await buildRepoTree(repoRoot);
|
|
17
|
+
|
|
18
|
+
const doc = {
|
|
19
|
+
meta: {
|
|
20
|
+
generatedAt: new Date().toISOString(),
|
|
21
|
+
repositoryRoot: repoRoot,
|
|
22
|
+
condensedBy: "condense-file-structure",
|
|
23
|
+
description:
|
|
24
|
+
"Repository file tree for legal-prmpt-eng (excludes node_modules, .git, dist, build).",
|
|
25
|
+
excludeDirs: TREE_IGNORE_DIRS,
|
|
26
|
+
excludeFiles: TREE_IGNORE_FILES,
|
|
27
|
+
treeIgnoreFlag: 'tree -I "node_modules|.git|dist|build"'
|
|
28
|
+
},
|
|
29
|
+
stats,
|
|
30
|
+
tree,
|
|
31
|
+
treeText,
|
|
32
|
+
flatPaths
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const { exportPath, modelsPath } = await writeConsolidatedArtifact("fileStructure", doc);
|
|
36
|
+
console.log(
|
|
37
|
+
`Consolidated ${stats.fileCount} files, ${stats.directoryCount} dirs → ${exportPath} (+ ${modelsPath})`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
main().catch((err) => {
|
|
42
|
+
console.error(err);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Model condenser CLI — calls POST /api/model-condenser/condense (or --local direct service).
|
|
4
|
+
* Always copies consolidated-models.json → file-exchange/exports/.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npm run condense-models
|
|
8
|
+
* node scripts/condense-models.mjs --local
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from "fs/promises";
|
|
11
|
+
import { join, dirname } from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
const args = new Set(process.argv.slice(2));
|
|
15
|
+
const useLocal = args.has("--local");
|
|
16
|
+
const includePayload = args.has("--include-payload");
|
|
17
|
+
const baseUrl = process.env.API_BASE_URL || "http://localhost:3001";
|
|
18
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
|
+
|
|
20
|
+
async function condenseViaApi() {
|
|
21
|
+
const res = await fetch(`${baseUrl}/api/model-condenser/condense`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify({ includePayload })
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const body = await res.json().catch(() => ({}));
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(body.error || body.message || `HTTP ${res.status}`);
|
|
30
|
+
}
|
|
31
|
+
return body;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function condenseLocal() {
|
|
35
|
+
const { getModuleConfig } = await import(
|
|
36
|
+
"../backend/src/modules/model-condenser/config/index.js"
|
|
37
|
+
);
|
|
38
|
+
const { condenseModels } = await import(
|
|
39
|
+
"../backend/src/modules/model-condenser/services/modelCondenser.service.js"
|
|
40
|
+
);
|
|
41
|
+
const config = getModuleConfig();
|
|
42
|
+
return condenseModels({
|
|
43
|
+
repoRoot: config.repoRoot,
|
|
44
|
+
modelsDir: config.modelsDir,
|
|
45
|
+
consolidatedFileName: config.consolidatedFileName,
|
|
46
|
+
includePayload
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const result = useLocal ? await condenseLocal() : await condenseViaApi();
|
|
52
|
+
const { CONSOLIDATED_EXPORT_DIR, CONSOLIDATED_FILENAMES, writeConsolidatedExport } =
|
|
53
|
+
await import("../backend/src/shared/utils/consolidatedExport.js");
|
|
54
|
+
const modelsPath = join(repoRoot, "models", CONSOLIDATED_FILENAMES.models);
|
|
55
|
+
const json = await readFile(modelsPath, "utf8");
|
|
56
|
+
await writeConsolidatedExport(repoRoot, CONSOLIDATED_FILENAMES.models, json);
|
|
57
|
+
const exportRel = `${CONSOLIDATED_EXPORT_DIR}/${CONSOLIDATED_FILENAMES.models}`;
|
|
58
|
+
|
|
59
|
+
console.log(`Model condenser: ${result.modelCount} models`);
|
|
60
|
+
console.log(` → ${exportRel}`);
|
|
61
|
+
console.log(` → models/${CONSOLIDATED_FILENAMES.models} (API mirror)`);
|
|
62
|
+
console.log(`Generated at: ${result.generatedAt}`);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (!useLocal) {
|
|
65
|
+
console.error(`API condense failed (${error.message}). Retry with --local or start the backend.`);
|
|
66
|
+
} else {
|
|
67
|
+
console.error(error.message);
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Collect all versioned prompt templates into models/consolidated-prompts.json
|
|
4
|
+
* Usage: node scripts/condense-prompts.mjs
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
7
|
+
import { join, relative, dirname } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { writeConsolidatedArtifact } from "./consolidated-output.mjs";
|
|
10
|
+
|
|
11
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
|
|
13
|
+
const SCAN_ROOTS = [
|
|
14
|
+
"backend/src/modules",
|
|
15
|
+
"work-log/handoffs/001_2026-05-23_starter_case-filing-ai-updated/prompts"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const PROMPT_EXTENSIONS = [".prompt.md", ".prompt.js"];
|
|
19
|
+
|
|
20
|
+
function extractVariables(text) {
|
|
21
|
+
const vars = new Set();
|
|
22
|
+
const re = /\{\{(\w+)\}\}/g;
|
|
23
|
+
let m;
|
|
24
|
+
while ((m = re.exec(text)) !== null) {
|
|
25
|
+
vars.add(m[1]);
|
|
26
|
+
}
|
|
27
|
+
return [...vars].sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function walk(dir, acc = []) {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
34
|
+
} catch {
|
|
35
|
+
return acc;
|
|
36
|
+
}
|
|
37
|
+
for (const ent of entries) {
|
|
38
|
+
const full = join(dir, ent.name);
|
|
39
|
+
if (ent.isDirectory()) {
|
|
40
|
+
if (ent.name === "node_modules" || ent.name.startsWith(".")) continue;
|
|
41
|
+
await walk(full, acc);
|
|
42
|
+
} else if (PROMPT_EXTENSIONS.some((ext) => ent.name.endsWith(ext))) {
|
|
43
|
+
acc.push(full);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function findManifests() {
|
|
50
|
+
const manifests = {};
|
|
51
|
+
async function scanManifests(dir) {
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const ent of entries) {
|
|
59
|
+
const full = join(dir, ent.name);
|
|
60
|
+
if (ent.isDirectory()) {
|
|
61
|
+
if (ent.name === "node_modules" || ent.name.startsWith(".")) continue;
|
|
62
|
+
await scanManifests(full);
|
|
63
|
+
} else if (ent.name === "manifest.json" && full.includes("/prompts/")) {
|
|
64
|
+
const rel = relative(repoRoot, full);
|
|
65
|
+
try {
|
|
66
|
+
manifests[rel] = JSON.parse(await readFile(full, "utf8"));
|
|
67
|
+
} catch {
|
|
68
|
+
/* skip */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
await scanManifests(join(repoRoot, "backend/src/modules"));
|
|
74
|
+
return manifests;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function loadCaseFilingVersions() {
|
|
78
|
+
const path = join(
|
|
79
|
+
repoRoot,
|
|
80
|
+
"backend/src/modules/case-filing-ai/prompts/promptVersions.js"
|
|
81
|
+
);
|
|
82
|
+
const raw = await readFile(path, "utf8");
|
|
83
|
+
const versions = {};
|
|
84
|
+
const blockRe = /(\w+):\s*\{[^}]*id:\s*"([^"]+)"[^}]*masterCaseFiling:\s*"([^"]+)"[^}]*description:\s*"([^"]+)"/gs;
|
|
85
|
+
let m;
|
|
86
|
+
while ((m = blockRe.exec(raw)) !== null) {
|
|
87
|
+
versions[m[2]] = {
|
|
88
|
+
key: m[1],
|
|
89
|
+
id: m[2],
|
|
90
|
+
masterCaseFiling: m[3],
|
|
91
|
+
description: m[4]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return { sourcePath: relative(repoRoot, path), versions };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const promptFiles = [];
|
|
99
|
+
for (const root of SCAN_ROOTS) {
|
|
100
|
+
const found = await walk(join(repoRoot, root));
|
|
101
|
+
promptFiles.push(...found.filter((f) => !f.endsWith("manifest.json")));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
promptFiles.sort();
|
|
105
|
+
|
|
106
|
+
const inventory = [];
|
|
107
|
+
const prompts = {};
|
|
108
|
+
|
|
109
|
+
for (const abs of promptFiles) {
|
|
110
|
+
const rel = relative(repoRoot, abs);
|
|
111
|
+
const content = await readFile(abs, "utf8");
|
|
112
|
+
const st = await stat(abs);
|
|
113
|
+
const moduleMatch = rel.match(/^backend\/src\/modules\/([^/]+)/);
|
|
114
|
+
const module = moduleMatch ? moduleMatch[1] : "starter-handoff";
|
|
115
|
+
const id = rel
|
|
116
|
+
.replace(/^backend\/src\/modules\/[^/]+\/prompts\//, "")
|
|
117
|
+
.replace(/^work-log\/handoffs\/[^/]+\/prompts\//, "starter/")
|
|
118
|
+
.replace(/[/.]/g, "_");
|
|
119
|
+
|
|
120
|
+
const entry = {
|
|
121
|
+
id,
|
|
122
|
+
path: rel,
|
|
123
|
+
module,
|
|
124
|
+
fileName: abs.split("/").pop(),
|
|
125
|
+
variables: extractVariables(content),
|
|
126
|
+
byteLength: Buffer.byteLength(content, "utf8"),
|
|
127
|
+
modifiedAt: st.mtime.toISOString()
|
|
128
|
+
};
|
|
129
|
+
inventory.push(entry);
|
|
130
|
+
prompts[rel] = {
|
|
131
|
+
...entry,
|
|
132
|
+
content
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const caseFilingVersions = await loadCaseFilingVersions();
|
|
137
|
+
const moduleManifests = await findManifests();
|
|
138
|
+
|
|
139
|
+
const doc = {
|
|
140
|
+
meta: {
|
|
141
|
+
generatedAt: new Date().toISOString(),
|
|
142
|
+
repositoryRoot: repoRoot,
|
|
143
|
+
condensedBy: "condense-prompts",
|
|
144
|
+
description:
|
|
145
|
+
"Consolidated prompt templates for legal-prmpt-eng (active + starter reference).",
|
|
146
|
+
promptCount: inventory.length
|
|
147
|
+
},
|
|
148
|
+
caseFilingPromptVersions: caseFilingVersions,
|
|
149
|
+
moduleManifests,
|
|
150
|
+
inventory,
|
|
151
|
+
prompts
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const { exportPath, modelsPath } = await writeConsolidatedArtifact("prompts", doc);
|
|
155
|
+
console.log(`Consolidated ${inventory.length} prompts → ${exportPath} (+ ${modelsPath})`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
main().catch((err) => {
|
|
159
|
+
console.error(err);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import {
|
|
5
|
+
CONSOLIDATED_EXPORT_DIR,
|
|
6
|
+
CONSOLIDATED_FILENAMES,
|
|
7
|
+
writeConsolidatedExport
|
|
8
|
+
} from "../backend/src/shared/utils/consolidatedExport.js";
|
|
9
|
+
|
|
10
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
|
|
12
|
+
export { CONSOLIDATED_EXPORT_DIR, CONSOLIDATED_FILENAMES };
|
|
13
|
+
|
|
14
|
+
/** Repo-relative paths: exports/ is primary; models/ mirrors for model-condenser API. */
|
|
15
|
+
export const CONSOLIDATED_ARTIFACTS = {
|
|
16
|
+
models: {
|
|
17
|
+
filename: CONSOLIDATED_FILENAMES.models,
|
|
18
|
+
exportPath: `${CONSOLIDATED_EXPORT_DIR}/${CONSOLIDATED_FILENAMES.models}`,
|
|
19
|
+
modelsPath: `models/${CONSOLIDATED_FILENAMES.models}`
|
|
20
|
+
},
|
|
21
|
+
prompts: {
|
|
22
|
+
filename: CONSOLIDATED_FILENAMES.prompts,
|
|
23
|
+
exportPath: `${CONSOLIDATED_EXPORT_DIR}/${CONSOLIDATED_FILENAMES.prompts}`,
|
|
24
|
+
modelsPath: `models/${CONSOLIDATED_FILENAMES.prompts}`
|
|
25
|
+
},
|
|
26
|
+
fileStructure: {
|
|
27
|
+
filename: CONSOLIDATED_FILENAMES.fileStructure,
|
|
28
|
+
exportPath: `${CONSOLIDATED_EXPORT_DIR}/${CONSOLIDATED_FILENAMES.fileStructure}`,
|
|
29
|
+
modelsPath: `models/${CONSOLIDATED_FILENAMES.fileStructure}`
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Write JSON to file-exchange/exports/ (primary) and models/ (API mirror).
|
|
35
|
+
* @param {"models"|"prompts"|"fileStructure"} kind
|
|
36
|
+
* @param {object} doc
|
|
37
|
+
*/
|
|
38
|
+
export async function writeConsolidatedArtifact(kind, doc) {
|
|
39
|
+
const spec = CONSOLIDATED_ARTIFACTS[kind];
|
|
40
|
+
const json = JSON.stringify(doc, null, 2);
|
|
41
|
+
await writeConsolidatedExport(repoRoot, spec.filename, json);
|
|
42
|
+
const modelsAbs = join(repoRoot, spec.modelsPath);
|
|
43
|
+
await mkdir(dirname(modelsAbs), { recursive: true });
|
|
44
|
+
await writeFile(modelsAbs, json);
|
|
45
|
+
return {
|
|
46
|
+
exportPath: spec.exportPath,
|
|
47
|
+
modelsPath: spec.modelsPath
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** @deprecated Use `npm run condense-models` (model condenser API) instead. */
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
|
|
7
|
+
const script = join(dirname(fileURLToPath(import.meta.url)), "condense-models.mjs");
|
|
8
|
+
const child = spawn(process.execPath, [script, ...process.argv.slice(2)], {
|
|
9
|
+
stdio: "inherit"
|
|
10
|
+
});
|
|
11
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Optional pre-push reminder — install:
|
|
3
|
+
# cp scripts/git-hooks/pre-push.sample .git/hooks/pre-push && chmod +x .git/hooks/pre-push
|
|
4
|
+
#
|
|
5
|
+
# Enforce agent log for HEAD (strict):
|
|
6
|
+
# DEVLOG_STRICT=1 npm run dev-log:pre-push -- --check
|
|
7
|
+
|
|
8
|
+
if [ "$DEVLOG_STRICT" = "1" ]; then
|
|
9
|
+
npm run dev-log:pre-push -- --check || exit 1
|
|
10
|
+
else
|
|
11
|
+
echo "pre-push: consider npm run dev-log:pre-push -- --slug <topic> (human + agent audit)"
|
|
12
|
+
echo "pre-push: strict check: DEVLOG_STRICT=1 (set in hook or env)"
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
exit 0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copy an inbound bundle into file-exchange/imports/{UTC-stamp}/
|
|
4
|
+
* Usage: node scripts/import-to-file-exchange.mjs /path/to/bundle [optional-stamp]
|
|
5
|
+
*/
|
|
6
|
+
import { cp, mkdir, access } from "fs/promises";
|
|
7
|
+
import { join, basename, dirname } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { formatExchangeTimestamp } from "../backend/src/shared/utils/formatExchangeTimestamp.js";
|
|
10
|
+
|
|
11
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const source = process.argv[2];
|
|
15
|
+
if (!source) {
|
|
16
|
+
console.error("Usage: node scripts/import-to-file-exchange.mjs <sourceDirOrZipParent> [UTC-stamp]");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const stamp = process.argv[3] || formatExchangeTimestamp();
|
|
21
|
+
const dest = join(repoRoot, "file-exchange/imports", stamp);
|
|
22
|
+
const absSource = join(process.cwd(), source);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await access(absSource);
|
|
26
|
+
} catch {
|
|
27
|
+
console.error("Source not found:", absSource);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await mkdir(dest, { recursive: true });
|
|
32
|
+
const folderName = basename(absSource);
|
|
33
|
+
const target = join(dest, folderName);
|
|
34
|
+
await cp(absSource, target, { recursive: true });
|
|
35
|
+
|
|
36
|
+
console.log(`Imported → file-exchange/imports/${stamp}/${folderName}`);
|
|
37
|
+
console.log(`Use this stamp for ingest scripts or pass paths under imports/${stamp}/`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main().catch((err) => {
|
|
41
|
+
console.error(err);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
const SKIP_MODULES = new Set(["_reference"]);
|
|
5
|
+
const ROUTE_RE = /router\.(get|post|put|patch|delete)\(\s*["'`]([^"'`]+)["'`]/gi;
|
|
6
|
+
const BASE_PATH_RE = /app\.use\(\s*["'`](\/api\/[^"'`]+)["'`]/;
|
|
7
|
+
|
|
8
|
+
function readText(path) {
|
|
9
|
+
return readFileSync(path, "utf8");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function parseRegistryRows(masterText) {
|
|
13
|
+
const start = masterText.indexOf("## Endpoint registry");
|
|
14
|
+
if (start < 0) return [];
|
|
15
|
+
const section = masterText.slice(start);
|
|
16
|
+
const end = section.indexOf("\n## ", 4);
|
|
17
|
+
const body = end >= 0 ? section.slice(0, end) : section;
|
|
18
|
+
const rows = [];
|
|
19
|
+
for (const line of body.split("\n")) {
|
|
20
|
+
if (!line.startsWith("|") || line.includes("---") || line.toLowerCase().includes("method")) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const cols = line
|
|
24
|
+
.split("|")
|
|
25
|
+
.map((c) => c.trim())
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
if (cols.length >= 4) {
|
|
28
|
+
rows.push({
|
|
29
|
+
method: cols[0].toUpperCase(),
|
|
30
|
+
path: cols[1].replace(/^`/, "").replace(/`$/, ""),
|
|
31
|
+
module: cols[2],
|
|
32
|
+
description: cols[3]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return rows;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseModuleIndex(masterText) {
|
|
40
|
+
const start = masterText.indexOf("## Module index");
|
|
41
|
+
if (start < 0) return [];
|
|
42
|
+
const section = masterText.slice(start);
|
|
43
|
+
const end = section.indexOf("\n## ", 4);
|
|
44
|
+
const body = end >= 0 ? section.slice(0, end) : section;
|
|
45
|
+
const rows = [];
|
|
46
|
+
for (const line of body.split("\n")) {
|
|
47
|
+
if (!line.startsWith("|") || line.includes("---") || line.toLowerCase().includes("module")) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const cols = line
|
|
51
|
+
.split("|")
|
|
52
|
+
.map((c) => c.trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
if (cols.length >= 3) {
|
|
55
|
+
rows.push({
|
|
56
|
+
module: cols[0],
|
|
57
|
+
basePath: cols[1].replace(/^`/, "").replace(/`$/, ""),
|
|
58
|
+
status: cols[3] || cols[2]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return rows;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function classifyRoute(row) {
|
|
66
|
+
const desc = row.description.toLowerCase();
|
|
67
|
+
if (desc.includes("deprecated")) return "deprecated";
|
|
68
|
+
if (desc.includes("stub") || desc.includes("health only")) return "stub";
|
|
69
|
+
return "active";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} repoRoot
|
|
74
|
+
*/
|
|
75
|
+
export async function collectApiInventory(repoRoot) {
|
|
76
|
+
const masterApiPath = join(repoRoot, "docs/API.md");
|
|
77
|
+
const masterText = existsSync(masterApiPath) ? readText(masterApiPath) : "";
|
|
78
|
+
const registry = parseRegistryRows(masterText);
|
|
79
|
+
const moduleIndex = parseModuleIndex(masterText);
|
|
80
|
+
|
|
81
|
+
const http = { active: [], stub: [], deprecated: [] };
|
|
82
|
+
for (const row of registry) {
|
|
83
|
+
const bucket = classifyRoute(row);
|
|
84
|
+
http[bucket].push({
|
|
85
|
+
method: row.method,
|
|
86
|
+
path: row.path,
|
|
87
|
+
module: row.module,
|
|
88
|
+
description: row.description
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const promptVersions = {
|
|
93
|
+
defaultEnv: "n/a",
|
|
94
|
+
envVar: "MASTER_PROMPT_VERSION",
|
|
95
|
+
allowed: [],
|
|
96
|
+
specs: {},
|
|
97
|
+
notes: ["Add promptVersions.js in your domain module when you introduce LLM workflows"]
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const pkg = JSON.parse(readText(join(repoRoot, "package.json")));
|
|
101
|
+
const pipelineVersions = {
|
|
102
|
+
app: pkg.version ?? "2.0.0",
|
|
103
|
+
note: "Add pipelineVersions.contract.js in domain modules for batch/runtime versioning"
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const versioned = {
|
|
107
|
+
pipeline: pipelineVersions,
|
|
108
|
+
prompts: promptVersions,
|
|
109
|
+
storage: {},
|
|
110
|
+
app: { packageJson: pkg.version }
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const deprecated = { http: http.deprecated, cli: [], prompts: [], notes: [] };
|
|
114
|
+
const exportDeprecated = join(repoRoot, "scripts/export-consolidated-models.mjs");
|
|
115
|
+
if (existsSync(exportDeprecated)) {
|
|
116
|
+
const t = readText(exportDeprecated);
|
|
117
|
+
if (/@deprecated/i.test(t)) {
|
|
118
|
+
deprecated.cli.push({
|
|
119
|
+
command: "scripts/export-consolidated-models.mjs",
|
|
120
|
+
replacement: "npm run condense-models or POST /api/model-condenser/condense"
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const cli = [
|
|
126
|
+
{ command: "npm run dev-log:pre-push", purpose: "Paired human + agent dev logs" },
|
|
127
|
+
{ command: "npm run condense:all", purpose: "Snapshots → file-exchange/exports/" },
|
|
128
|
+
{ command: "npm run import:file-exchange", purpose: "Inbound → file-exchange/imports/{stamp}/" },
|
|
129
|
+
{ command: "npm run lint:contracts", purpose: "Verify contract manifest paths" },
|
|
130
|
+
{ command: "npm --prefix backend run condense-models", purpose: "Regenerate consolidated-models.json" }
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
capturedAt: new Date().toISOString(),
|
|
135
|
+
sourceDocs: ["docs/API.md", "backend/src/modules/*/routes/"],
|
|
136
|
+
http,
|
|
137
|
+
moduleStatus: moduleIndex.map((m) => ({
|
|
138
|
+
module: m.module,
|
|
139
|
+
basePath: m.basePath,
|
|
140
|
+
status: m.status
|
|
141
|
+
})),
|
|
142
|
+
versioned,
|
|
143
|
+
deprecated,
|
|
144
|
+
cli
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {Awaited<ReturnType<typeof collectApiInventory>>} apis
|
|
150
|
+
*/
|
|
151
|
+
export function formatApisMarkdown(apis) {
|
|
152
|
+
const lines = [
|
|
153
|
+
"### HTTP — active",
|
|
154
|
+
"",
|
|
155
|
+
"| Method | Path | Module | Description |",
|
|
156
|
+
"|--------|------|--------|-------------|"
|
|
157
|
+
];
|
|
158
|
+
for (const r of apis.http.active) {
|
|
159
|
+
lines.push(`| ${r.method} | \`${r.path}\` | ${r.module} | ${r.description} |`);
|
|
160
|
+
}
|
|
161
|
+
lines.push("", "### HTTP — stub (health only)", "");
|
|
162
|
+
if (apis.http.stub.length) {
|
|
163
|
+
lines.push("| Method | Path | Module | Description |", "|--------|------|--------|-------------|");
|
|
164
|
+
for (const r of apis.http.stub) {
|
|
165
|
+
lines.push(`| ${r.method} | \`${r.path}\` | ${r.module} | ${r.description} |`);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
lines.push("_none_");
|
|
169
|
+
}
|
|
170
|
+
lines.push("", "### HTTP — deprecated", "");
|
|
171
|
+
if (apis.http.deprecated.length) {
|
|
172
|
+
lines.push("| Method | Path | Module | Description |", "|--------|------|--------|-------------|");
|
|
173
|
+
for (const r of apis.http.deprecated) {
|
|
174
|
+
lines.push(`| ${r.method} | \`${r.path}\` | ${r.module} | ${r.description} |`);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
lines.push("_none registered in docs/API.md_");
|
|
178
|
+
}
|
|
179
|
+
lines.push("", "### Versioned contracts (platform)", "", "```json");
|
|
180
|
+
lines.push(JSON.stringify(apis.versioned.pipeline, null, 2));
|
|
181
|
+
lines.push("```");
|
|
182
|
+
if (apis.deprecated.cli.length) {
|
|
183
|
+
lines.push("", "### Deprecated CLI", "");
|
|
184
|
+
for (const d of apis.deprecated.cli) {
|
|
185
|
+
lines.push(`- \`${d.command}\` → ${d.replacement}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|