@mafianacho/nacholens 0.0.0-stage → 0.3.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/LICENSE +202 -0
- package/NOTICE +13 -0
- package/README.de.md +46 -0
- package/README.fr.md +46 -0
- package/README.ja.md +46 -0
- package/README.ko.md +46 -0
- package/README.md +45 -2
- package/README.zh.md +46 -0
- package/demo/shop/v1/README.md +7 -0
- package/demo/shop/v1/api.js +78 -0
- package/demo/shop/v1/db.js +38 -0
- package/demo/shop/v1/gateway.js +50 -0
- package/demo/shop/v1/mock-external.js +17 -0
- package/demo/shop/v1/public/app.js +32 -0
- package/demo/shop/v1/public/format.js +2 -0
- package/demo/shop/v1/public/index.html +13 -0
- package/demo/shop/v1/public/styles.css +4 -0
- package/demo/shop/v2/README.md +7 -0
- package/demo/shop/v2/api.js +88 -0
- package/demo/shop/v2/db.js +38 -0
- package/demo/shop/v2/gateway.js +50 -0
- package/demo/shop/v2/mock-external.js +17 -0
- package/demo/shop/v2/public/app.js +33 -0
- package/demo/shop/v2/public/format.js +3 -0
- package/demo/shop/v2/public/index.html +13 -0
- package/demo/shop/v2/public/styles.css +5 -0
- package/docs/guide.ko.md +97 -0
- package/docs/guide.md +142 -0
- package/docs/model-schema.md +137 -0
- package/docs/review.md +78 -0
- package/examples/demo-shop.artifact.html +1535 -0
- package/examples/demo-shop.artifact.json +7 -0
- package/examples/demo-shop.html +1535 -0
- package/examples/external/README.md +12 -0
- package/examples/external/full-stack-fastapi-template/LICENSE +21 -0
- package/examples/external/full-stack-fastapi-template/changes.json +56 -0
- package/examples/external/full-stack-fastapi-template/full-stack-fastapi-template-backend.html +1535 -0
- package/models/demo-shop/changes.auto.json +121 -0
- package/models/demo-shop/changes.example.json +16 -0
- package/models/demo-shop/intent.example.json +75 -0
- package/models/demo-shop/model.json +3151 -0
- package/package.json +60 -4
- package/skills/nacholens/SKILL.md +235 -0
- package/src/cli.js +377 -0
- package/src/demo.js +42 -0
- package/src/diff.js +254 -0
- package/src/gitio.js +67 -0
- package/src/install.js +94 -0
- package/src/model.js +434 -0
- package/src/page.js +87 -0
- package/src/player/fonts/FONTLOG.md +30 -0
- package/src/player/fonts/OFL-Anton.txt +93 -0
- package/src/player/fonts/OFL-IBMPlex.txt +94 -0
- package/src/player/fonts/anton-400.woff2 +0 -0
- package/src/player/fonts/nacho-mono-500.woff2 +0 -0
- package/src/player/fonts/nacho-mono-600.woff2 +0 -0
- package/src/player/fonts/nacho-sans-400.woff2 +0 -0
- package/src/player/fonts/nacho-sans-500.woff2 +0 -0
- package/src/player/fonts/nacho-sans-600.woff2 +0 -0
- package/src/player/fonts/nacho-sans-700.woff2 +0 -0
- package/src/player/player.css +318 -0
- package/src/player/player.js +872 -0
- package/src/player/review.css +49 -0
- package/src/player/review.js +230 -0
- package/src/player/template.html +58 -0
- package/src/review.js +321 -0
- package/src/scan.js +255 -0
- package/src/template.js +36 -0
- package/src/verify.js +90 -0
package/src/verify.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Evidence verification: every cited file:lines must exist in a checkout of the source repo,
|
|
2
|
+
// and (when `contains` is given) the cited line range must really contain that text.
|
|
3
|
+
// This is what keeps the model from drifting into guesses.
|
|
4
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
5
|
+
import { join, resolve, sep } from "node:path";
|
|
6
|
+
|
|
7
|
+
export function collectEvidence(model) {
|
|
8
|
+
const out = [];
|
|
9
|
+
model.components.forEach((c) =>
|
|
10
|
+
c.evidence.forEach((e) => out.push({ where: `component ${c.id}`, e, owner: { type: "component", id: c.id } })),
|
|
11
|
+
);
|
|
12
|
+
model.links.forEach((l) =>
|
|
13
|
+
l.evidence.forEach((e) => out.push({ where: `link ${l.a}-${l.b}`, e, owner: { type: "link", a: l.a, b: l.b } })),
|
|
14
|
+
);
|
|
15
|
+
// optional drill-down detail: parts, wires, tables, endpoints belong to their component (owner "detail")
|
|
16
|
+
model.components.forEach((c) => {
|
|
17
|
+
const d = (kind, id, list, label) => (list ?? []).forEach((e) => out.push({ where: `component ${c.id} ${label}`, e, owner: { type: "detail", comp: c.id, kind, id } }));
|
|
18
|
+
(c.parts ?? []).forEach((p) => d("part", p.id, p.evidence, `part ${p.id}`));
|
|
19
|
+
(c.wires ?? []).forEach((w) => d("wire", `${w.from}>${w.to}`, w.evidence, `wire ${w.from}>${w.to}`));
|
|
20
|
+
(c.tables ?? []).forEach((t) => d("table", t.name, t.evidence, `table ${t.name}`));
|
|
21
|
+
(c.endpoints ?? []).forEach((ep) => d("endpoint", ep.id, ep.evidence, `endpoint ${ep.id}`));
|
|
22
|
+
});
|
|
23
|
+
model.useCases.forEach((u) =>
|
|
24
|
+
u.steps.forEach((s) => {
|
|
25
|
+
s.evidence.forEach((e) => out.push({ where: `${u.id}/${s.id}`, e, owner: { type: "step", uc: u.id, step: s.id } }));
|
|
26
|
+
// table access cited on a step is part of that step (a change to the query changes the step)
|
|
27
|
+
(s.data ?? []).forEach((d) => (d.evidence ?? []).forEach((e) => out.push({ where: `${u.id}/${s.id} data ${d.store}.${d.table}`, e, owner: { type: "step", uc: u.id, step: s.id } })));
|
|
28
|
+
// the data shapes and transforms cited on a step belong to that step too
|
|
29
|
+
(s.payload ?? []).forEach((x, k) => (x.evidence ?? []).forEach((e) => out.push({ where: `${u.id}/${s.id} payload ${k + 1}`, e, owner: { type: "step", uc: u.id, step: s.id } })));
|
|
30
|
+
(s.transforms ?? []).forEach((x) => (x.evidence ?? []).forEach((e) => out.push({ where: `${u.id}/${s.id} transform at ${x.at}`, e, owner: { type: "step", uc: u.id, step: s.id } })));
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Same checks as verifyEvidence, but file content comes from readFile(path) -> string|null
|
|
37
|
+
* (e.g. `git show <commit>:<path>`), so no checkout is needed. */
|
|
38
|
+
export function verifyEvidenceWith(model, readFile) {
|
|
39
|
+
const failures = [];
|
|
40
|
+
const cache = new Map();
|
|
41
|
+
let checked = 0;
|
|
42
|
+
for (const { where, e } of collectEvidence(model)) {
|
|
43
|
+
checked++;
|
|
44
|
+
if (!cache.has(e.file)) {
|
|
45
|
+
const text = readFile(e.file);
|
|
46
|
+
cache.set(e.file, text === null ? null : text.split("\n"));
|
|
47
|
+
}
|
|
48
|
+
const lines = cache.get(e.file);
|
|
49
|
+
if (lines === null) { failures.push(`${where}: 파일 없음 ${e.file}`); continue; }
|
|
50
|
+
const [a, b] = e.lines;
|
|
51
|
+
// a trailing newline yields one empty last element that is not a real line
|
|
52
|
+
const total = lines.length && lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
53
|
+
if (b > total) { failures.push(`${where}: ${e.file}:${a}-${b} 범위 초과 (총 ${total}줄)`); continue; }
|
|
54
|
+
if (e.contains !== undefined && !lines.slice(a - 1, b).join("\n").includes(e.contains))
|
|
55
|
+
failures.push(`${where}: ${e.file}:${a}-${b} 에 "${e.contains}" 없음`);
|
|
56
|
+
}
|
|
57
|
+
return { checked, failures };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function verifyEvidence(model, sourceDir) {
|
|
61
|
+
const root = resolve(sourceDir);
|
|
62
|
+
const failures = [];
|
|
63
|
+
const cache = new Map();
|
|
64
|
+
let checked = 0;
|
|
65
|
+
for (const { where, e } of collectEvidence(model)) {
|
|
66
|
+
checked++;
|
|
67
|
+
const full = resolve(join(root, e.file));
|
|
68
|
+
if (full !== root && !full.startsWith(root + sep)) {
|
|
69
|
+
failures.push(`${where}: ${e.file} 경로가 소스 밖입니다`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!existsSync(full) || !statSync(full).isFile()) {
|
|
73
|
+
failures.push(`${where}: 파일 없음 ${e.file}`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!cache.has(full)) cache.set(full, readFileSync(full, "utf8").split("\n"));
|
|
77
|
+
const lines = cache.get(full);
|
|
78
|
+
const [a, b] = e.lines;
|
|
79
|
+
if (b > lines.length) {
|
|
80
|
+
failures.push(`${where}: ${e.file}:${a}-${b} 범위 초과 (총 ${lines.length}줄)`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (e.contains !== undefined) {
|
|
84
|
+
const slice = lines.slice(a - 1, b).join("\n");
|
|
85
|
+
if (!slice.includes(e.contains))
|
|
86
|
+
failures.push(`${where}: ${e.file}:${a}-${b} 에 "${e.contains}" 없음`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { checked, failures };
|
|
90
|
+
}
|