@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.
- package/README.md +2 -1
- package/dist/cli.js +28857 -19477
- package/dist/core.js +25751 -0
- package/package.json +15 -2
- package/src/gdgraph/dangling.ts +204 -0
- package/src/gdgraph/find.ts +529 -37
- package/src/gdgraph/repomap.ts +140 -12
- package/src/gdgraph/staleness.ts +22 -9
- package/src/gdgraph/symbol.ts +45 -6
- package/src/gdgraph/treesitter/extract.ts +153 -5
- package/src/gdgraph/wiki-layer.ts +32 -1
- package/src/gdskills/bundled/skills/core/reviewer-skill-creator/SKILL.md +29 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +34 -6
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +52 -5
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +52 -5
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +52 -5
- package/src/gdgraph/affected.test.ts +0 -133
- package/src/gdgraph/build-integrity.test.ts +0 -193
- package/src/gdgraph/build-lang.test.ts +0 -406
- package/src/gdgraph/build.test.ts +0 -120
- package/src/gdgraph/config.test.ts +0 -47
- package/src/gdgraph/core-sources.test.ts +0 -99
- package/src/gdgraph/fallback.test.ts +0 -153
- package/src/gdgraph/find.test.ts +0 -78
- package/src/gdgraph/import-kind.test.ts +0 -525
- package/src/gdgraph/path.test.ts +0 -56
- package/src/gdgraph/repomap.test.ts +0 -110
- package/src/gdgraph/service.test.ts +0 -89
- package/src/gdgraph/staleness.test.ts +0 -208
- package/src/gdgraph/symbol.test.ts +0 -89
- package/src/gdgraph/symbols-capability.test.ts +0 -138
- package/src/gdgraph/treesitter/adapter.test.ts +0 -496
- package/src/gdgraph/treesitter/extract.test.ts +0 -278
- package/src/gdgraph/treesitter/no-treesitter-import.test.ts +0 -51
- package/src/gdgraph/treesitter/real-grammar-fixture.test.ts +0 -71
- package/src/gdgraph/treesitter/resolve-calls.test.ts +0 -38
- package/src/gdgraph/wiki-layer-no-git.test.ts +0 -73
- 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.
|
|
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
|
+
}
|