@pukujan/create-modular-monolith 2.0.0 → 2.2.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 +94 -23
- 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/.github/workflows/ci.yml +44 -0
- package/template/AGENTS.md +41 -0
- package/template/README.md +25 -55
- package/template/backend/.env.example +38 -0
- package/template/backend/package-lock.json +1118 -24
- package/template/backend/package.json +14 -4
- package/template/backend/scripts/check-module-boundaries.mjs +3 -0
- 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/backend/src/shared/utils/traceId.js +19 -0
- package/template/docs/API.md +42 -0
- package/template/docs/PUBLISHING.md +13 -1
- package/template/docs/README.md +7 -1
- 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 +180 -0
- package/template/docs/architecture/EVAL_AND_CI.md +79 -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 +33 -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 +39 -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/exports/consolidated-models.json +625 -0
- package/template/file-exchange/imports/.gitkeep +0 -0
- package/template/frontend/.env.example +2 -0
- package/template/frontend/package-lock.json +125 -122
- 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 +13 -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 +182 -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/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/scripts/sync-cli-template.mjs +0 -44
- /package/template/{frontend/src/modules → backend/db/migrations}/.gitkeep +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const manifestPath = join(repoRoot, "docs/architecture/contracts/manifest.json");
|
|
8
|
+
|
|
9
|
+
const PATH_KEYS = [
|
|
10
|
+
"file",
|
|
11
|
+
"doc",
|
|
12
|
+
"overview",
|
|
13
|
+
"userDoc",
|
|
14
|
+
"timestampHelper",
|
|
15
|
+
"importScript",
|
|
16
|
+
"utility",
|
|
17
|
+
"schema",
|
|
18
|
+
"generator",
|
|
19
|
+
"humanFormat",
|
|
20
|
+
"verify",
|
|
21
|
+
"apiInventory",
|
|
22
|
+
"repoTree",
|
|
23
|
+
"workLogReadme",
|
|
24
|
+
"registry",
|
|
25
|
+
"lintScript",
|
|
26
|
+
"architectureDoc",
|
|
27
|
+
"promptVersions"
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (!existsSync(manifestPath)) {
|
|
31
|
+
console.error("Missing contracts manifest:", manifestPath);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
36
|
+
const failures = [];
|
|
37
|
+
|
|
38
|
+
for (const [key, entry] of Object.entries(manifest)) {
|
|
39
|
+
const paths = PATH_KEYS.map((k) => entry?.[k]).filter(Boolean);
|
|
40
|
+
if (!paths.length) {
|
|
41
|
+
failures.push(`${key}: no documented paths in manifest`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
for (const rel of paths) {
|
|
45
|
+
const abs = join(repoRoot, rel);
|
|
46
|
+
if (!existsSync(abs)) {
|
|
47
|
+
failures.push(`${key}: path not found — ${rel}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (failures.length) {
|
|
53
|
+
console.error("Contract lint failed:\n" + failures.map((f) => ` - ${f}`).join("\n"));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(`Contract lint OK (${Object.keys(manifest).length} entries)`);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
|
|
8
|
+
const requiredPaths = [
|
|
9
|
+
"docs/architecture/CONTRACTS_OVERVIEW.md",
|
|
10
|
+
"docs/architecture/REPO_ARTIFACT_LAYOUT.md",
|
|
11
|
+
"docs/architecture/contracts/manifest.json",
|
|
12
|
+
"docs/architecture/contracts/changelog.jsonl",
|
|
13
|
+
"docs/architecture/contracts/fileExchange.contract.md",
|
|
14
|
+
"docs/architecture/contracts/consolidatedExports.contract.md",
|
|
15
|
+
"docs/architecture/contracts/prePushDevLog.contract.md",
|
|
16
|
+
"docs/architecture/contracts/apiDocumentationRegistry.contract.md",
|
|
17
|
+
"backend/src/shared/contracts/prePushDevLog.contract.js",
|
|
18
|
+
"backend/src/shared/contracts/consolidatedExports.contract.js",
|
|
19
|
+
"work-log/dev-logs/schemas/dev-log-agent.v1.schema.json",
|
|
20
|
+
"work-log/dev-logs/human",
|
|
21
|
+
"work-log/dev-logs/agent",
|
|
22
|
+
"file-exchange/README.md",
|
|
23
|
+
"file-exchange/imports",
|
|
24
|
+
"file-exchange/exports",
|
|
25
|
+
"docs/API.md",
|
|
26
|
+
"backend/src/modules/_reference/index.js",
|
|
27
|
+
"backend/src/modules/model-condenser/index.js"
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const failures = requiredPaths.filter((rel) => !existsSync(join(repoRoot, rel)));
|
|
31
|
+
|
|
32
|
+
if (failures.length) {
|
|
33
|
+
console.error("Repo artifact lint failed — missing:\n" + failures.map((f) => ` - ${f}`).join("\n"));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`Repo artifact lint OK (${requiredPaths.length} paths)`);
|
|
@@ -4,6 +4,7 @@ import { join } from "path";
|
|
|
4
4
|
import {
|
|
5
5
|
getBackendFiles,
|
|
6
6
|
getFrontendFiles,
|
|
7
|
+
getModuleApiDocContent,
|
|
7
8
|
toTitleCase
|
|
8
9
|
} from "./lib/module-scaffold.mjs";
|
|
9
10
|
|
|
@@ -47,11 +48,17 @@ function writeTree(baseDir, files) {
|
|
|
47
48
|
writeTree(backendDir, getBackendFiles(name));
|
|
48
49
|
writeTree(frontendDir, getFrontendFiles(name, label));
|
|
49
50
|
|
|
51
|
+
const docsDir = join(root, "docs", name);
|
|
52
|
+
mkdirSync(docsDir, { recursive: true });
|
|
53
|
+
writeFileSync(join(docsDir, "API.md"), getModuleApiDocContent(name, label), "utf8");
|
|
54
|
+
|
|
50
55
|
console.log(`Created module: ${name}`);
|
|
51
56
|
console.log(` backend/src/modules/${name}/`);
|
|
52
57
|
console.log(` frontend/src/modules/${name}/`);
|
|
58
|
+
console.log(` docs/${name}/API.md`);
|
|
53
59
|
console.log("");
|
|
54
60
|
console.log("Next:");
|
|
61
|
+
console.log(` Add rows to docs/API.md (Module index + Endpoint registry) for /api/${name}`);
|
|
55
62
|
console.log(" npm run lint:architecture");
|
|
56
63
|
console.log(` npm test`);
|
|
57
64
|
console.log(` npm run test:evals -- ${name}`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { access } from "fs/promises";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { normalizeExchangeStamp } from "../backend/src/shared/utils/formatExchangeTimestamp.js";
|
|
5
|
+
|
|
6
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve imports/{stamp}/ — accepts human or legacy compact stamps.
|
|
10
|
+
* @param {string} [stamp]
|
|
11
|
+
* @returns {Promise<string>} resolved stamp folder name under imports/
|
|
12
|
+
*/
|
|
13
|
+
export async function resolveImportStamp(stamp) {
|
|
14
|
+
const candidates = [];
|
|
15
|
+
if (stamp) {
|
|
16
|
+
candidates.push(stamp, normalizeExchangeStamp(stamp));
|
|
17
|
+
}
|
|
18
|
+
const importsRoot = join(repoRoot, "file-exchange/imports");
|
|
19
|
+
const { readdir } = await import("fs/promises");
|
|
20
|
+
let dirs = [];
|
|
21
|
+
try {
|
|
22
|
+
dirs = await readdir(importsRoot);
|
|
23
|
+
} catch {
|
|
24
|
+
dirs = [];
|
|
25
|
+
}
|
|
26
|
+
if (!stamp && dirs.length) {
|
|
27
|
+
dirs.sort();
|
|
28
|
+
candidates.push(dirs[dirs.length - 1]);
|
|
29
|
+
}
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
for (const c of candidates) {
|
|
32
|
+
if (!c || seen.has(c)) continue;
|
|
33
|
+
seen.add(c);
|
|
34
|
+
try {
|
|
35
|
+
await access(join(importsRoot, c));
|
|
36
|
+
return c;
|
|
37
|
+
} catch {
|
|
38
|
+
/* try next */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error(
|
|
42
|
+
stamp
|
|
43
|
+
? `No import folder for stamp: ${stamp}`
|
|
44
|
+
: "No file-exchange/imports/{stamp}/ folder found"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function importDirForStamp(stamp) {
|
|
49
|
+
return join(repoRoot, "file-exchange/imports", stamp);
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, readdir } from "fs/promises";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const agentDir = join(repoRoot, "work-log/dev-logs/agent");
|
|
8
|
+
|
|
9
|
+
const files = (await readdir(agentDir)).filter((f) => f.endsWith(".json")).sort();
|
|
10
|
+
const latest = files[files.length - 1];
|
|
11
|
+
const agentPath = join(agentDir, latest);
|
|
12
|
+
const humanName = latest.replace("_dev-log-agent_", "_dev-log_").replace(/\.json$/, ".md");
|
|
13
|
+
const humanPath = join(repoRoot, "work-log/dev-logs/human", humanName);
|
|
14
|
+
|
|
15
|
+
const agent = JSON.parse(await readFile(agentPath, "utf8"));
|
|
16
|
+
const human = await readFile(humanPath, "utf8");
|
|
17
|
+
|
|
18
|
+
const required = ["meta", "summary", "apis", "git", "tests", "repositoryTree", "changes", "decisions"];
|
|
19
|
+
const missing = required.filter((k) => !(k in agent));
|
|
20
|
+
|
|
21
|
+
console.log("Latest pair:");
|
|
22
|
+
console.log(" agent:", agentPath.replace(repoRoot + "/", ""));
|
|
23
|
+
console.log(" human:", humanPath.replace(repoRoot + "/", ""));
|
|
24
|
+
|
|
25
|
+
let failed = 0;
|
|
26
|
+
function assert(name, ok) {
|
|
27
|
+
console.log(ok ? " PASS" : " FAIL", name);
|
|
28
|
+
if (!ok) failed += 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
assert("agent JSON has required keys", missing.length === 0);
|
|
32
|
+
assert("apis.http.active > 0", agent.apis.http.active.length > 0);
|
|
33
|
+
assert("repositoryTree.treeText present", agent.repositoryTree.treeText.length > 100);
|
|
34
|
+
assert("treeIgnoreFlag set", agent.repositoryTree.treeIgnoreFlag?.includes("node_modules"));
|
|
35
|
+
assert("tests ran", agent.tests.ran === true);
|
|
36
|
+
|
|
37
|
+
assert("human: TOC", human.includes("## Table of contents"));
|
|
38
|
+
assert("human: Part I", human.includes("Part I — Summary"));
|
|
39
|
+
assert("human: Part II", human.includes("Part II — Detailed"));
|
|
40
|
+
assert("human: mermaid blocks", (human.match(/```mermaid/g) || []).length >= 3);
|
|
41
|
+
assert("human: I.3 API summary", human.includes("I.3 API surface"));
|
|
42
|
+
assert("human: I.5 test audit", human.includes("I.5 Test audit"));
|
|
43
|
+
assert("human: II.10 full tree", human.includes("II.10 Repository tree"));
|
|
44
|
+
|
|
45
|
+
const treeSection = human.split("II.10 Repository tree")[1] || "";
|
|
46
|
+
assert("tree excludes node_modules dir", !treeSection.includes("node_modules/"));
|
|
47
|
+
|
|
48
|
+
console.log("\nTests:", agent.tests.summary);
|
|
49
|
+
console.log(failed === 0 ? "\nAll checks passed." : `\n${failed} check(s) failed.`);
|
|
50
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Create paired pre-push dev logs: human (markdown) + agent (JSON audit).
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npm run dev-log:pre-push -- --slug consolidated-exports
|
|
7
|
+
* npm run dev-log:pre-push -- --slug my-topic --program 005 --no-tests
|
|
8
|
+
* npm run dev-log:pre-push -- --check # verify agent log exists for HEAD
|
|
9
|
+
*/
|
|
10
|
+
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
|
|
11
|
+
import { join, dirname } from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { formatWorkLogTimestamp } from "../backend/src/shared/utils/formatExchangeTimestamp.js";
|
|
14
|
+
import { buildRepoTree, TREE_IGNORE_DIRS } from "./lib/repo-tree.mjs";
|
|
15
|
+
import { collectGitSnapshot } from "./lib/git-snapshot.mjs";
|
|
16
|
+
import { runTestSuite } from "./lib/run-tests.mjs";
|
|
17
|
+
import { collectApiInventory, formatApisMarkdown } from "./lib/api-inventory.mjs";
|
|
18
|
+
import { buildHumanDevLog } from "./lib/dev-log-human-format.mjs";
|
|
19
|
+
|
|
20
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
21
|
+
const humanDir = join(repoRoot, "work-log/dev-logs/human");
|
|
22
|
+
const agentDir = join(repoRoot, "work-log/dev-logs/agent");
|
|
23
|
+
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const out = { slug: "", program: "005", noTests: false, check: false, title: "" };
|
|
26
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
27
|
+
const a = argv[i];
|
|
28
|
+
if (a === "--slug" && argv[i + 1]) out.slug = argv[++i];
|
|
29
|
+
else if (a === "--program" && argv[i + 1]) out.program = argv[++i];
|
|
30
|
+
else if (a === "--title" && argv[i + 1]) out.title = argv[++i];
|
|
31
|
+
else if (a === "--no-tests") out.noTests = true;
|
|
32
|
+
else if (a === "--check") out.check = true;
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function slugify(s) {
|
|
38
|
+
return String(s)
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
41
|
+
.replace(/^-|-$/g, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function classifyChangedFiles(changedFiles) {
|
|
45
|
+
const byArea = {};
|
|
46
|
+
const added = [];
|
|
47
|
+
const modified = [];
|
|
48
|
+
const deleted = [];
|
|
49
|
+
|
|
50
|
+
for (const { code, path } of changedFiles) {
|
|
51
|
+
const area = path.split("/")[0] || "root";
|
|
52
|
+
if (!byArea[area]) byArea[area] = [];
|
|
53
|
+
byArea[area].push(path);
|
|
54
|
+
if (code.includes("A") || code === "??") added.push(path);
|
|
55
|
+
else if (code.includes("D")) deleted.push(path);
|
|
56
|
+
else modified.push(path);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { byArea, added, modified, deleted };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function findAgentLogForSha(sha) {
|
|
63
|
+
let files;
|
|
64
|
+
try {
|
|
65
|
+
files = await readdir(agentDir);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
for (const f of files.filter((x) => x.endsWith(".json")).sort().reverse()) {
|
|
70
|
+
const raw = await readFile(join(agentDir, f), "utf8");
|
|
71
|
+
const doc = JSON.parse(raw);
|
|
72
|
+
if (doc.git?.sha === sha) return join(agentDir, f);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main() {
|
|
78
|
+
const args = parseArgs(process.argv.slice(2));
|
|
79
|
+
const git = await collectGitSnapshot(repoRoot);
|
|
80
|
+
|
|
81
|
+
if (args.check) {
|
|
82
|
+
const found = await findAgentLogForSha(git.sha);
|
|
83
|
+
if (!found) {
|
|
84
|
+
console.error(`No agent dev-log for HEAD (${git.shortSha}). Run: npm run dev-log:pre-push -- --slug <topic>`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
console.log(`OK: agent dev-log matches HEAD → ${found.replace(repoRoot + "/", "")}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!args.slug) {
|
|
92
|
+
console.error("Usage: npm run dev-log:pre-push -- --slug <kebab-topic> [--program 005] [--no-tests]");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { date, time, folder: stamp } = formatWorkLogTimestamp();
|
|
97
|
+
const entryId = String(args.program).padStart(3, "0");
|
|
98
|
+
const slug = slugify(args.slug);
|
|
99
|
+
const base = `${entryId}_${date}_${time}`;
|
|
100
|
+
const humanFilename = `${base}_dev-log_${slug}.md`;
|
|
101
|
+
const agentFilename = `${base}_dev-log-agent_${slug}.json`;
|
|
102
|
+
const humanPath = join(humanDir, humanFilename);
|
|
103
|
+
const agentPath = join(agentDir, agentFilename);
|
|
104
|
+
const humanRel = `work-log/dev-logs/human/${humanFilename}`;
|
|
105
|
+
const agentRel = `work-log/dev-logs/agent/${agentFilename}`;
|
|
106
|
+
|
|
107
|
+
console.log("Building repository tree…");
|
|
108
|
+
const tree = await buildRepoTree(repoRoot);
|
|
109
|
+
|
|
110
|
+
console.log("Collecting API inventory…");
|
|
111
|
+
const apis = await collectApiInventory(repoRoot);
|
|
112
|
+
const apisMarkdown = formatApisMarkdown(apis);
|
|
113
|
+
const treeIgnoreList = TREE_IGNORE_DIRS.filter((d) => d !== ".DS_Store").join("`, `");
|
|
114
|
+
|
|
115
|
+
console.log(args.noTests ? "Skipping tests." : "Running npm test…");
|
|
116
|
+
const tests = await runTestSuite(repoRoot, { run: !args.noTests });
|
|
117
|
+
const changes = classifyChangedFiles(git.changedFiles);
|
|
118
|
+
|
|
119
|
+
const title = args.title || slug.replace(/-/g, " ");
|
|
120
|
+
const apisDetailedMarkdown = apisMarkdown;
|
|
121
|
+
|
|
122
|
+
const humanBody = buildHumanDevLog({
|
|
123
|
+
title,
|
|
124
|
+
entryId,
|
|
125
|
+
date,
|
|
126
|
+
time,
|
|
127
|
+
humanFilename,
|
|
128
|
+
agentFilename,
|
|
129
|
+
git,
|
|
130
|
+
tests,
|
|
131
|
+
apis,
|
|
132
|
+
apisDetailedMarkdown,
|
|
133
|
+
tree,
|
|
134
|
+
treeIgnoreList
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const agentDoc = {
|
|
138
|
+
meta: {
|
|
139
|
+
schemaVersion: "1.0.0",
|
|
140
|
+
entryId,
|
|
141
|
+
slug,
|
|
142
|
+
generatedAt: new Date().toISOString(),
|
|
143
|
+
humanLogPath: humanRel,
|
|
144
|
+
audience: "agent",
|
|
145
|
+
filledBy: "script",
|
|
146
|
+
handoffRefs: []
|
|
147
|
+
},
|
|
148
|
+
summary:
|
|
149
|
+
"FILL: One-paragraph audit summary for the next agent. What shipped, current state, blockers.",
|
|
150
|
+
apis,
|
|
151
|
+
git: {
|
|
152
|
+
branch: git.branch,
|
|
153
|
+
sha: git.sha,
|
|
154
|
+
shortSha: git.shortSha,
|
|
155
|
+
changedFiles: git.changedFiles,
|
|
156
|
+
diffStatAgainstHead: git.diffStatAgainstHead,
|
|
157
|
+
diffCachedStat: git.diffCachedStat,
|
|
158
|
+
recentCommits: git.recentCommits
|
|
159
|
+
},
|
|
160
|
+
tests: {
|
|
161
|
+
ran: tests.ran,
|
|
162
|
+
exitCode: tests.exitCode,
|
|
163
|
+
summary: tests.summary,
|
|
164
|
+
passed: tests.passed,
|
|
165
|
+
failed: tests.failed,
|
|
166
|
+
commands: tests.ran ? ["npm test"] : []
|
|
167
|
+
},
|
|
168
|
+
repositoryTree: {
|
|
169
|
+
capturedAt: new Date().toISOString(),
|
|
170
|
+
excludeDirs: TREE_IGNORE_DIRS,
|
|
171
|
+
treeIgnoreFlag: 'tree -I "node_modules|.git|dist|build"',
|
|
172
|
+
stats: tree.stats,
|
|
173
|
+
treeText: tree.treeText,
|
|
174
|
+
flatPathCount: tree.flatPaths.length
|
|
175
|
+
},
|
|
176
|
+
changes: {
|
|
177
|
+
...changes,
|
|
178
|
+
narrative: ["FILL: bullet list of intentional behavioral / API changes"]
|
|
179
|
+
},
|
|
180
|
+
decisions: [
|
|
181
|
+
{
|
|
182
|
+
id: "D1",
|
|
183
|
+
decision: "FILL",
|
|
184
|
+
rationale: "FILL",
|
|
185
|
+
alternativesRejected: ["FILL"],
|
|
186
|
+
tradeoff: "FILL"
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
iterations: [
|
|
190
|
+
{
|
|
191
|
+
attempt: 1,
|
|
192
|
+
action: "FILL: what was tried",
|
|
193
|
+
outcome: "FILL: pass/fail/deferred",
|
|
194
|
+
blockedBy: ""
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
tradeoffs: ["FILL"],
|
|
198
|
+
improvements: ["FILL"],
|
|
199
|
+
regressions: ["FILL"],
|
|
200
|
+
risks: ["FILL"],
|
|
201
|
+
followUps: ["FILL"]
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
await mkdir(humanDir, { recursive: true });
|
|
205
|
+
await mkdir(agentDir, { recursive: true });
|
|
206
|
+
await writeFile(humanPath, humanBody);
|
|
207
|
+
await writeFile(agentPath, JSON.stringify(agentDoc, null, 2));
|
|
208
|
+
|
|
209
|
+
console.log("\nPre-push dev logs created:");
|
|
210
|
+
console.log(` Human: ${humanRel}`);
|
|
211
|
+
console.log(` Agent: ${agentRel}`);
|
|
212
|
+
console.log(` Git: ${git.branch} @ ${git.shortSha}`);
|
|
213
|
+
console.log(` Tests: ${tests.summary}`);
|
|
214
|
+
console.log("\nNext: fill FILL sections in the agent JSON and narrative sections in the human MD, then add rows to work-log/INDEX.md");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
main().catch((err) => {
|
|
218
|
+
console.error(err);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Work log
|
|
2
|
+
|
|
3
|
+
Planning artifacts for this repo: **what to build** (handoffs) and **how we decided** (study docs).
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
work-log/
|
|
7
|
+
README.md ← you are here
|
|
8
|
+
INDEX.md ← full index (handoffs + study-docs + dev-logs)
|
|
9
|
+
handoffs/ ← numbered specs, starter packs (002, 005, …)
|
|
10
|
+
study-docs/ ← study logs, planning notes, blog drafts
|
|
11
|
+
dev-logs/ ← pre-push audit: human/ + agent/ (paired per push)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## When to use which folder
|
|
15
|
+
|
|
16
|
+
| Folder | Put here |
|
|
17
|
+
|--------|----------|
|
|
18
|
+
| **handoffs/** | Implementation specs, second/third handoffs, starter pack snapshots |
|
|
19
|
+
| **study-docs/** | Conversation study logs, design rationale, portfolio / recruiter logs |
|
|
20
|
+
| **dev-logs/** | What shipped — **paired human MD + agent JSON** before each push |
|
|
21
|
+
|
|
22
|
+
## Filename convention (all three folders)
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
{NNN}_{YYYY-MM-DD}_{HH-MM}_{kind}_{short-slug}.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Folder | `kind` value | Example |
|
|
29
|
+
|--------|----------------|---------|
|
|
30
|
+
| handoffs/ | `handoff`, `handoff-v2`, `handoff-original`, … | `005_2026-05-23_10-49_handoff-original_…` |
|
|
31
|
+
| study-docs/ | `study-log` | `006_2026-05-23_11-21_study-log_cursor-planning-phase` |
|
|
32
|
+
| dev-logs/ | `dev-log` (fixed) | `001_2026-05-24_14-30_dev-log_work-log-reorg` |
|
|
33
|
+
|
|
34
|
+
Details: [handoffs/README.md](./handoffs/README.md) · [study-docs/README.md](./study-docs/README.md) · [dev-logs/README.md](./dev-logs/README.md)
|
|
35
|
+
|
|
36
|
+
## 005 program order
|
|
37
|
+
|
|
38
|
+
1. Original spec → [handoffs/005_…_handoff-original_…](./handoffs/005_2026-05-23_10-49_handoff-original_parsed-cache-rule-authority.md)
|
|
39
|
+
2. v3 architecture → [handoffs/005_…_handoff-v3_…](./handoffs/005_2026-05-23_11-20_handoff-v3_filing-structure-architecture.md)
|
|
40
|
+
3. v2 pipeline → [handoffs/005_…_handoff-v2_…](./handoffs/005_2026-05-23_11-14_handoff-v2_planned-review-in-cursor.md)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Dev logs
|
|
2
|
+
|
|
3
|
+
Session **what we shipped** notes — written **before each push** as a paired human + agent audit.
|
|
4
|
+
|
|
5
|
+
## Two types (required before push)
|
|
6
|
+
|
|
7
|
+
| Type | Folder | Format | Audience |
|
|
8
|
+
|------|--------|--------|----------|
|
|
9
|
+
| **Human** | `human/` | Markdown (narrative, tables) | You, reviewers, GitHub |
|
|
10
|
+
| **Agent audit** | `agent/` | JSON (`dev-log-agent.v1`) | Cursor / automation — fast structured read |
|
|
11
|
+
|
|
12
|
+
Same timestamp prefix on both files so they stay paired:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
005_2026-05-23_17-30_dev-log_consolidated-exports.md ← human/
|
|
16
|
+
005_2026-05-23_17-30_dev-log-agent_consolidated-exports.json ← agent/
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Pre-push workflow
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Generate pair (git + npm test + full repo tree auto-filled)
|
|
23
|
+
npm run dev-log:pre-push -- --slug <kebab-topic> --program 005
|
|
24
|
+
|
|
25
|
+
# 2. Fill FILL sections in agent JSON, then human markdown
|
|
26
|
+
|
|
27
|
+
# 3. Optional: block push if no agent log for HEAD
|
|
28
|
+
npm run dev-log:pre-push -- --check
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Cursor: use command **Pre-push dev log** (`.cursor/commands/pre-push-dev-log.md`).
|
|
32
|
+
|
|
33
|
+
### Human log (two-part markdown)
|
|
34
|
+
|
|
35
|
+
Each `human/*.md` file has:
|
|
36
|
+
|
|
37
|
+
**Part I — Summary** (read first): table of contents, mermaid diagrams (HTTP modules, pipeline versions, pre-push flow), audit tables for APIs, prompt/version contracts, tests, git, condensed repo tree.
|
|
38
|
+
|
|
39
|
+
**Part II — Detailed**: goals, decisions, changes, iterations, full API registry, full git snapshot, full repository tree.
|
|
40
|
+
|
|
41
|
+
Generator: `scripts/lib/dev-log-human-format.mjs`
|
|
42
|
+
|
|
43
|
+
### Agent log schema
|
|
44
|
+
|
|
45
|
+
`schemas/dev-log-agent.v1.schema.json` — machine-readable:
|
|
46
|
+
|
|
47
|
+
- `meta`, `summary`, `apis` (HTTP active/stub/deprecated, versioned contracts, CLI), `git`, `tests`, `repositoryTree`
|
|
48
|
+
- `changes`, `decisions[]`, `iterations[]`
|
|
49
|
+
- `tradeoffs`, `improvements`, `regressions`, `risks`, `followUps`
|
|
50
|
+
|
|
51
|
+
Agents should read the **JSON** first when resuming work; humans read **markdown**.
|
|
52
|
+
|
|
53
|
+
## vs other work-log folders
|
|
54
|
+
|
|
55
|
+
| Folder | Audience | Content |
|
|
56
|
+
|--------|----------|---------|
|
|
57
|
+
| **handoffs/** | Implementers | Spec — what to build |
|
|
58
|
+
| **study-docs/** | You + recruiters | Why — planning conversation |
|
|
59
|
+
| **dev-logs/** | You + agents + reviewers | What landed — audit per push |
|
|
60
|
+
|
|
61
|
+
## vs `docs/DEVLOG_V2.md`
|
|
62
|
+
|
|
63
|
+
| Location | Use |
|
|
64
|
+
|----------|-----|
|
|
65
|
+
| [docs/DEVLOG_V2.md](../../docs/DEVLOG_V2.md) | Long-lived architecture narrative |
|
|
66
|
+
| **dev-logs/** | Incremental per-push audit entries |
|
|
67
|
+
|
|
68
|
+
## Legacy entries
|
|
69
|
+
|
|
70
|
+
Older single-file logs at `dev-logs/*.md` (repo root of this folder) predate the human/agent split. New entries go only under `human/` and `agent/`.
|
|
71
|
+
|
|
72
|
+
## Filename convention
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
{NNN}_{YYYY-MM-DD}_{HH-MM}_dev-log_{slug}.md → human/
|
|
76
|
+
{NNN}_{YYYY-MM-DD}_{HH-MM}_dev-log-agent_{slug}.json → agent/
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Part | Meaning |
|
|
80
|
+
|------|---------|
|
|
81
|
+
| `NNN` | Program id (`005`) or global sequence (`001`) |
|
|
82
|
+
| `YYYY-MM-DD` | Session date |
|
|
83
|
+
| `HH-MM` | Finish time (24h) |
|
|
84
|
+
| `slug` | Kebab-case topic |
|
|
85
|
+
|
|
86
|
+
## Git hook (optional)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cp scripts/git-hooks/pre-push.sample .git/hooks/pre-push
|
|
90
|
+
chmod +x .git/hooks/pre-push
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Reminds you to run `dev-log:pre-push`; use `--check` to enforce agent log for current `HEAD`.
|
|
94
|
+
|
|
95
|
+
## GitHub
|
|
96
|
+
|
|
97
|
+
Track in git. No secrets, credentials, or real filing text. Update [../INDEX.md](../INDEX.md) after each pair.
|