@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.
Files changed (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +13 -0
  3. package/README.de.md +46 -0
  4. package/README.fr.md +46 -0
  5. package/README.ja.md +46 -0
  6. package/README.ko.md +46 -0
  7. package/README.md +45 -2
  8. package/README.zh.md +46 -0
  9. package/demo/shop/v1/README.md +7 -0
  10. package/demo/shop/v1/api.js +78 -0
  11. package/demo/shop/v1/db.js +38 -0
  12. package/demo/shop/v1/gateway.js +50 -0
  13. package/demo/shop/v1/mock-external.js +17 -0
  14. package/demo/shop/v1/public/app.js +32 -0
  15. package/demo/shop/v1/public/format.js +2 -0
  16. package/demo/shop/v1/public/index.html +13 -0
  17. package/demo/shop/v1/public/styles.css +4 -0
  18. package/demo/shop/v2/README.md +7 -0
  19. package/demo/shop/v2/api.js +88 -0
  20. package/demo/shop/v2/db.js +38 -0
  21. package/demo/shop/v2/gateway.js +50 -0
  22. package/demo/shop/v2/mock-external.js +17 -0
  23. package/demo/shop/v2/public/app.js +33 -0
  24. package/demo/shop/v2/public/format.js +3 -0
  25. package/demo/shop/v2/public/index.html +13 -0
  26. package/demo/shop/v2/public/styles.css +5 -0
  27. package/docs/guide.ko.md +97 -0
  28. package/docs/guide.md +142 -0
  29. package/docs/model-schema.md +137 -0
  30. package/docs/review.md +78 -0
  31. package/examples/demo-shop.artifact.html +1535 -0
  32. package/examples/demo-shop.artifact.json +7 -0
  33. package/examples/demo-shop.html +1535 -0
  34. package/examples/external/README.md +12 -0
  35. package/examples/external/full-stack-fastapi-template/LICENSE +21 -0
  36. package/examples/external/full-stack-fastapi-template/changes.json +56 -0
  37. package/examples/external/full-stack-fastapi-template/full-stack-fastapi-template-backend.html +1535 -0
  38. package/models/demo-shop/changes.auto.json +121 -0
  39. package/models/demo-shop/changes.example.json +16 -0
  40. package/models/demo-shop/intent.example.json +75 -0
  41. package/models/demo-shop/model.json +3151 -0
  42. package/package.json +60 -4
  43. package/skills/nacholens/SKILL.md +235 -0
  44. package/src/cli.js +377 -0
  45. package/src/demo.js +42 -0
  46. package/src/diff.js +254 -0
  47. package/src/gitio.js +67 -0
  48. package/src/install.js +94 -0
  49. package/src/model.js +434 -0
  50. package/src/page.js +87 -0
  51. package/src/player/fonts/FONTLOG.md +30 -0
  52. package/src/player/fonts/OFL-Anton.txt +93 -0
  53. package/src/player/fonts/OFL-IBMPlex.txt +94 -0
  54. package/src/player/fonts/anton-400.woff2 +0 -0
  55. package/src/player/fonts/nacho-mono-500.woff2 +0 -0
  56. package/src/player/fonts/nacho-mono-600.woff2 +0 -0
  57. package/src/player/fonts/nacho-sans-400.woff2 +0 -0
  58. package/src/player/fonts/nacho-sans-500.woff2 +0 -0
  59. package/src/player/fonts/nacho-sans-600.woff2 +0 -0
  60. package/src/player/fonts/nacho-sans-700.woff2 +0 -0
  61. package/src/player/player.css +318 -0
  62. package/src/player/player.js +872 -0
  63. package/src/player/review.css +49 -0
  64. package/src/player/review.js +230 -0
  65. package/src/player/template.html +58 -0
  66. package/src/review.js +321 -0
  67. package/src/scan.js +255 -0
  68. package/src/template.js +36 -0
  69. 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
+ }