@selesai/code 0.13.22 → 0.13.23

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.
@@ -0,0 +1,392 @@
1
+ /**
2
+ * The `/graft` command surface: setup, build, deep, refresh, status, doctor, mode.
3
+ *
4
+ * Command handlers own two things the tools never touch: consent, and honest
5
+ * reporting. A command that can write to the repository says exactly which
6
+ * files it may create or change and asks first; with no dialog UI it refuses
7
+ * and prints the exact manual command instead.
8
+ *
9
+ * Nothing here ever invokes Graft's upstream agent-wiring command. Selesai's
10
+ * setup installs a missing CLI automatically, explains local effects, and builds
11
+ * a graph — it does not edit Claude, Codex, Cursor, Gemini, Copilot, or any
12
+ * other agent's configuration.
13
+ */
14
+
15
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@selesai/code";
16
+ import {
17
+ GRAFT_PACKAGE,
18
+ GRAPH_DIR_NAME,
19
+ INSTALL_COMMAND,
20
+ graftFailureMessage,
21
+ MAX_GRAFT_VERSION_EXCLUSIVE,
22
+ MIN_GRAFT_VERSION,
23
+ TELEMETRY_ENV,
24
+ type GraftInstallRun,
25
+ type GraftRun,
26
+ } from "./cli.ts";
27
+ import { describeStrategy } from "./prompt.ts";
28
+ import {
29
+ DEFAULT_MAX_INJECTION_BYTES,
30
+ DEFAULT_MIN_COVERAGE,
31
+ RETRIEVAL_MODES,
32
+ type RetrievalMode,
33
+ type GraftSettings,
34
+ type GraftState,
35
+ MODE_ENTRY_TYPE,
36
+ recoveryFor,
37
+ type GraphPresence,
38
+ statusDetail,
39
+ statusText,
40
+ } from "./state.ts";
41
+
42
+ /**
43
+ * Structural shape of a slash-command argument completion. Declared locally so
44
+ * this module depends only on `@selesai/code` at runtime.
45
+ */
46
+ export interface ArgumentCompletion {
47
+ value: string;
48
+ label: string;
49
+ description?: string;
50
+ }
51
+
52
+ /** Everything the command surface needs from the extension runtime. */
53
+ export interface GraftCommandRuntime {
54
+ /** Re-resolve repository root, executable, and graph presence. */
55
+ recheck(ctx: ExtensionContext): Promise<void>;
56
+ state(): GraftState;
57
+ settings(): GraftSettings;
58
+ mode(): RetrievalMode;
59
+ setMode(mode: RetrievalMode, ctx: ExtensionContext): void;
60
+ repoRoot(): string | undefined;
61
+ graphPresence(): GraphPresence;
62
+ /** Install the compatible CLI when probing found none. */
63
+ installCli(ctx: ExtensionContext): Promise<GraftInstallRun>;
64
+ /** Structural or deep build. Call only after consent. */
65
+ runBuild(deep: boolean, ctx: ExtensionContext): Promise<GraftRun>;
66
+ /** Proactive refresh of an existing graph; undefined when there is nothing to refresh. */
67
+ runRefresh(ctx: ExtensionContext): Promise<GraftRun | undefined>;
68
+ /** Names (never values) of Graft provider environment variables already set. */
69
+ providerEnvNames(): string[];
70
+ /** Whether this session actually set the telemetry opt-out default itself. */
71
+ telemetryDefaultApplied(): boolean;
72
+ }
73
+
74
+ export const DOCTOR_USAGE = `/graft <command>
75
+
76
+ status current availability, graph state, and retrieval mode
77
+ doctor full health report with a recovery step for each problem
78
+ setup install the CLI if needed, then explain build effects and ask for consent
79
+ build build graft/ for this repository (structural; no model, no key)
80
+ deep provider-backed build: adds summaries and per-symbol crux
81
+ refresh re-index an existing graph after edits
82
+ mode [pull|push|hybrid] show or set the retrieval strategy
83
+ help this message`;
84
+
85
+ /** Provider environment variables Graft reads. Values are never read or shown. */
86
+ export const PROVIDER_ENV_NAMES = [
87
+ "GRAFT_PROVIDER",
88
+ "GRAFT_MODEL",
89
+ "GRAFT_BASE_URL",
90
+ "GRAFT_API_KEY",
91
+ "OPENROUTER_API_KEY",
92
+ "ORCAROUTER_API_KEY",
93
+ ] as const;
94
+
95
+ /** Console fallback so print/JSON modes still get actionable diagnostics. */
96
+ function report(ctx: ExtensionContext, message: string, type?: "info" | "warning" | "error"): void {
97
+ if (ctx.hasUI) {
98
+ ctx.ui.notify(message, type);
99
+ return;
100
+ }
101
+ console.warn(`[graft] ${message}`);
102
+ }
103
+
104
+ export interface DoctorInput {
105
+ repoRoot: string | undefined;
106
+ gitRepo: boolean;
107
+ state: GraftState;
108
+ presence: GraphPresence;
109
+ mode: RetrievalMode;
110
+ settings: GraftSettings;
111
+ /** True when the extension set DO_NOT_TRACK itself for this session. */
112
+ telemetryDefault: boolean;
113
+ providerEnv: string[];
114
+ }
115
+
116
+ /** Pure health report. Exported so the wording can be asserted directly. */
117
+ export function formatDoctor(input: DoctorInput): string {
118
+ const lines: string[] = ["Graft doctor", ""];
119
+ const row = (label: string, value: string) => lines.push(` ${label.padEnd(12)} ${value}`);
120
+
121
+ row("repository", input.repoRoot ? `${input.repoRoot}${input.gitRepo ? " (git ✓)" : " (not a git repository)"}` : "unresolved");
122
+ row(
123
+ "executable",
124
+ input.state.name === "unavailable"
125
+ ? `not found on PATH — ${statusDetail(input.state)}`
126
+ : input.state.version
127
+ ? `graft ${input.state.version} (supported: >=${MIN_GRAFT_VERSION} <${MAX_GRAFT_VERSION_EXCLUSIVE})`
128
+ : statusDetail(input.state),
129
+ );
130
+ row(
131
+ "graph",
132
+ input.presence.built
133
+ ? `${GRAPH_DIR_NAME}/ present — ${input.presence.deep ? "structural + deep summaries" : "structural only"}`
134
+ : `no ${GRAPH_DIR_NAME}/ here`,
135
+ );
136
+ row("state", `${input.state.name} — ${statusDetail(input.state)}`);
137
+ row("mode", describeStrategy(input.mode));
138
+ row(
139
+ "telemetry",
140
+ input.telemetryDefault
141
+ ? `${TELEMETRY_ENV}=1 set by this extension for Graft processes (your Graft settings are untouched)`
142
+ : `${TELEMETRY_ENV} left to your environment`,
143
+ );
144
+ row("injection", `min coverage ${input.settings.minCoverage ?? DEFAULT_MIN_COVERAGE}, max ${input.settings.maxInjectionBytes ?? DEFAULT_MAX_INJECTION_BYTES} bytes`);
145
+ row(
146
+ "provider env",
147
+ input.providerEnv.length > 0
148
+ ? `${input.providerEnv.join(", ")} set (values never read or copied) — deep builds use Graft's own provider configuration`
149
+ : `none of ${PROVIDER_ENV_NAMES.join(", ")} set — deep builds would need Graft's own provider configuration`,
150
+ );
151
+
152
+ const recovery = recoveryFor(input.state);
153
+ if (recovery) {
154
+ lines.push("", ` next step ${recovery}`);
155
+ } else if (input.state.name === "fresh-deep") {
156
+ lines.push("", " next step nothing to fix; deep summaries are active for this graph.");
157
+ } else if (!input.presence.deep) {
158
+ lines.push(
159
+ "",
160
+ " next step structural context is active. /graft deep adds provider-backed summaries if you accept that source-derived work leaves the machine.",
161
+ );
162
+ }
163
+ return lines.join("\n");
164
+ }
165
+
166
+ export function formatStatus(input: DoctorInput): string {
167
+ const lines = [
168
+ statusText(input.state),
169
+ ` ${describeStrategy(input.mode)}`,
170
+ ` graph: ${input.presence.built ? (input.presence.deep ? "structural + deep" : "structural only") : "not built"}`,
171
+ ];
172
+ if (input.repoRoot) lines.push(` root: ${input.repoRoot}`);
173
+ return lines.join("\n");
174
+ }
175
+
176
+ /**
177
+ * What a build writes. Shown verbatim before consent so the user is agreeing to
178
+ * a concrete list, not to "some files".
179
+ */
180
+ export function buildEffects(deep: boolean): string {
181
+ const effects = [
182
+ ` • creates/updates ./${GRAPH_DIR_NAME}/ (markdown nodes, wiring graph, per-file cards)`,
183
+ " • adds graft/ to .gitignore and writes .ignore for ripgrep re-admit",
184
+ " • updates .graft/config.json with this repository's build choices",
185
+ ];
186
+ if (deep) {
187
+ effects.push(
188
+ " • sends source-derived file summaries and symbol cruxes through the LLM provider configured in Graft's own environment (GRAFT_PROVIDER / GRAFT_MODEL / GRAFT_BASE_URL / GRAFT_API_KEY)",
189
+ " • caches those summaries under graft/ so later deep builds are incremental",
190
+ );
191
+ }
192
+ return effects.join("\n");
193
+ }
194
+
195
+ /** Consent-gated mutation step. Returns false when the user declined or has no UI. */
196
+ async function confirmMutation(
197
+ ctx: ExtensionCommandContext,
198
+ title: string,
199
+ body: string,
200
+ manualCommand: string,
201
+ ): Promise<boolean> {
202
+ if (!ctx.hasUI) {
203
+ report(
204
+ ctx,
205
+ `this command changes the repository, so it needs an interactive confirmation. Run it yourself: ${manualCommand}`,
206
+ "warning",
207
+ );
208
+ return false;
209
+ }
210
+ const accepted = await ctx.ui.confirm(
211
+ title,
212
+ `${body}\n\nGraft's own \`init\` command is never run: this extension does not write configuration for other coding agents.\n\nProceed?`,
213
+ );
214
+ if (!accepted) report(ctx, `Cancelled. Nothing was written. To run it yourself: ${manualCommand}`, "info");
215
+ return accepted;
216
+ }
217
+
218
+ /** Install only when probing proved the CLI is absent; never overwrite an incompatible user install. */
219
+ async function ensureCli(
220
+ ctx: ExtensionCommandContext,
221
+ runtime: GraftCommandRuntime,
222
+ ): Promise<boolean> {
223
+ if (runtime.state().name !== "unavailable") return true;
224
+ report(ctx, "Installing Graft CLI…", "info");
225
+ const install = await runtime.installCli(ctx);
226
+ if (install.code !== 0 || install.killed) {
227
+ const detail = (install.stderr.trim() || install.stdout.trim() || `exit status ${install.code}`).split("\n")[0];
228
+ report(ctx, `Graft CLI install failed: ${detail}\nInstall it yourself: ${INSTALL_COMMAND}`, "error");
229
+ return false;
230
+ }
231
+
232
+ await runtime.recheck(ctx);
233
+ if (runtime.state().name === "unavailable" || runtime.state().name === "incompatible") {
234
+ report(
235
+ ctx,
236
+ `npm reported success, but Graft is still not usable: ${statusDetail(runtime.state())}. Restart Selesai or install it yourself: ${INSTALL_COMMAND}`,
237
+ "warning",
238
+ );
239
+ return false;
240
+ }
241
+ return true;
242
+ }
243
+
244
+ export function registerGraftCommands(pi: ExtensionAPI, runtime: GraftCommandRuntime): void {
245
+ const doctorInput = (): DoctorInput => ({
246
+ repoRoot: runtime.repoRoot(),
247
+ gitRepo: runtime.repoRoot() !== undefined,
248
+ state: runtime.state(),
249
+ presence: runtime.graphPresence(),
250
+ mode: runtime.mode(),
251
+ settings: runtime.settings(),
252
+ telemetryDefault: runtime.telemetryDefaultApplied(),
253
+ providerEnv: runtime.providerEnvNames(),
254
+ });
255
+
256
+ pi.registerCommand("graft", {
257
+ description: "Graft codebase-context: status, doctor, setup, build, deep, refresh, mode",
258
+ getArgumentCompletions: (prefix: string): ArgumentCompletion[] | null => {
259
+ const items: ArgumentCompletion[] = [
260
+ { value: "status", label: "status", description: "availability, graph state, retrieval mode" },
261
+ { value: "doctor", label: "doctor", description: "full health report and recovery steps" },
262
+ { value: "setup", label: "setup", description: "install CLI if needed, then build graft/" },
263
+ { value: "build", label: "build", description: "build graft/ (structural; no model, no key)" },
264
+ { value: "deep", label: "deep", description: "provider-backed build (needs Graft provider config)" },
265
+ { value: "refresh", label: "refresh", description: "re-index an existing graph after edits" },
266
+ { value: "mode", label: "mode", description: "show or set pull | push | hybrid" },
267
+ { value: "help", label: "help", description: "usage" },
268
+ ];
269
+ for (const mode of RETRIEVAL_MODES) {
270
+ items.push({ value: `mode ${mode}`, label: `mode ${mode}`, description: describeStrategy(mode) });
271
+ }
272
+ const filtered = prefix ? items.filter((item) => item.value.startsWith(prefix)) : items;
273
+ return filtered.length > 0 ? filtered : null;
274
+ },
275
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
276
+ const [subcommand = "status", ...rest] = args.trim().split(/\s+/).filter(Boolean);
277
+
278
+ switch (subcommand) {
279
+ case "help":
280
+ report(ctx, DOCTOR_USAGE, "info");
281
+ return;
282
+
283
+ case "status": {
284
+ await runtime.recheck(ctx);
285
+ report(ctx, formatStatus(doctorInput()), "info");
286
+ return;
287
+ }
288
+
289
+ case "doctor": {
290
+ await runtime.recheck(ctx);
291
+ report(ctx, formatDoctor(doctorInput()), "info");
292
+ const recovery = recoveryFor(runtime.state());
293
+ if (recovery && ctx.hasUI) ctx.ui.notify(recovery, "warning");
294
+ return;
295
+ }
296
+
297
+ case "mode": {
298
+ const requested = rest[0];
299
+ if (!requested) {
300
+ report(ctx, `${describeStrategy(runtime.mode())}\n\n${DOCTOR_USAGE}`, "info");
301
+ return;
302
+ }
303
+ if (!(RETRIEVAL_MODES as readonly string[]).includes(requested)) {
304
+ report(ctx, `Unknown mode "${requested}". Choose one of: ${RETRIEVAL_MODES.join(", ")}.`, "error");
305
+ return;
306
+ }
307
+ const mode = requested as RetrievalMode;
308
+ runtime.setMode(mode, ctx);
309
+ pi.appendEntry(MODE_ENTRY_TYPE, { mode });
310
+ report(ctx, `Retrieval mode: ${describeStrategy(mode)}`, "info");
311
+ return;
312
+ }
313
+
314
+ // `build --deep` and `deep` are the same operation, so they get the same
315
+ // consent: a deep build must never reach the provider under weaker
316
+ // framing than the dedicated command.
317
+ case "setup":
318
+ case "build":
319
+ case "deep": {
320
+ const deep = subcommand === "deep" || rest.includes("--deep");
321
+ await runtime.recheck(ctx);
322
+ if (!(await ensureCli(ctx, runtime))) return;
323
+ const repo = runtime.repoRoot() ?? "this repository";
324
+ const manualCommand = `cd ${runtime.repoRoot() ?? "."} && graft build${deep ? " --deep" : ""}`;
325
+ const accepted = await confirmMutation(
326
+ ctx,
327
+ deep
328
+ ? "Run the provider-backed deep build?"
329
+ : subcommand === "setup"
330
+ ? "Set up Graft for this repository?"
331
+ : "Build the Graft graph?",
332
+ deep
333
+ ? `This runs \`graft build --deep\` in ${repo}.\n\nThe structural graph stays local and deterministic. The deep pass is different: Graft summarizes each changed file and extracts per-symbol cruxes using the LLM provider configured in Graft's own environment, so source-derived content leaves this machine.\n\nSelesai does not read, copy, or forward its own provider credentials to Graft.\n\nFiles it can write:\n${buildEffects(true)}`
334
+ : `This runs \`graft build\` in ${repo} and can write:\n\n${buildEffects(false)}`,
335
+ manualCommand,
336
+ );
337
+ if (!accepted) return;
338
+ await runAndReport(ctx, runtime, () => runtime.runBuild(deep, ctx));
339
+ return;
340
+ }
341
+
342
+ case "refresh": {
343
+ await runtime.recheck(ctx);
344
+ if (!runtime.graphPresence().built) {
345
+ report(
346
+ ctx,
347
+ `No ${GRAPH_DIR_NAME}/ here yet — /graft build creates one (refresh only re-indexes an existing graph).`,
348
+ "warning",
349
+ );
350
+ return;
351
+ }
352
+ await runAndReport(ctx, runtime, () => runtime.runRefresh(ctx));
353
+ return;
354
+ }
355
+
356
+ default:
357
+ report(ctx, `Unknown command "${subcommand}".\n\n${DOCTOR_USAGE}`, "error");
358
+ }
359
+ },
360
+ });
361
+ }
362
+
363
+ async function runAndReport(
364
+ ctx: ExtensionCommandContext,
365
+ runtime: GraftCommandRuntime,
366
+ action: () => Promise<GraftRun | undefined>,
367
+ ): Promise<void> {
368
+ const run = await action();
369
+ if (!run) {
370
+ report(ctx, "Nothing to refresh: the graph is already in sync with the working tree.", "info");
371
+ return;
372
+ }
373
+ await runtime.recheck(ctx);
374
+ const state = runtime.state();
375
+ if (run.code !== 0) {
376
+ // A degraded deep pass still keeps everything it computed, so it is a
377
+ // warning with the command's own explanation rather than a plain failure.
378
+ const detail = (run.stderr.trim() || run.stdout.trim()).split("\n").slice(-12).join("\n");
379
+ report(
380
+ ctx,
381
+ `${graftFailureMessage(run)}\n${detail}`,
382
+ state.name === "failed" ? "error" : "warning",
383
+ );
384
+ return;
385
+ }
386
+ report(ctx, `Done. ${statusText(state)} — ${statusDetail(state)}`, "info");
387
+ }
388
+
389
+ /** Where the deep-build guidance points a user whose provider is not configured. */
390
+ export const DEEP_PROVIDER_HINT =
391
+ `Set GRAFT_PROVIDER, GRAFT_MODEL, and GRAFT_API_KEY in your own environment (or a repo .env), then re-run /graft deep. ` +
392
+ `${GRAFT_PACKAGE} reads them itself; this extension never forwards Selesai credentials.`;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Opt-in integration test against a real Graft CLI and a real Git fixture.
3
+ *
4
+ * The regular suite is deterministic and offline: it never installs anything and
5
+ * never calls a provider. This file is the escape hatch that exercises the
6
+ * adapter against the real executable, and it skips itself when no compatible
7
+ * Graft CLI is on PATH — which is the state of ordinary CI.
8
+ *
9
+ * To run it for real: `npm install -g @nanonets/graft@^0.18` then
10
+ * npx vitest run src/extensions/pi-graft/graft.integration.test.ts
11
+ *
12
+ * A structural build is tree-sitter only: no model, no API key, no network. The
13
+ * fixture never runs `graft init`, so the repository is left exactly as the test
14
+ * created it.
15
+ */
16
+
17
+ import { execFileSync, spawnSync } from "node:child_process";
18
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
19
+ import { tmpdir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
22
+ import {
23
+ isCompatibleVersion,
24
+ parseVersion,
25
+ probeGraft,
26
+ runGraft,
27
+ type ExecLike,
28
+ type GraftRun,
29
+ } from "./cli.ts";
30
+
31
+ /** Real-exec implementation of the same seam the extension uses via `pi.exec`. */
32
+ const realExec: ExecLike = async (command, args, options) => {
33
+ const result = spawnSync(command, args, {
34
+ cwd: options?.cwd,
35
+ encoding: "utf-8",
36
+ timeout: options?.timeout,
37
+ env: { ...process.env, DO_NOT_TRACK: "1" },
38
+ });
39
+ return {
40
+ code: result.status ?? 1,
41
+ stdout: result.stdout ?? "",
42
+ stderr: result.stderr ?? result.error?.message ?? "",
43
+ killed: result.signal !== null,
44
+ };
45
+ };
46
+
47
+ /** Synchronous probe so `describe.skipIf` can decide at collection time. */
48
+ function localGraftVersion(): string | undefined {
49
+ try {
50
+ const output = execFileSync("graft", ["--version"], { encoding: "utf-8", timeout: 5_000 });
51
+ return parseVersion(output);
52
+ } catch {
53
+ return undefined;
54
+ }
55
+ }
56
+
57
+ const localVersion = localGraftVersion();
58
+ const hasCompatibleGraft = localVersion !== undefined && isCompatibleVersion(localVersion);
59
+
60
+ describe("graft CLI compatibility", () => {
61
+ it("decides from the CLI's own version output and never claims an unverified version", async () => {
62
+ const probe = await probeGraft(realExec, process.cwd());
63
+ if (probe.kind === "ok") {
64
+ expect(probe.version).toBeDefined();
65
+ expect(isCompatibleVersion(probe.version)).toBe(true);
66
+ } else if (probe.kind === "incompatible") {
67
+ expect(probe.version === undefined || !isCompatibleVersion(probe.version)).toBe(true);
68
+ } else {
69
+ expect(localVersion).toBeUndefined();
70
+ }
71
+ });
72
+ });
73
+
74
+ describe.skipIf(!hasCompatibleGraft)("real Graft on a real Git working tree", () => {
75
+ let repo: string;
76
+ const run = (op: Parameters<typeof runGraft>[2]): Promise<GraftRun> => runGraft(realExec, repo, op);
77
+
78
+ beforeAll(() => {
79
+ repo = mkdtempSync(join(tmpdir(), "graft-integration-"));
80
+ mkdirSync(join(repo, "src"), { recursive: true });
81
+ writeFileSync(
82
+ join(repo, "src", "auth.ts"),
83
+ [
84
+ "export function verifyToken(token: string): boolean {",
85
+ " return token.length > 0;",
86
+ "}",
87
+ "",
88
+ ].join("\n"),
89
+ "utf-8",
90
+ );
91
+ writeFileSync(
92
+ join(repo, "src", "login.ts"),
93
+ ['import { verifyToken } from "./auth.js";', "", "export function login(token: string) {", " return verifyToken(token);", "}", ""].join(
94
+ "\n",
95
+ ),
96
+ "utf-8",
97
+ );
98
+ writeFileSync(join(repo, "package.json"), JSON.stringify({ name: "graft-fixture", type: "module" }), "utf-8");
99
+ execFileSync("git", ["init", "-q"], { cwd: repo });
100
+ execFileSync("git", ["add", "-A"], { cwd: repo });
101
+ execFileSync(
102
+ "git",
103
+ ["-c", "user.email=test@example.com", "-c", "user.name=Fixture", "commit", "-qm", "init"],
104
+ { cwd: repo },
105
+ );
106
+ });
107
+
108
+ afterAll(() => {
109
+ if (repo) rmSync(repo, { recursive: true, force: true });
110
+ });
111
+
112
+ it("builds a structural graph with no model and no key", async () => {
113
+ const build = await run({ kind: "build", deep: false });
114
+ expect(build.stderr + build.stdout).not.toContain("✗");
115
+ expect(build.code).toBe(0);
116
+ expect(existsSync(join(repo, "graft", ".graph", "wiring.json"))).toBe(true);
117
+ }, 300_000);
118
+
119
+ it("reports drift after an uncommitted edit, then answers from the working tree", async () => {
120
+ const inSync = await run({ kind: "check-freshness" });
121
+ expect(inSync.code).toBe(0);
122
+
123
+ // Uncommitted, unstaged: exactly what a Graft query is supposed to see.
124
+ writeFileSync(
125
+ join(repo, "src", "auth.ts"),
126
+ [
127
+ "export function verifyToken(token: string): boolean {",
128
+ " return token.length > 0;",
129
+ "}",
130
+ "",
131
+ "export function rotateSessionKey(key: string): string {",
132
+ ' return key.split("").reverse().join("");',
133
+ "}",
134
+ "",
135
+ ].join("\n"),
136
+ "utf-8",
137
+ );
138
+
139
+ const drifted = await run({ kind: "check-freshness" });
140
+ expect(drifted.code).not.toBe(0);
141
+
142
+ const answer = await run({ kind: "find-code", question: "rotateSessionKey", limit: 5 });
143
+ expect(answer.code).toBe(0);
144
+ expect(answer.stdout).toContain("src/auth.ts");
145
+ expect(answer.stdout).toContain("rotateSessionKey");
146
+ }, 300_000);
147
+
148
+ it("traces who depends on a symbol without a model", async () => {
149
+ const trace = await run({ kind: "trace-calls", symbol: "verifyToken", direction: "in" });
150
+ expect(trace.code).toBe(0);
151
+ expect(trace.stdout).toContain("src/login.ts");
152
+ }, 120_000);
153
+ });