Stage 1: project foundation (strict TS, lint boundaries B-1..B-7, directory structure, CI, boundary tests)
- dedicated git repo at /home/avi/Projects/Lumen (main)
- TypeScript strict (target ES2022, bundler, exactOptionalPropertyTypes, noUncheckedIndexedAccess)
- ESLint 9 + typescript-eslint strictTypeChecked + eslint-plugin-boundaries for B-1..B-7, no-restricted-globals/syntax for B-1/B-7
- Prettier 3.5
- Structure per IMPLEMENTATION-CONTRACT.md §4 (src/platform/idb|cache|sw, storage, data, sync/{transport,verifier}, domain/{emergency,schedule,map,festival,readiness,clock,favorites}, ui/{components,views,router,render}, app, emergency-baseline, assets, public, content, pipeline, tests, scripts)
- CI: .github/workflows/ci.yml (typecheck + lint + format + test)
- Boundary tests: tests/unit/boundaries.test.ts (4 tests) + scripts/check-boundaries.ts
- No feature code, no PWA/IDB/sync/mesh/accounts per contract Stage 1
This commit is contained in:
commit
c0bfd413ff
100 changed files with 9863 additions and 0 deletions
98
scripts/check-boundaries.ts
Normal file
98
scripts/check-boundaries.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Stage 1 — architectural boundary gate.
|
||||
* Minimal check that no source file violates B-1 … B-7 by string scanning
|
||||
* (full boundary enforcement is via eslint-plugin-boundaries in `npm run lint`).
|
||||
* This script is a second cheap gate for CI and for `tests/unit/boundaries.test.ts`.
|
||||
*
|
||||
* Rules checked (subset where string grep is sufficient for Stage 1):
|
||||
* B-1 ui must not import indexedDB/caches/fetch/localStorage
|
||||
* B-7 no innerHTML/insertAdjacentHTML in src
|
||||
*/
|
||||
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const SRC = "src";
|
||||
|
||||
const violations: string[] = [];
|
||||
|
||||
function walk(dir: string, cb: (file: string) => void): void {
|
||||
if (!existsSync(dir)) return;
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(p, cb);
|
||||
else if (entry.isFile() && (p.endsWith(".ts") || p.endsWith(".js"))) cb(p);
|
||||
}
|
||||
}
|
||||
|
||||
const B1_FORBIDDEN_IN_UI = [
|
||||
/from\s+["'].*platform\//,
|
||||
/from\s+["'].*storage\//,
|
||||
/from\s+["'].*sync\//,
|
||||
/indexedDB/,
|
||||
/\bcaches\b/,
|
||||
/\blocalStorage\b/,
|
||||
];
|
||||
|
||||
walk(SRC, (file) => {
|
||||
const content = readFileSync(file, "utf8");
|
||||
const isUI = file.startsWith("src/ui/");
|
||||
const isDomain = file.startsWith("src/domain/");
|
||||
const isSyncTransport = file.startsWith("src/sync/transport/");
|
||||
const isPlatform = file.startsWith("src/platform/");
|
||||
|
||||
if (isUI) {
|
||||
for (const rx of B1_FORBIDDEN_IN_UI) {
|
||||
if (rx.test(content)) {
|
||||
// allow comments that mention the pattern; require actual import usage
|
||||
// simple heuristic: check import line
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line: string = lines[i] ?? "";
|
||||
if (rx.test(line) && /import|require/.test(line)) {
|
||||
violations.push(
|
||||
`${file}:${String(i + 1)}: B-1 violation — ui must not touch platform/storage/sync or raw indexedDB/caches/localStorage — line: ${line.trim()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B-7 innerHTML
|
||||
if (file.includes("src/")) {
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line: string = lines[i] ?? "";
|
||||
if (/\.innerHTML\s*=/.test(line) || line.includes("insertAdjacentHTML")) {
|
||||
violations.push(
|
||||
`${file}:${String(i + 1)}: B-7 violation — do not use innerHTML/insertAdjacentHTML with festival content (C-22).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// B-2 domain must not import platform/sync
|
||||
if (isDomain && /from\s+["'].*platform\//.test(content)) {
|
||||
violations.push(`${file}: B-2 violation — domain must not import platform.`);
|
||||
}
|
||||
|
||||
// sync-transport must not import data/domain
|
||||
if (isSyncTransport && /from\s+["'].*(data|domain)\//.test(content)) {
|
||||
violations.push(`${file}: B-3 violation — transport must not import data/domain.`);
|
||||
}
|
||||
|
||||
// platform must not import domain/ui/sync
|
||||
if (isPlatform && /from\s+["'].*(domain|ui|sync)\//.test(content)) {
|
||||
violations.push(`${file}: platform must not import domain/ui/sync.`);
|
||||
}
|
||||
});
|
||||
|
||||
if (violations.length) {
|
||||
console.error("Boundary violations found:");
|
||||
for (const v of violations) console.error(" " + v);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.warn(
|
||||
"Boundary check OK — no B-1…B-7 string violations in src/ (full check via eslint boundaries).",
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue