@mrciphersmith/keryx 0.3.21 → 0.3.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.21",
3
+ "version": "0.3.24",
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": {
@@ -45,7 +45,7 @@
45
45
  "typecheck": "tsc --noEmit",
46
46
  "typecheck:scripts": "tsc --project tsconfig.scripts.json --noEmit",
47
47
  "test": "bun test",
48
- "test:core": "bun test src/cli src/core src/shell-source-audits.test.ts src/acp/ src/assets/ src/bundle/ src/capability/ src/commands/ src/contracts/ src/ctx/ src/eval/ src/flow/ src/forgetting/ src/gdgraph/ src/gdskills/ src/governance/ src/health/ src/integrations/ src/job/ src/learning/ src/lib/ src/mcp/ src/memory/ src/metrics/ src/retention/ src/review/ src/rules/ src/sac/ src/security/ src/stack/ src/standard/ src/sync/ src/testing/ src/trigger/ src/wiki/",
48
+ "test:core": "bun test src/cli src/core src/impact-evidence/ src/shell-source-audits.test.ts src/acp/ src/assets/ src/bundle/ src/capability/ src/commands/ src/contracts/ src/ctx/ src/eval/ src/flow/ src/forgetting/ src/gdgraph/ src/gdskills/ src/governance/ src/health/ src/integrations/ src/job/ src/learning/ src/lib/ src/mcp/ src/memory/ src/metrics/ src/retention/ src/review/ src/rules/ src/sac/ src/security/ src/stack/ src/standard/ src/sync/ src/testing/ src/trigger/ src/wiki/",
49
49
  "test:client:terminal": "bun test src/tui/ src/commands/shell",
50
50
  "test:client:streaming": "bun test src/harness/provider/",
51
51
  "test:client:cancel-resume": "bun test src/harness/run/ src/harness/resume/ src/harness/session/ src/session/ src/bus/ src/commands/sessions",
@@ -72,7 +72,20 @@ export function getFilesDescribedBy(graph: GraphData, pageId: string): string[]
72
72
  return [...files].sort();
73
73
  }
74
74
 
75
- export function getOrphans(graph: GraphData): string[] {
75
+ /**
76
+ * `roots` (A-8, flow 356): files reached OUT OF BAND — never by any `import`
77
+ * statement this graph's builder can see, so they carry no inbound OR
78
+ * outbound edge and would otherwise report as orphans. `src/lib/
79
+ * test-preload.ts` is the motivating case: `bunfig.toml`'s `[test].preload`
80
+ * loads it before any test module runs, which is a real caller this scan
81
+ * simply cannot see (it is not written as an `import`). Reporting it dead is
82
+ * a false positive, not a finding — `bunfigPreloadRoots` below is the one
83
+ * caller (`commands/gdgraph.ts`, `gdgraph/service.ts`) resolves and passes
84
+ * in; a caller that does not know about a project's `bunfig.toml` (e.g. this
85
+ * module's own unit tests, `forgetting/propagation.ts`) omits it and gets
86
+ * the previous, unfiltered behaviour.
87
+ */
88
+ export function getOrphans(graph: GraphData, roots: ReadonlySet<string> = new Set()): string[] {
76
89
  const inbound = new Set(
77
90
  graph.edges.filter((edge) => edge.kind !== "unresolved").map((edge) => edge.to),
78
91
  );
@@ -81,10 +94,97 @@ export function getOrphans(graph: GraphData): string[] {
81
94
  );
82
95
  return graph.nodes
83
96
  .map((node) => node.path)
84
- .filter((file) => !inbound.has(file) && !outbound.has(file))
97
+ .filter((file) => !inbound.has(file) && !outbound.has(file) && !roots.has(file))
85
98
  .sort();
86
99
  }
87
100
 
101
+ /**
102
+ * `bunfig.toml`'s declared `preload` entries, resolved to graph-relative
103
+ * paths (forward-slash, relative to `projectRoot` — the same shape
104
+ * `GraphNode.path` uses) — the roots {@link getOrphans} excludes (A-8).
105
+ *
106
+ * Handles the two shapes Bun actually reads a `preload` array from: a
107
+ * top-level `preload = […]` (`bun run`/`bunx`) and a table-scoped one
108
+ * (`[test]\npreload = […]`, this repository's own `bunfig.toml`). Not a
109
+ * general TOML parser — narrow on purpose, one array key, matching this
110
+ * repository's own `mcp-servers/compat.ts` precedent of a small reader over
111
+ * a dependency for one config shape. An absent or unreadable `bunfig.toml`
112
+ * yields no roots rather than an error: most projects this graph runs
113
+ * against have none at all.
114
+ */
115
+ /**
116
+ * Strip `#`-to-end-of-line comments from `text`, leaving string literals
117
+ * (single- or double-quoted, double-quoted honoring a `\"` escape) alone —
118
+ * a `#` inside a string is content, not a comment start. Review round 1,
119
+ * L3: without this, a commented-out `# preload = […]` line still matched
120
+ * {@link bunfigPreloadRoots}'s array pattern, since that pattern searched
121
+ * the raw text with no comment awareness.
122
+ */
123
+ function stripTomlComments(text: string): string {
124
+ let result = "";
125
+ let quote: '"' | "'" | undefined;
126
+ for (let i = 0; i < text.length; i++) {
127
+ const ch = text[i];
128
+ if (quote !== undefined) {
129
+ result += ch;
130
+ if (ch === "\\" && quote === '"' && i + 1 < text.length) {
131
+ result += text[i + 1];
132
+ i++;
133
+ continue;
134
+ }
135
+ if (ch === quote) {
136
+ quote = undefined;
137
+ }
138
+ continue;
139
+ }
140
+ if (ch === '"' || ch === "'") {
141
+ quote = ch;
142
+ result += ch;
143
+ continue;
144
+ }
145
+ if (ch === "#") {
146
+ const newlineIndex = text.indexOf("\n", i);
147
+ if (newlineIndex === -1) {
148
+ break;
149
+ }
150
+ i = newlineIndex - 1;
151
+ continue;
152
+ }
153
+ result += ch;
154
+ }
155
+ return result;
156
+ }
157
+
158
+ export async function bunfigPreloadRoots(projectRoot: string): Promise<Set<string>> {
159
+ const roots = new Set<string>();
160
+ let text: string;
161
+ try {
162
+ text = await readFile(path.join(projectRoot, "bunfig.toml"), "utf8");
163
+ } catch {
164
+ return roots;
165
+ }
166
+ // Review round 1, L3: strip `#` comments (outside string literals) before
167
+ // matching, and anchor the key to a line start — a commented-out
168
+ // `# preload = […]` line must never contribute a root, and a key that
169
+ // merely ENDS in "preload" mid-line must never match either.
170
+ const stripped = stripTomlComments(text);
171
+ const arrayPattern = /^[ \t]*preload\s*=\s*\[([^\]]*)\]/gm;
172
+ const stringPattern = /"((?:[^"\\]|\\.)*)"|'([^']*)'/g;
173
+ for (const arrayMatch of stripped.matchAll(arrayPattern)) {
174
+ const body = arrayMatch[1] ?? "";
175
+ for (const stringMatch of body.matchAll(stringPattern)) {
176
+ const raw = stringMatch[1] ?? stringMatch[2] ?? "";
177
+ if (raw.length === 0) {
178
+ continue;
179
+ }
180
+ const absolute = path.resolve(projectRoot, raw);
181
+ const relative = path.relative(projectRoot, absolute).split(path.sep).join("/");
182
+ roots.add(relative);
183
+ }
184
+ }
185
+ return roots;
186
+ }
187
+
88
188
  export function getAffected(
89
189
  graph: GraphData,
90
190
  target: string,
@@ -8,7 +8,7 @@
8
8
  import { buildGraph } from "./build";
9
9
  import { computeAffected, type AffectedOptions, type AffectedResult } from "./affected";
10
10
  import { loadGdgraphConfig } from "./config";
11
- import { getCycles, getOrphans, loadGraph } from "./query";
11
+ import { bunfigPreloadRoots, getCycles, getOrphans, loadGraph } from "./query";
12
12
  import { writeRepomap, type RepomapOptions, type RepomapResult } from "./repomap";
13
13
  import type { GraphData } from "./types";
14
14
 
@@ -90,7 +90,13 @@ export function createGdgraphService(): GdgraphService {
90
90
 
91
91
  async query(cwd, q) {
92
92
  const graph = await loadGraph(cwd);
93
- return q === "cycles" ? getCycles(graph) : getOrphans(graph);
93
+ if (q === "cycles") {
94
+ return getCycles(graph);
95
+ }
96
+ // A-8 (flow 356): same roots as `commands/gdgraph.ts` — a `bunfig.toml`
97
+ // `preload` entry is a real caller this graph's scan cannot see.
98
+ const roots = await bunfigPreloadRoots(cwd);
99
+ return getOrphans(graph, roots);
94
100
  },
95
101
  };
96
102
  }
@@ -1587,10 +1587,12 @@ Fix: <concrete>
1587
1587
  - <what was checked> — <how>
1588
1588
 
1589
1589
  ### How this review was run
1590
- - **Run by:** @<gh-login> with `review-orchestrator`
1590
+ - **Workflow:** `review-orchestrator`
1591
1591
  - **Scope:** `<base>..<head>`, round N, PR #N
1592
- - **Orchestrator:** `review-orchestrator`
1593
- - **<actual model name>:** <reviewers on that model>
1592
+ - **Models:** <actual models and reviewers, or not recorded>
1593
+ - **Tools:** <tools actually invoked, or not recorded>
1594
+ - **Skills:** <skills actually used, or not recorded>
1595
+ - **Subagents:** <dispatched reviewers and models, or none when confirmed; otherwise not recorded>
1594
1596
  - **Not run:** <reviewer — reason>
1595
1597
  - **Verification:** <mode>; confirmed N, refuted N, unverifiable N, unverified N
1596
1598
  ```
@@ -1663,17 +1665,15 @@ Automation values, names unchanged:
1663
1665
 
1664
1666
  Default is do not publish. No resolvable PR number means skip and say so.
1665
1667
 
1666
- The comment is the report rendered from `templates/review-report.md`, English, with the domain file chosen above (`templates/pr-comment-frontend.md` / `templates/pr-comment-backend.md`) — never a summary written freehand. It does not use a tool heading, a finding table, or a meta table. It does not carry a co-author line, a `Generated with` trailer, or any sentence that names a vendor or a product as the author. Say who ran the orchestrator and which reviewers ran; do not sign the comment as them.
1668
+ The comment is the report rendered from `templates/review-report.md`, English, with the domain file chosen above (`templates/pr-comment-frontend.md` / `templates/pr-comment-backend.md`) — never a summary written freehand. It does not use a tool heading, a finding table, or a meta table. It does not carry a co-author line, a `Generated with` trailer, or any sentence that names a vendor or a product as the author. Name the workflow and reviewers that actually ran, with models, tools, skills and subagents. Never identify the operator from `gh auth status`, git config or a credential; do not sign the comment as a person, reviewer or model.
1667
1669
 
1668
1670
  The follow-up file path and the metadata rules (real model names, Run vs Not run, no `adaptive` in the model slot) live in that same template. Write the body to a temp file and post with `gh pr comment <n> --body-file <file>`.
1669
1671
 
1670
1672
  **No GitHub write without an approved draft.** Before ANY write — the comment, a thread reply, `keryx review comments reply`, a review — show the user the rendered body and wait for explicit approval of that body.
1671
1673
  Picking A or B above chooses *whether* to publish, not *what*; a comment is public and cannot be unsent. With no user to answer (a dispatched run), do not write: hand the rendered body back to the caller.
1672
1674
 
1673
- Re-read head and the thread immediately before posting. If head moved, re-check
1674
- the findings against the new head and name the commits that were not reviewed.
1675
- Do not post a finding another reviewer already filed on this head; if yours
1676
- extends theirs, cite theirs.
1675
+ Re-read head and thread before posting. If head moved, re-check findings
1676
+ and name unchecked commits. Do not duplicate findings; cite extended ones.
1677
1677
 
1678
1678
  ## Job Context Awareness
1679
1679
 
@@ -95,14 +95,19 @@ Do not list a checklist item that was not checked. Do not praise.
95
95
 
96
96
  ## How this review was run
97
97
 
98
- A list, not a meta table. Keep every fact the old Meta table carried.
98
+ A factual run inventory, not an author or account signature. List only steps
99
+ confirmed by the run record; never infer an operator identity from `gh auth`,
100
+ git config, a credential or a PR author. Use `not recorded` when evidence for
101
+ a tool, skill or model is unavailable; use `none` only when the run proves none.
99
102
 
100
103
  ```markdown
101
104
  ### How this review was run
102
- - **Run by:** @<gh-login> with `review-orchestrator`. Severity and lanes per the repository review rules, else the canonical rubric in this skill.
105
+ - **Workflow:** `review-orchestrator`; severity and lanes per the repository review rules, else the canonical rubric in this skill.
103
106
  - **Scope:** `<base>..<head>`, round N, PR #N. Unchecked commits: <list or none>.
104
- - **Orchestrator:** `review-orchestrator`
105
- - **<actual model name>:** <reviewers dispatched on it, comma-separated>
107
+ - **Models:** <actual model names and the reviewers that ran on each; or not recorded>.
108
+ - **Tools:** <tools actually invoked for this review and their roles; or not recorded>.
109
+ - **Skills:** <skills actually loaded or executed and their roles; or not recorded>.
110
+ - **Subagents:** <dispatched subagents, their reviewer roles and actual models; or none when confirmed, otherwise not recorded>.
106
111
  - **Fallback:** <reviewer> via `general-purpose` because <native agent type unavailable | no bundled agent>. Or `none`.
107
112
  - **Not run:** <reviewer> — <short reason>. Or `none`.
108
113
  - **Selection:** <auto-detected scope | explicit flags | optional groups the user picked>
@@ -118,8 +123,8 @@ Model rules, unchanged from the old metadata table:
118
123
  - Name the model that actually ran. Never write `adaptive`, `inherit`,
119
124
  `unsupported`, or `current-session` in the model slot.
120
125
  - Group reviewers under the model they ran on. One bullet per model.
121
- - `Model strategy` (`ask` or `adaptive`) goes on the Run by line or under
122
- Selection, not in place of a model name.
126
+ - `Model strategy` (`ask` or `adaptive`) goes under Selection, never in
127
+ place of an actual model name.
123
128
  - Keep **Run** complete. If Not run is long, group it with counts and notable
124
129
  names; the full list belongs in the follow-up file.
125
130