@mrciphersmith/keryx 0.2.83 → 0.2.84

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 (47) hide show
  1. package/dist/cli.js +28463 -19413
  2. package/dist/core.js +25751 -0
  3. package/package.json +15 -2
  4. package/src/gdgraph/dangling.ts +204 -0
  5. package/src/gdgraph/find.ts +529 -37
  6. package/src/gdgraph/repomap.ts +140 -12
  7. package/src/gdgraph/staleness.ts +22 -9
  8. package/src/gdgraph/symbol.ts +45 -6
  9. package/src/gdgraph/treesitter/extract.ts +153 -5
  10. package/src/gdgraph/wiki-layer.ts +32 -1
  11. package/src/gdskills/bundled/skills/core/reviewer-skill-creator/SKILL.md +29 -0
  12. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +1 -1
  13. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +1 -1
  14. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +1 -1
  15. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +1 -1
  16. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +1 -1
  17. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +34 -6
  18. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1 -1
  19. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1 -1
  20. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
  21. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1 -1
  22. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1 -1
  23. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +52 -5
  24. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +52 -5
  25. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +52 -5
  26. package/src/gdgraph/affected.test.ts +0 -133
  27. package/src/gdgraph/build-integrity.test.ts +0 -193
  28. package/src/gdgraph/build-lang.test.ts +0 -406
  29. package/src/gdgraph/build.test.ts +0 -120
  30. package/src/gdgraph/config.test.ts +0 -47
  31. package/src/gdgraph/core-sources.test.ts +0 -99
  32. package/src/gdgraph/fallback.test.ts +0 -153
  33. package/src/gdgraph/find.test.ts +0 -78
  34. package/src/gdgraph/import-kind.test.ts +0 -525
  35. package/src/gdgraph/path.test.ts +0 -56
  36. package/src/gdgraph/repomap.test.ts +0 -110
  37. package/src/gdgraph/service.test.ts +0 -89
  38. package/src/gdgraph/staleness.test.ts +0 -208
  39. package/src/gdgraph/symbol.test.ts +0 -89
  40. package/src/gdgraph/symbols-capability.test.ts +0 -138
  41. package/src/gdgraph/treesitter/adapter.test.ts +0 -496
  42. package/src/gdgraph/treesitter/extract.test.ts +0 -278
  43. package/src/gdgraph/treesitter/no-treesitter-import.test.ts +0 -51
  44. package/src/gdgraph/treesitter/real-grammar-fixture.test.ts +0 -71
  45. package/src/gdgraph/treesitter/resolve-calls.test.ts +0 -38
  46. package/src/gdgraph/wiki-layer-no-git.test.ts +0 -73
  47. package/src/gdgraph/wiki-layer.test.ts +0 -211
@@ -1,278 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { expect, test } from "bun:test";
5
- import { extractSymbolLayer, type TsNode } from "./extract";
6
- import type { CallEdge, SymbolNode } from "../types";
7
-
8
- const FIXTURE_DIR = fileURLToPath(new URL("../../../fixtures/symbol-graph/", import.meta.url));
9
-
10
- // --- Minimal mock syntax-tree builder (a structural stand-in for a parsed
11
- // `web-tree-sitter` tree; the extractor is dependency-free). ---
12
- interface MockOptions {
13
- type: string;
14
- line?: number;
15
- endLine?: number;
16
- text?: string;
17
- fields?: Record<string, TsNode | null>;
18
- namedChildren?: TsNode[];
19
- }
20
-
21
- function mk(o: MockOptions): TsNode {
22
- const line = o.line ?? 1;
23
- const endLine = o.endLine ?? line;
24
- const named = o.namedChildren ?? [];
25
- return {
26
- type: o.type,
27
- text: o.text ?? "",
28
- startPosition: { row: line - 1, column: 0 },
29
- endPosition: { row: endLine - 1, column: 0 },
30
- childForFieldName: (field: string) => o.fields?.[field] ?? null,
31
- namedChildren: named,
32
- children: named,
33
- };
34
- }
35
-
36
- const idNode = (text: string): TsNode => mk({ type: "identifier", text });
37
- const paramsNode = (): TsNode => mk({ type: "formal_parameters", text: "()" });
38
- const callTo = (name: string, line: number): TsNode =>
39
- mk({ type: "call_expression", line, fields: { function: idNode(name) }, text: `${name}()` });
40
-
41
- // Build the tree modeling `fixtures/symbol-graph/source.ts`.
42
- export function buildWidgetTree(): TsNode {
43
- const alphaBody = mk({ type: "statement_block", line: 1, namedChildren: [callTo("helper", 1)] });
44
- const funcAlpha = mk({
45
- type: "function_declaration",
46
- line: 1,
47
- endLine: 1,
48
- fields: { name: idNode("alpha"), parameters: paramsNode() },
49
- namedChildren: [alphaBody],
50
- });
51
- const funcHelper = mk({
52
- type: "function_declaration",
53
- line: 2,
54
- endLine: 2,
55
- fields: { name: idNode("helper"), parameters: paramsNode() },
56
- });
57
- const renderBody = mk({ type: "statement_block", line: 4, namedChildren: [callTo("alpha", 4)] });
58
- const methodRender = mk({
59
- type: "method_definition",
60
- line: 4,
61
- endLine: 4,
62
- fields: { name: idNode("render"), parameters: paramsNode() },
63
- namedChildren: [renderBody],
64
- });
65
- const classBody = mk({ type: "class_body", line: 3, endLine: 5, namedChildren: [methodRender] });
66
- const classWidget = mk({
67
- type: "class_declaration",
68
- line: 3,
69
- endLine: 5,
70
- fields: { name: idNode("Widget") },
71
- namedChildren: [classBody],
72
- });
73
- const interfaceShape = mk({
74
- type: "interface_declaration",
75
- line: 6,
76
- endLine: 6,
77
- fields: { name: idNode("Shape") },
78
- });
79
- return mk({
80
- type: "program",
81
- line: 1,
82
- endLine: 6,
83
- namedChildren: [funcAlpha, funcHelper, classWidget, interfaceShape],
84
- });
85
- }
86
-
87
- async function loadJsonl<T>(file: string): Promise<T[]> {
88
- const content = await readFile(file, "utf8");
89
- return content
90
- .split("\n")
91
- .map((line) => line.trim())
92
- .filter(Boolean)
93
- .map((line) => JSON.parse(line) as T);
94
- }
95
-
96
- function prf(produced: Set<string>, expected: Set<string>): { precision: number; recall: number } {
97
- let truePos = 0;
98
- for (const item of produced) {
99
- if (expected.has(item)) {
100
- truePos += 1;
101
- }
102
- }
103
- return {
104
- precision: produced.size === 0 ? 0 : truePos / produced.size,
105
- recall: expected.size === 0 ? 0 : truePos / expected.size,
106
- };
107
- }
108
-
109
- test("AC1.2/AC1.3 — extracted symbols/calls hit precision ≥ 0.90 and recall ≥ 0.85", async () => {
110
- const layer = extractSymbolLayer(buildWidgetTree(), "src/widget.ts", "typescript");
111
- const expectedSymbols = await loadJsonl<SymbolNode>(path.join(FIXTURE_DIR, "expected", "symbols.jsonl"));
112
- const expectedCalls = await loadJsonl<CallEdge>(path.join(FIXTURE_DIR, "expected", "calls.jsonl"));
113
-
114
- const symbolKey = (s: SymbolNode) => `${s.id}|${s.kind}|${s.startLine}`;
115
- const callKey = (c: CallEdge) => `${c.kind}|${c.from}|${c.to}`;
116
-
117
- const symbolPrf = prf(new Set(layer.symbols.map(symbolKey)), new Set(expectedSymbols.map(symbolKey)));
118
- const callPrf = prf(new Set(layer.calls.map(callKey)), new Set(expectedCalls.map(callKey)));
119
-
120
- expect(symbolPrf.precision).toBeGreaterThanOrEqual(0.9);
121
- expect(symbolPrf.recall).toBeGreaterThanOrEqual(0.85);
122
- expect(callPrf.precision).toBeGreaterThanOrEqual(0.9);
123
- expect(callPrf.recall).toBeGreaterThanOrEqual(0.85);
124
- });
125
-
126
- test("AC1.1 — emits function/class/method/interface nodes + calls/defines/unresolved edges", () => {
127
- const layer = extractSymbolLayer(buildWidgetTree(), "src/widget.ts", "typescript");
128
- const kinds = new Set(layer.symbols.map((symbol) => symbol.kind));
129
- expect(kinds).toEqual(new Set(["function", "class", "method", "interface"]));
130
- const callKinds = new Set(layer.calls.map((call) => call.kind));
131
- expect(callKinds.has("defines")).toBe(true);
132
- expect(callKinds.has("calls")).toBe(true);
133
- });
134
-
135
- test("AC1.4 — symbol ids are stable + content-independent; re-run is byte-identical", () => {
136
- const a = extractSymbolLayer(buildWidgetTree(), "src/widget.ts", "typescript");
137
- const b = extractSymbolLayer(buildWidgetTree(), "src/widget.ts", "typescript");
138
- expect(JSON.stringify(a)).toBe(JSON.stringify(b));
139
- expect(a.symbols.map((symbol) => symbol.id)).toEqual([
140
- "src/widget.ts#alpha",
141
- "src/widget.ts#helper",
142
- "src/widget.ts#Widget",
143
- "src/widget.ts#Widget.render",
144
- "src/widget.ts#Shape",
145
- ]);
146
- });
147
-
148
- test("AC1.4 — colliding names get a positional @startLine disambiguator only", () => {
149
- const idNodeLocal = (text: string): TsNode => mk({ type: "identifier", text });
150
- const dup1 = mk({
151
- type: "function_declaration",
152
- line: 1,
153
- endLine: 1,
154
- fields: { name: idNodeLocal("dup"), parameters: paramsNode() },
155
- });
156
- const dup2 = mk({
157
- type: "function_declaration",
158
- line: 5,
159
- endLine: 5,
160
- fields: { name: idNodeLocal("dup"), parameters: paramsNode() },
161
- });
162
- const unique = mk({
163
- type: "function_declaration",
164
- line: 9,
165
- endLine: 9,
166
- fields: { name: idNodeLocal("only"), parameters: paramsNode() },
167
- });
168
- const root = mk({ type: "program", line: 1, endLine: 9, namedChildren: [dup1, dup2, unique] });
169
- const layer = extractSymbolLayer(root, "src/dup.ts", "typescript");
170
- const ids = layer.symbols.map((symbol) => symbol.id);
171
- expect(ids).toEqual(["src/dup.ts#dup@1", "src/dup.ts#dup@5", "src/dup.ts#only"]);
172
- });
173
-
174
- test("unresolved calls to unknown callees are kept as unresolved-call edges", () => {
175
- const idNodeLocal = (text: string): TsNode => mk({ type: "identifier", text });
176
- const body = mk({
177
- type: "statement_block",
178
- line: 1,
179
- namedChildren: [
180
- mk({ type: "call_expression", line: 1, fields: { function: idNodeLocal("externalThing") }, text: "externalThing()" }),
181
- ],
182
- });
183
- const fn = mk({
184
- type: "function_declaration",
185
- line: 1,
186
- endLine: 2,
187
- fields: { name: idNodeLocal("main"), parameters: paramsNode() },
188
- namedChildren: [body],
189
- });
190
- const root = mk({ type: "program", line: 1, endLine: 2, namedChildren: [fn] });
191
- const layer = extractSymbolLayer(root, "src/main.ts", "typescript");
192
- const unresolved = layer.calls.filter((call) => call.kind === "unresolved-call");
193
- expect(unresolved).toHaveLength(1);
194
- expect(unresolved[0]?.to).toBe("externalThing");
195
- expect(unresolved[0]?.resolved).toBe(false);
196
- });
197
-
198
- test("Java class and method extraction", () => {
199
- const idNode = (text: string): TsNode => mk({ type: "identifier", text });
200
- const methodBody = mk({ type: "block", line: 4, namedChildren: [
201
- mk({ type: "method_invocation", line: 4, fields: { method: idNode("helper") }, text: "helper()" }),
202
- ]});
203
- const methodHelper = mk({
204
- type: "method_declaration",
205
- line: 3,
206
- endLine: 3,
207
- fields: { name: idNode("helper"), parameters: mk({ type: "formal_parameters", text: "()" }) },
208
- });
209
- const methodMain = mk({
210
- type: "method_declaration",
211
- line: 4,
212
- endLine: 5,
213
- fields: { name: idNode("main"), parameters: mk({ type: "formal_parameters", text: "()" }) },
214
- namedChildren: [methodBody],
215
- });
216
- const classBody = mk({ type: "class_body", line: 2, endLine: 6, namedChildren: [methodHelper, methodMain] });
217
- const classApp = mk({
218
- type: "class_declaration",
219
- line: 1,
220
- endLine: 6,
221
- fields: { name: idNode("App") },
222
- namedChildren: [classBody],
223
- });
224
- const root = mk({ type: "program", line: 1, endLine: 6, namedChildren: [classApp] });
225
- const layer = extractSymbolLayer(root, "src/App.java", "java");
226
-
227
- expect(layer.symbols).toHaveLength(3); // App class + 2 methods
228
- const classSymbol = layer.symbols.find((s) => s.name === "App");
229
- expect(classSymbol).toBeDefined();
230
- expect(classSymbol?.kind).toBe("class");
231
-
232
- const methods = layer.symbols.filter((s) => s.kind === "method");
233
- expect(methods).toHaveLength(2);
234
- });
235
-
236
- test("Python function and class extraction", () => {
237
- const idNode = (text: string): TsNode => mk({ type: "identifier", text });
238
- const funcBody = mk({ type: "block", line: 4, namedChildren: [
239
- mk({ type: "call", line: 4, fields: { function: idNode("helper") }, text: "helper()" }),
240
- ]});
241
- const funcHelper = mk({
242
- type: "function_definition",
243
- line: 2,
244
- endLine: 2,
245
- fields: { name: idNode("helper"), parameters: mk({ type: "parameters", text: "()" }) },
246
- });
247
- const funcMain = mk({
248
- type: "function_definition",
249
- line: 4,
250
- endLine: 5,
251
- fields: { name: idNode("main"), parameters: mk({ type: "parameters", text: "()" }) },
252
- namedChildren: [funcBody],
253
- });
254
- const methodProcess = mk({
255
- type: "function_definition",
256
- line: 8,
257
- endLine: 8,
258
- fields: { name: idNode("process"), parameters: mk({ type: "parameters", text: "(self)" }) },
259
- });
260
- const classBody = mk({ type: "block", line: 7, endLine: 9, namedChildren: [methodProcess] });
261
- const classApp = mk({
262
- type: "class_definition",
263
- line: 6,
264
- endLine: 9,
265
- fields: { name: idNode("App") },
266
- namedChildren: [classBody],
267
- });
268
- const root = mk({ type: "program", line: 1, endLine: 9, namedChildren: [funcHelper, funcMain, classApp] });
269
- const layer = extractSymbolLayer(root, "src/main.py", "python");
270
-
271
- expect(layer.symbols).toHaveLength(4); // 2 functions + 1 class + 1 nested function (process)
272
- const classSymbol = layer.symbols.find((s) => s.name === "App");
273
- expect(classSymbol).toBeDefined();
274
- expect(classSymbol?.kind).toBe("class");
275
-
276
- const functions = layer.symbols.filter((s) => s.kind === "function");
277
- expect(functions.length).toBeGreaterThan(0); // Should have at least the top-level functions
278
- });
@@ -1,51 +0,0 @@
1
- import { readFile, readdir } from "node:fs/promises";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { expect, test } from "bun:test";
5
-
6
- // AC1.5 (C0-2): `web-tree-sitter` is imported ONLY via `await import()` inside
7
- // `src/gdgraph/treesitter/adapter.ts`. This is an ADDITIVE, gdgraph-specific
8
- // assertion layered on the generic `src/capability/no-optional-imports.test.ts`
9
- // (which already scans every optionalDependencies package across `src/`).
10
-
11
- const SRC_ROOT = fileURLToPath(new URL("../../", import.meta.url));
12
- const ADAPTER = path.join(SRC_ROOT, "gdgraph", "treesitter", "adapter.ts");
13
-
14
- async function tsFiles(dir: string): Promise<string[]> {
15
- const entries = await readdir(dir, { withFileTypes: true });
16
- const files: string[] = [];
17
- for (const entry of entries) {
18
- const full = path.join(dir, entry.name);
19
- if (entry.isDirectory()) {
20
- files.push(...(await tsFiles(full)));
21
- } else if (entry.isFile() && entry.name.endsWith(".ts")) {
22
- files.push(full);
23
- }
24
- }
25
- return files;
26
- }
27
-
28
- test("no static import of web-tree-sitter anywhere in src/", async () => {
29
- const files = await tsFiles(SRC_ROOT);
30
- const staticImport = /\b(?:import|export)\b[^()]*?\bfrom\s*['"]web-tree-sitter['"]|\bimport\s*['"]web-tree-sitter['"]|\brequire\s*\(\s*['"]web-tree-sitter['"]\s*\)/;
31
- const violations: string[] = [];
32
- for (const file of files) {
33
- const content = await readFile(file, "utf8");
34
- if (staticImport.test(content)) {
35
- violations.push(path.relative(SRC_ROOT, file));
36
- }
37
- }
38
- expect(violations).toEqual([]);
39
- });
40
-
41
- test("adapter declares web-tree-sitter as a lazy optionalDependency, never a static import", async () => {
42
- const content = await readFile(ADAPTER, "utf8");
43
- // The dep is loaded lazily by the seam (`await import(spec.optionalDependency)`
44
- // in capability/seam.ts) using the id the adapter declares here.
45
- expect(content).toContain('optionalDependency: "web-tree-sitter"');
46
- // The adapter never statically imports the dep (types are structural).
47
- expect(/from\s*['"]web-tree-sitter['"]/.test(content)).toBe(false);
48
-
49
- const seam = await readFile(path.join(SRC_ROOT, "capability", "seam.ts"), "utf8");
50
- expect(seam).toContain("await import(spec.optionalDependency)");
51
- });
@@ -1,71 +0,0 @@
1
- // AFC-13 / AC5 requirement 4: "fixture даёт реальные symbols после явной
2
- // установки" — a fixture that genuinely yields real symbols after an
3
- // explicit runtime/grammar installation. This task is forbidden from
4
- // performing that installation (no dependency installs of any kind).
5
- //
6
- // So this file does NOT install anything. It probes whether a working
7
- // `web-tree-sitter` runtime + a real, verified grammar are ALREADY present in
8
- // this environment (exactly the state an explicit install would produce) and:
9
- // - genuinely present ⇒ the test RUNS FOR REAL against the real adapter
10
- // (`resolveTreesitterCapability`, the actual production entry point —
11
- // not a mock) and asserts real, non-empty symbols extracted from a real
12
- // TypeScript fixture by the real grammar.
13
- // - genuinely absent ⇒ `test.skipIf` marks the test SKIPPED (visible as
14
- // "skip" in `bun test`'s own summary, not a silent pass), with the reason
15
- // embedded in the test name.
16
- //
17
- // A vacuous pass (the "absent" branch quietly returning without asserting
18
- // anything) would NOT be evidence for requirement 4 — `test.skipIf` is used
19
- // specifically so an absent environment shows up as a skip, never as a pass.
20
-
21
- import { expect, test } from "bun:test";
22
- import { resolveTreesitterCapability } from "./adapter";
23
-
24
- const FIXTURE_SOURCE = ["export function boot(): void {", " tick();", "}", "", "function tick(): void {}", ""].join(
25
- "\n",
26
- );
27
-
28
- // Probed once at module load (top-level await — this file's only test needs
29
- // the answer before `test.skipIf` decides whether to run). This calls the
30
- // REAL `resolveTreesitterCapability` against the real project root, so it
31
- // reflects whatever runtime + grammar state is genuinely on this machine —
32
- // no mocking, no injected `dep`.
33
- const projectRoot = process.cwd();
34
- const probedAdapter = await resolveTreesitterCapability(projectRoot, {
35
- languages: ["typescript"],
36
- grammarsPath: null,
37
- });
38
- const skipReason = probedAdapter
39
- ? ""
40
- : "no working web-tree-sitter runtime + verified typescript grammar resolved in this environment " +
41
- "(gdgraph.treesitter must be enabled in metaproject.json AND a matching grammar installed via " +
42
- '"keryx gdgraph assets pull tree-sitter-typescript" — an install this task must not perform)';
43
-
44
- test.skipIf(!probedAdapter)(
45
- skipReason ? `AC5.req4 — real fixture yields real symbols [SKIPPED: ${skipReason}]` : "AC5.req4 — real fixture yields real symbols after explicit installation",
46
- async () => {
47
- const adapter = probedAdapter;
48
- if (!adapter) {
49
- // Unreachable when the test actually runs (skipIf gates it), kept only
50
- // so TypeScript sees the non-null adapter below without a cast.
51
- throw new Error("AC5.req4 ran without a resolved adapter — skipIf condition was wrong");
52
- }
53
-
54
- const layer = await adapter.run({
55
- files: [{ path: "fixtures/treesitter/ac5-req4-fixture.ts", content: FIXTURE_SOURCE }],
56
- });
57
-
58
- // Real symbols, not a mock's canned output: names, kinds, and a resolved
59
- // call edge all come from the actually-installed tree-sitter grammar
60
- // parsing actual TypeScript source.
61
- expect(layer.symbols.length).toBeGreaterThan(0);
62
- const byName = Object.fromEntries(layer.symbols.map((s) => [s.name, s]));
63
- expect(byName.boot).toMatchObject({ kind: "function", language: "typescript" });
64
- expect(byName.tick).toMatchObject({ kind: "function", language: "typescript" });
65
-
66
- const callSummaries = layer.calls.map((c) => `${c.kind}:${c.from}=>${c.to}`);
67
- expect(callSummaries).toContain(
68
- "calls:fixtures/treesitter/ac5-req4-fixture.ts#boot=>fixtures/treesitter/ac5-req4-fixture.ts#tick",
69
- );
70
- },
71
- );
@@ -1,38 +0,0 @@
1
- import { expect, test } from "bun:test";
2
- import { resolveCrossFileCalls } from "./extract";
3
- import type { CallEdge, SymbolNode } from "../types";
4
-
5
- function sym(id: string, name: string, path: string): SymbolNode {
6
- return { id, kind: "function", path, name, container: null, startLine: 1, endLine: 2, language: "typescript" };
7
- }
8
- function unresolved(from: string, to: string): CallEdge {
9
- return { id: `u:${from}=>${to}`, from, to, kind: "unresolved-call", resolved: false };
10
- }
11
-
12
- test("resolves a cross-file member call to a globally-unique symbol", () => {
13
- const symbols = [sym("src/a.ts#runPipeline", "runPipeline", "src/a.ts"), sym("src/b.ts#caller", "caller", "src/b.ts")];
14
- // caller (in b.ts) calls `TasksApi.runPipeline` — cross-file, unresolved by per-file extraction
15
- const calls = [unresolved("src/b.ts#caller", "TasksApi.runPipeline")];
16
- const out = resolveCrossFileCalls(symbols, calls);
17
- expect(out).toHaveLength(1);
18
- expect(out[0]).toMatchObject({ from: "src/b.ts#caller", to: "src/a.ts#runPipeline", kind: "calls", resolved: true });
19
- });
20
-
21
- test("leaves an ambiguous name (multiple defs) unresolved — never guesses", () => {
22
- const symbols = [
23
- sym("src/a.ts#clonePipeline", "clonePipeline", "src/a.ts"),
24
- sym("src/b.ts#clonePipeline", "clonePipeline", "src/b.ts"),
25
- sym("src/c.ts#caller", "caller", "src/c.ts"),
26
- ];
27
- const out = resolveCrossFileCalls(symbols, [unresolved("src/c.ts#caller", "x.clonePipeline")]);
28
- expect(out[0]?.kind).toBe("unresolved-call");
29
- });
30
-
31
- test("drops self-edges and passes resolved calls through unchanged", () => {
32
- const symbols = [sym("src/a.ts#f", "f", "src/a.ts")];
33
- const resolved: CallEdge = { id: "c", from: "src/a.ts#g", to: "src/a.ts#f", kind: "calls", resolved: true };
34
- const out = resolveCrossFileCalls(symbols, [unresolved("src/a.ts#f", "f"), resolved]);
35
- // the self-call f->f is not added as a resolved edge; the pre-resolved call stays
36
- expect(out.some((c) => c.kind === "calls" && c.from === "src/a.ts#f" && c.to === "src/a.ts#f")).toBe(false);
37
- expect(out).toContainEqual(resolved);
38
- });
@@ -1,73 +0,0 @@
1
- // Flow 223 AC11: nothing phase 0 adds may depend on git.
2
- //
3
- // Different projects treat `.metaproject/` differently, and
4
- // `src/commands/init.no-git.test.ts` already pins "no git at all" as a
5
- // supported state. The describe edge, the wiki layer, the build manifest and
6
- // page provenance must therefore all work on a bare directory — this suite is
7
- // the guard that keeps a future `git` call from creeping into that path.
8
-
9
- import { expect, test } from "bun:test";
10
- import { mkdtemp, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
11
- import { tmpdir } from "node:os";
12
- import path from "node:path";
13
- import { computeVerifiedScope, parseProvenance, writeProvenance } from "../wiki/provenance";
14
- import { buildGraph } from "./build";
15
- import { getFilesDescribedBy, loadGraph } from "./query";
16
-
17
- async function bareProject(): Promise<string> {
18
- const root = await mkdtemp(path.join(tmpdir(), "lwg-nogit-"));
19
- await mkdir(path.join(root, "src", "ctx"), { recursive: true });
20
- await writeFile(path.join(root, "src", "ctx", "index.ts"), 'export * from "./run";\n');
21
- await writeFile(path.join(root, "src", "ctx", "run.ts"), "export const run = () => 1;\n");
22
- await mkdir(path.join(root, ".metaproject", "wiki", "components"), { recursive: true });
23
- await writeFile(
24
- path.join(root, ".metaproject", "wiki", "components", "src-ctx.md"),
25
- "# src/ctx\nVersion: 1.0.0\nType: component\nStatus: accepted\nDescribes:\n - src/ctx/**\n",
26
- );
27
- return root;
28
- }
29
-
30
- test("the whole phase-0 path works with no .git directory anywhere", async () => {
31
- const root = await bareProject();
32
-
33
- // Two builds: the first writes nodes.jsonl, the second sees a module set.
34
- await buildGraph(root);
35
- await buildGraph(root);
36
-
37
- const graph = await loadGraph(root);
38
- expect(graph.wikiPages?.[0]?.id).toBe("wiki:components/src-ctx.md");
39
- expect(getFilesDescribedBy(graph, "wiki:components/src-ctx.md")).toEqual([
40
- "src/ctx/index.ts",
41
- "src/ctx/run.ts",
42
- ]);
43
-
44
- // VerifiedScope is the git-free freshness path; it must compute here.
45
- const scope = await computeVerifiedScope(root, ["src/ctx/index.ts", "src/ctx/run.ts"], graph);
46
- expect(scope).toMatch(/^sha256:[0-9a-f]{64}$/);
47
-
48
- const pagePath = path.join(root, ".metaproject", "wiki", "components", "src-ctx.md");
49
- const stamped = writeProvenance(await readFile(pagePath, "utf8"), { verifiedScope: scope });
50
- await writeFile(pagePath, stamped);
51
- expect(parseProvenance(await readFile(pagePath, "utf8")).verifiedScope).toBe(scope);
52
-
53
- // And no `.git` was created along the way.
54
- expect(await readdir(root)).not.toContain(".git");
55
- });
56
-
57
- test("a page carrying VerifiedAt but no reachable git history still resolves", async () => {
58
- const root = await bareProject();
59
- const pagePath = path.join(root, ".metaproject", "wiki", "components", "src-ctx.md");
60
- await writeFile(
61
- pagePath,
62
- writeProvenance(await readFile(pagePath, "utf8"), { verifiedAt: "a".repeat(40) }),
63
- );
64
-
65
- await buildGraph(root);
66
- await buildGraph(root);
67
-
68
- // The sha is parsed and kept; deciding it is unreachable is phase 1's job,
69
- // and must not become a build-time failure here (specification §4.1 path 2).
70
- expect(parseProvenance(await readFile(pagePath, "utf8")).verifiedAt).toBe("a".repeat(40));
71
- const graph = await loadGraph(root);
72
- expect(graph.wikiPages?.[0]?.undecidable).toBe(false);
73
- });