@mrciphersmith/keryx 0.2.82 → 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 (48) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +28857 -19477
  3. package/dist/core.js +25751 -0
  4. package/package.json +15 -2
  5. package/src/gdgraph/dangling.ts +204 -0
  6. package/src/gdgraph/find.ts +529 -37
  7. package/src/gdgraph/repomap.ts +140 -12
  8. package/src/gdgraph/staleness.ts +22 -9
  9. package/src/gdgraph/symbol.ts +45 -6
  10. package/src/gdgraph/treesitter/extract.ts +153 -5
  11. package/src/gdgraph/wiki-layer.ts +32 -1
  12. package/src/gdskills/bundled/skills/core/reviewer-skill-creator/SKILL.md +29 -0
  13. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +1 -1
  14. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +1 -1
  15. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +1 -1
  16. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +1 -1
  17. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +1 -1
  18. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +34 -6
  19. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1 -1
  20. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1 -1
  21. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
  22. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1 -1
  23. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1 -1
  24. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +52 -5
  25. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +52 -5
  26. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +52 -5
  27. package/src/gdgraph/affected.test.ts +0 -133
  28. package/src/gdgraph/build-integrity.test.ts +0 -193
  29. package/src/gdgraph/build-lang.test.ts +0 -406
  30. package/src/gdgraph/build.test.ts +0 -120
  31. package/src/gdgraph/config.test.ts +0 -47
  32. package/src/gdgraph/core-sources.test.ts +0 -99
  33. package/src/gdgraph/fallback.test.ts +0 -153
  34. package/src/gdgraph/find.test.ts +0 -78
  35. package/src/gdgraph/import-kind.test.ts +0 -525
  36. package/src/gdgraph/path.test.ts +0 -56
  37. package/src/gdgraph/repomap.test.ts +0 -110
  38. package/src/gdgraph/service.test.ts +0 -89
  39. package/src/gdgraph/staleness.test.ts +0 -208
  40. package/src/gdgraph/symbol.test.ts +0 -89
  41. package/src/gdgraph/symbols-capability.test.ts +0 -138
  42. package/src/gdgraph/treesitter/adapter.test.ts +0 -496
  43. package/src/gdgraph/treesitter/extract.test.ts +0 -278
  44. package/src/gdgraph/treesitter/no-treesitter-import.test.ts +0 -51
  45. package/src/gdgraph/treesitter/real-grammar-fixture.test.ts +0 -71
  46. package/src/gdgraph/treesitter/resolve-calls.test.ts +0 -38
  47. package/src/gdgraph/wiki-layer-no-git.test.ts +0 -73
  48. package/src/gdgraph/wiki-layer.test.ts +0 -211
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.82",
3
+ "version": "0.2.84",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -33,14 +33,26 @@
33
33
  "bin": {
34
34
  "keryx": "./dist/cli.js"
35
35
  },
36
+ "main": "./dist/core.js",
37
+ "exports": {
38
+ ".": "./dist/core.js",
39
+ "./package.json": "./package.json"
40
+ },
36
41
  "scripts": {
37
42
  "keryx": "bun ./src/cli.ts",
38
- "build": "bun build ./src/cli.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/harness/process/sandbox/proxy-worker.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core",
43
+ "build": "bun build ./src/cli.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/core.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/harness/process/sandbox/proxy-worker.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core",
39
44
  "prepare": "bun run build",
40
45
  "typecheck": "tsc --noEmit",
41
46
  "typecheck:scripts": "tsc --project tsconfig.scripts.json --noEmit",
42
47
  "test": "bun test",
48
+ "test:core": "bun test src/cli src/core src/assets/ src/capability/ src/commands/ src/contracts/ src/ctx/ src/eval/ src/flow/ src/forgetting/ src/gdgraph/ src/gdskills/ src/health/ src/job/ src/lib/ src/mcp/ src/memory/ src/metrics/ src/retention/ src/review/ src/sac/ src/security/ src/standard/ src/sync/ src/testing/ src/wiki/",
49
+ "test:client:terminal": "bun test src/tui/ src/commands/shell",
50
+ "test:client:streaming": "bun test src/harness/provider/",
51
+ "test:client:cancel-resume": "bun test src/harness/run/ src/harness/resume/ src/harness/session/ src/session/ src/commands/sessions",
52
+ "test:client:runtime": "bun test src/harness/ src/mcp-client/ src/agents/ src/commands/agent src/commands/harness src/commands/providers",
53
+ "test:client": "bun run test:client:terminal && bun run test:client:streaming && bun run test:client:cancel-resume && bun run test:client:runtime",
43
54
  "check": "bun run lint && bun run typecheck && bun run typecheck:scripts && bun test",
55
+ "check:core": "bun run lint && bun run typecheck && bun run typecheck:scripts && bun run test:core",
44
56
  "check:doc-links": "bun scripts/check-doc-links.ts",
45
57
  "baseline:review-precision": "bun scripts/review-precision-baseline.ts",
46
58
  "test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts src/gdskills/agent-catalogue-xref.test.ts src/gdskills/enforcement-claims.test.ts",
@@ -52,6 +64,7 @@
52
64
  "src/gdgraph",
53
65
  "src/gdskills/bundled",
54
66
  "src/gdskills/contracts",
67
+ "!src/**/*.test.ts",
55
68
  "LICENSE",
56
69
  "README.md",
57
70
  "package.json"
@@ -0,0 +1,204 @@
1
+ // Flow 242 (forgetting), lane E — the graph layer's answer to a reference into
2
+ // deleted code.
3
+ //
4
+ // Measured on a scratch project before this module existed. `src/orders.ts`
5
+ // imports `./billing`; `src/billing.ts` is deleted; the graph is rebuilt.
6
+ //
7
+ // $ keryx gdgraph affected src/orders.ts
8
+ // ## Dependencies
9
+ // - none
10
+ // $ keryx gdgraph query orphans
11
+ // src/orders.ts
12
+ //
13
+ // Both answers are produced by the same line, repeated in three places:
14
+ // `edge.kind !== "unresolved"`. `computeAffected`, `getAffected` and
15
+ // `getOrphans` (`./affected.ts`, `./query.ts`) all drop unresolved edges before
16
+ // they answer, so a file whose dependency was deleted reports the same
17
+ // "Dependencies: none" as a file that never had one, and a file left holding
18
+ // nothing but a broken import is reported as an ORPHAN — a positive claim that
19
+ // nothing references it and it references nothing.
20
+ //
21
+ // That filter is correct for what those three functions compute: an unresolved
22
+ // edge has no node on the other end, so it cannot participate in a dependency
23
+ // closure, a blast radius or a cycle. What was missing is a second answer
24
+ // alongside them, saying that the edge is there and does not land. This module
25
+ // is that answer, and it deliberately does NOT change the three: their results
26
+ // stay byte-identical (`./dangling.test.ts` pins the orphan set to
27
+ // `getOrphans`'s own), because a "fix" that folded unresolved targets into
28
+ // `dependencies` would put strings that are not files into a set every caller
29
+ // reads as file paths.
30
+ //
31
+ // The one distinction that decides whether an unresolved edge means anything:
32
+ // SCOPE. On this repository's own graph, 60 of the unresolved edges are npm
33
+ // packages and Python standard-library modules (`collections`, `datetime`,
34
+ // `os`) — specifiers that were never expected to resolve to a file in this
35
+ // tree. Reporting those as dangling references would bury the two or three
36
+ // that matter under sixty that do not, which is how a report stops being read.
37
+ // Only a RELATIVE specifier addresses this project's own file tree, so only a
38
+ // relative specifier can dangle in it.
39
+
40
+ import type { GraphData, ImportKind } from "./types";
41
+ import { getOrphans } from "./query";
42
+
43
+ /**
44
+ * Whether a specifier addresses this project's own file tree.
45
+ *
46
+ * `in-project` is a relative specifier (`./x`, `../x`) — the only form whose
47
+ * failure to resolve is a statement about THIS repository. `external` is
48
+ * everything else: a bare package name, a Node/Python builtin, a URL. An
49
+ * external specifier that does not resolve is not a dangling reference into
50
+ * removed knowledge; it is a dependency this graph does not index, and calling
51
+ * it dangling would be a false positive sixty times over on this repo alone.
52
+ */
53
+ export type DanglingScope = "in-project" | "external";
54
+
55
+ export function classifyDanglingScope(specifier: string): DanglingScope {
56
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier === "." || specifier === ".."
57
+ ? "in-project"
58
+ : "external";
59
+ }
60
+
61
+ export type DanglingEdge = {
62
+ /** The file holding the reference. */
63
+ from: string;
64
+ /** The specifier as written in the source. */
65
+ specifier: string;
66
+ scope: DanglingScope;
67
+ /** What the transpiler reported for the statement, or null on an old graph. */
68
+ importKind: ImportKind | null;
69
+ };
70
+
71
+ export type DanglingEdgeOptions = {
72
+ /** Restrict to edges out of this file (exact `GraphNode.path` match). */
73
+ from?: string;
74
+ /** Restrict to one scope. Omit for both. */
75
+ scope?: DanglingScope;
76
+ };
77
+
78
+ /**
79
+ * Every unresolved edge in the graph, with its scope — the references that
80
+ * `dependencies`/`dependents`/`orphans` drop.
81
+ *
82
+ * Deterministic order (from, then specifier) so a report diffed between two
83
+ * runs shows only what actually changed.
84
+ */
85
+ export function getDanglingEdges(graph: GraphData, options: DanglingEdgeOptions = {}): DanglingEdge[] {
86
+ const found: DanglingEdge[] = [];
87
+ for (const edge of graph.edges) {
88
+ if (edge.kind !== "unresolved") {
89
+ continue;
90
+ }
91
+ if (options.from !== undefined && edge.from !== options.from) {
92
+ continue;
93
+ }
94
+ const scope = classifyDanglingScope(edge.specifier);
95
+ if (options.scope !== undefined && scope !== options.scope) {
96
+ continue;
97
+ }
98
+ found.push({
99
+ from: edge.from,
100
+ specifier: edge.specifier,
101
+ scope,
102
+ importKind: edge.importKind ?? null,
103
+ });
104
+ }
105
+ return found.sort((a, b) => a.from.localeCompare(b.from) || a.specifier.localeCompare(b.specifier));
106
+ }
107
+
108
+ /**
109
+ * Does this unresolved specifier name one of `deletedPaths`?
110
+ *
111
+ * A SECOND distinction, on top of scope, and the report is unusable without it.
112
+ * Run over this repository's own graph, `in-project` unresolved edges number in
113
+ * the dozens and almost all of them are import statements written inside test
114
+ * FIXTURE strings (`src/gdgraph/build-integrity.test.ts` contains
115
+ * `import "./dep"` as fixture content) — specifiers that never resolved to
116
+ * anything and never will. Listing those beside the one edge whose target was
117
+ * actually deleted buries it, and a report nobody reads reports nothing.
118
+ *
119
+ * So an edge is only called a reference into DELETED knowledge when its
120
+ * specifier names a file the caller can show was deleted. Everything else stays
121
+ * unclassified — explicitly, as "this stage cannot tell whether it once
122
+ * resolved", which is the true state and is not the same as "it is fine".
123
+ *
124
+ * The match is textual and deliberately does not re-run module resolution: this
125
+ * answers "is this plausibly that file", and the caller labels it as a match,
126
+ * never as a resolution.
127
+ */
128
+ export function namesDeletedFile(
129
+ edge: Pick<DanglingEdge, "from" | "specifier">,
130
+ deletedPaths: ReadonlySet<string>,
131
+ ): boolean {
132
+ const fromDir = edge.from.includes("/") ? edge.from.slice(0, edge.from.lastIndexOf("/")) : "";
133
+ const joined = normalizePosix(fromDir.length > 0 ? `${fromDir}/${edge.specifier}` : edge.specifier);
134
+ if (joined === null) {
135
+ return false;
136
+ }
137
+ for (const deleted of deletedPaths) {
138
+ if (deleted === joined || deleted.startsWith(`${joined}.`) || deleted.startsWith(`${joined}/index.`)) {
139
+ return true;
140
+ }
141
+ }
142
+ return false;
143
+ }
144
+
145
+ /** Resolve `.`/`..` segments textually. Null when the path escapes the root. */
146
+ function normalizePosix(value: string): string | null {
147
+ const parts: string[] = [];
148
+ for (const segment of value.split("/")) {
149
+ if (segment === "" || segment === ".") {
150
+ continue;
151
+ }
152
+ if (segment === "..") {
153
+ if (parts.length === 0) {
154
+ return null;
155
+ }
156
+ parts.pop();
157
+ continue;
158
+ }
159
+ parts.push(segment);
160
+ }
161
+ return parts.join("/");
162
+ }
163
+
164
+ /**
165
+ * Why a file appears in `getOrphans`.
166
+ *
167
+ * `isolated` is the answer the query has always implied: nothing points at this
168
+ * file and it points at nothing. `dangling-only` is the answer it was giving
169
+ * WITHOUT saying so: the file does reference something, and the something is
170
+ * not there. Rendering the two identically is the defect — "nothing depends on
171
+ * this, delete it" and "its dependency was deleted, this is now broken" are
172
+ * opposite conclusions drawn from the same line of output.
173
+ */
174
+ export type OrphanCause =
175
+ | { path: string; cause: "isolated" }
176
+ | { path: string; cause: "dangling-only"; unresolved: DanglingEdge[] };
177
+
178
+ /**
179
+ * Explain, never re-decide.
180
+ *
181
+ * The path set returned here is exactly `getOrphans(graph)` — this function
182
+ * classifies that set and does not add to or subtract from it, so an orphan
183
+ * count taken from either stays the same number. `./dangling.test.ts` asserts
184
+ * the equality rather than trusting the comment.
185
+ */
186
+ export function explainOrphans(graph: GraphData): OrphanCause[] {
187
+ const orphans = getOrphans(graph);
188
+ const unresolvedByFile = new Map<string, DanglingEdge[]>();
189
+ for (const edge of getDanglingEdges(graph)) {
190
+ const bucket = unresolvedByFile.get(edge.from);
191
+ if (bucket) {
192
+ bucket.push(edge);
193
+ } else {
194
+ unresolvedByFile.set(edge.from, [edge]);
195
+ }
196
+ }
197
+
198
+ return orphans.map((orphanPath) => {
199
+ const unresolved = unresolvedByFile.get(orphanPath) ?? [];
200
+ return unresolved.length > 0
201
+ ? ({ path: orphanPath, cause: "dangling-only", unresolved } as const)
202
+ : ({ path: orphanPath, cause: "isolated" } as const);
203
+ });
204
+ }