@selesai/code 0.13.22 → 0.13.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.
@@ -0,0 +1,392 @@
1
+ /**
2
+ * The `/graft` command surface: setup, build, deep, refresh, status, doctor, mode.
3
+ *
4
+ * Command handlers own reporting and the explicit deep-build confirmation.
5
+ * They say exactly which repository files may change; with no dialog UI a
6
+ * manual deep build refuses and prints the exact command instead.
7
+ *
8
+ * Nothing here ever invokes Graft's upstream agent-wiring command. Selesai's
9
+ * setup installs a missing CLI automatically, explains local effects, and builds
10
+ * a graph — it does not edit Claude, Codex, Cursor, Gemini, Copilot, or any
11
+ * other agent's configuration.
12
+ */
13
+
14
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@selesai/code";
15
+ import {
16
+ GRAFT_PACKAGE,
17
+ GRAPH_DIR_NAME,
18
+ INSTALL_COMMAND,
19
+ graftFailureMessage,
20
+ MAX_GRAFT_VERSION_EXCLUSIVE,
21
+ MIN_GRAFT_VERSION,
22
+ TELEMETRY_ENV,
23
+ type GraftInstallRun,
24
+ type GraftRun,
25
+ } from "./cli.ts";
26
+ import { describeStrategy } from "./prompt.ts";
27
+ import {
28
+ DEFAULT_MAX_INJECTION_BYTES,
29
+ DEFAULT_MIN_COVERAGE,
30
+ RETRIEVAL_MODES,
31
+ type RetrievalMode,
32
+ type GraftSettings,
33
+ type GraftState,
34
+ MODE_ENTRY_TYPE,
35
+ recoveryFor,
36
+ type GraphPresence,
37
+ statusDetail,
38
+ statusText,
39
+ } from "./state.ts";
40
+
41
+ /**
42
+ * Structural shape of a slash-command argument completion. Declared locally so
43
+ * this module depends only on `@selesai/code` at runtime.
44
+ */
45
+ export interface ArgumentCompletion {
46
+ value: string;
47
+ label: string;
48
+ description?: string;
49
+ }
50
+
51
+ /** Everything the command surface needs from the extension runtime. */
52
+ export interface GraftCommandRuntime {
53
+ /** Re-resolve repository root, executable, and graph presence. */
54
+ recheck(ctx: ExtensionContext): Promise<void>;
55
+ state(): GraftState;
56
+ settings(): GraftSettings;
57
+ mode(): RetrievalMode;
58
+ setMode(mode: RetrievalMode, ctx: ExtensionContext): void;
59
+ repoRoot(): string | undefined;
60
+ graphPresence(): GraphPresence;
61
+ /** Install the compatible CLI when probing found none. */
62
+ installCli(ctx: ExtensionContext): Promise<GraftInstallRun>;
63
+ /** Structural or deep build. Deep calls only after consent. */
64
+ runBuild(deep: boolean, ctx: ExtensionContext): Promise<GraftRun>;
65
+ /** Proactive refresh of an existing graph; undefined when there is nothing to refresh. */
66
+ runRefresh(ctx: ExtensionContext): Promise<GraftRun | undefined>;
67
+ /** Names (never values) of Graft provider environment variables already set. */
68
+ providerEnvNames(): string[];
69
+ /** Whether this session actually set the telemetry opt-out default itself. */
70
+ telemetryDefaultApplied(): boolean;
71
+ }
72
+
73
+ export const DOCTOR_USAGE = `/graft <command>
74
+
75
+ status current availability, graph state, and retrieval mode
76
+ doctor full health report with a recovery step for each problem
77
+ setup install the CLI if needed, then build the deep graph with the active model
78
+ build [--structural] build graft/ (deep by default; structural stays local)
79
+ deep provider-backed build: adds summaries and per-symbol crux
80
+ refresh re-index an existing graph after edits
81
+ mode [pull|push|hybrid] show or set the retrieval strategy
82
+ help this message`;
83
+
84
+ /** Provider environment variables Graft reads. Values are never read or shown. */
85
+ export const PROVIDER_ENV_NAMES = [
86
+ "GRAFT_PROVIDER",
87
+ "GRAFT_MODEL",
88
+ "GRAFT_BASE_URL",
89
+ "GRAFT_API_KEY",
90
+ "OPENROUTER_API_KEY",
91
+ "ORCAROUTER_API_KEY",
92
+ ] as const;
93
+
94
+ /** Console fallback so print/JSON modes still get actionable diagnostics. */
95
+ function report(ctx: ExtensionContext, message: string, type?: "info" | "warning" | "error"): void {
96
+ if (ctx.hasUI) {
97
+ ctx.ui.notify(message, type);
98
+ return;
99
+ }
100
+ console.warn(`[graft] ${message}`);
101
+ }
102
+
103
+ export interface DoctorInput {
104
+ repoRoot: string | undefined;
105
+ gitRepo: boolean;
106
+ state: GraftState;
107
+ presence: GraphPresence;
108
+ mode: RetrievalMode;
109
+ settings: GraftSettings;
110
+ /** True when the extension set DO_NOT_TRACK itself for this session. */
111
+ telemetryDefault: boolean;
112
+ providerEnv: string[];
113
+ }
114
+
115
+ /** Pure health report. Exported so the wording can be asserted directly. */
116
+ export function formatDoctor(input: DoctorInput): string {
117
+ const lines: string[] = ["Graft doctor", ""];
118
+ const row = (label: string, value: string) => lines.push(` ${label.padEnd(12)} ${value}`);
119
+
120
+ row("repository", input.repoRoot ? `${input.repoRoot}${input.gitRepo ? " (git ✓)" : " (not a git repository)"}` : "unresolved");
121
+ row(
122
+ "executable",
123
+ input.state.name === "unavailable"
124
+ ? `not found on PATH — ${statusDetail(input.state)}`
125
+ : input.state.version
126
+ ? `graft ${input.state.version} (supported: >=${MIN_GRAFT_VERSION} <${MAX_GRAFT_VERSION_EXCLUSIVE})`
127
+ : statusDetail(input.state),
128
+ );
129
+ row(
130
+ "graph",
131
+ input.presence.built
132
+ ? `${GRAPH_DIR_NAME}/ present — ${input.presence.deep ? "structural + deep summaries" : "structural only"}`
133
+ : `no ${GRAPH_DIR_NAME}/ here`,
134
+ );
135
+ row("state", `${input.state.name} — ${statusDetail(input.state)}`);
136
+ row("mode", describeStrategy(input.mode));
137
+ row(
138
+ "telemetry",
139
+ input.telemetryDefault
140
+ ? `${TELEMETRY_ENV}=1 set by this extension for Graft processes (your Graft settings are untouched)`
141
+ : `${TELEMETRY_ENV} left to your environment`,
142
+ );
143
+ row("injection", `min coverage ${input.settings.minCoverage ?? DEFAULT_MIN_COVERAGE}, max ${input.settings.maxInjectionBytes ?? DEFAULT_MAX_INJECTION_BYTES} bytes`);
144
+ row(
145
+ "provider env",
146
+ input.providerEnv.length > 0
147
+ ? `${input.providerEnv.join(", ")} set (values never shown) — manual Graft CLI configuration is available`
148
+ : `none of ${PROVIDER_ENV_NAMES.join(", ")} set — automatic deep builds use Selesai's active compatible model`,
149
+ );
150
+
151
+ const recovery = recoveryFor(input.state);
152
+ if (recovery) {
153
+ lines.push("", ` next step ${recovery}`);
154
+ } else if (input.state.name === "fresh-deep") {
155
+ lines.push("", " next step nothing to fix; deep summaries are active for this graph.");
156
+ } else if (!input.presence.deep) {
157
+ lines.push(
158
+ "",
159
+ " next step structural context is active. /graft deep adds provider-backed summaries if you accept that source-derived work leaves the machine.",
160
+ );
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+
165
+ export function formatStatus(input: DoctorInput): string {
166
+ const lines = [
167
+ statusText(input.state),
168
+ ` ${describeStrategy(input.mode)}`,
169
+ ` graph: ${input.presence.built ? (input.presence.deep ? "structural + deep" : "structural only") : "not built"}`,
170
+ ];
171
+ if (input.repoRoot) lines.push(` root: ${input.repoRoot}`);
172
+ return lines.join("\n");
173
+ }
174
+
175
+ /**
176
+ * What a build writes. Shown verbatim before consent so the user is agreeing to
177
+ * a concrete list, not to "some files".
178
+ */
179
+ export function buildEffects(deep: boolean): string {
180
+ const effects = [
181
+ ` • creates/updates ./${GRAPH_DIR_NAME}/ (markdown nodes, wiring graph, per-file cards)`,
182
+ " • adds graft/ to .gitignore and writes .ignore for ripgrep re-admit",
183
+ " • updates .graft/config.json with this repository's build choices",
184
+ ];
185
+ if (deep) {
186
+ effects.push(
187
+ " • sends source-derived file summaries and symbol cruxes through Selesai's active compatible model; credentials are passed only to the Graft child process",
188
+ " • caches those summaries under graft/ so later deep builds are incremental",
189
+ );
190
+ }
191
+ return effects.join("\n");
192
+ }
193
+
194
+ /** Consent-gated mutation step. Returns false when the user declined or has no UI. */
195
+ async function confirmMutation(
196
+ ctx: ExtensionCommandContext,
197
+ title: string,
198
+ body: string,
199
+ manualCommand: string,
200
+ ): Promise<boolean> {
201
+ if (!ctx.hasUI) {
202
+ report(
203
+ ctx,
204
+ `this command changes the repository, so it needs an interactive confirmation. Run it yourself: ${manualCommand}`,
205
+ "warning",
206
+ );
207
+ return false;
208
+ }
209
+ const accepted = await ctx.ui.confirm(
210
+ title,
211
+ `${body}\n\nGraft's own \`init\` command is never run: this extension does not write configuration for other coding agents.\n\nProceed?`,
212
+ );
213
+ if (!accepted) report(ctx, `Cancelled. Nothing was written. To run it yourself: ${manualCommand}`, "info");
214
+ return accepted;
215
+ }
216
+
217
+ /** Install only when probing proved the CLI is absent; never overwrite an incompatible user install. */
218
+ async function ensureCli(
219
+ ctx: ExtensionCommandContext,
220
+ runtime: GraftCommandRuntime,
221
+ ): Promise<boolean> {
222
+ if (runtime.state().name !== "unavailable") return true;
223
+ report(ctx, "Installing Graft CLI…", "info");
224
+ const install = await runtime.installCli(ctx);
225
+ if (install.code !== 0 || install.killed) {
226
+ const detail = (install.stderr.trim() || install.stdout.trim() || `exit status ${install.code}`).split("\n")[0];
227
+ report(ctx, `Graft CLI install failed: ${detail}\nInstall it yourself: ${INSTALL_COMMAND}`, "error");
228
+ return false;
229
+ }
230
+
231
+ await runtime.recheck(ctx);
232
+ if (runtime.state().name === "unavailable" || runtime.state().name === "incompatible") {
233
+ report(
234
+ ctx,
235
+ `npm reported success, but Graft is still not usable: ${statusDetail(runtime.state())}. Restart Selesai or install it yourself: ${INSTALL_COMMAND}`,
236
+ "warning",
237
+ );
238
+ return false;
239
+ }
240
+ return true;
241
+ }
242
+
243
+ export function registerGraftCommands(pi: ExtensionAPI, runtime: GraftCommandRuntime): void {
244
+ const doctorInput = (): DoctorInput => ({
245
+ repoRoot: runtime.repoRoot(),
246
+ gitRepo: runtime.repoRoot() !== undefined,
247
+ state: runtime.state(),
248
+ presence: runtime.graphPresence(),
249
+ mode: runtime.mode(),
250
+ settings: runtime.settings(),
251
+ telemetryDefault: runtime.telemetryDefaultApplied(),
252
+ providerEnv: runtime.providerEnvNames(),
253
+ });
254
+
255
+ pi.registerCommand("graft", {
256
+ description: "Graft codebase-context: status, doctor, setup, build, deep, refresh, mode",
257
+ getArgumentCompletions: (prefix: string): ArgumentCompletion[] | null => {
258
+ const items: ArgumentCompletion[] = [
259
+ { value: "status", label: "status", description: "availability, graph state, retrieval mode" },
260
+ { value: "doctor", label: "doctor", description: "full health report and recovery steps" },
261
+ { value: "setup", label: "setup", description: "install CLI if needed, then build the deep graph" },
262
+ { value: "build", label: "build", description: "build graft/ with the active model (default deep)" },
263
+ { value: "build --structural", label: "build --structural", description: "build graft/ locally without model enrichment" },
264
+ { value: "deep", label: "deep", description: "provider-backed build with the active model" },
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("--structural");
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 summarizes each changed file and extracts per-symbol cruxes using Selesai's active compatible model, so source-derived content leaves this machine. Its credential is passed only to this Graft child process.\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
+ `Select a compatible active Selesai model (TokenIn, LiteLLM, OpenAI, Anthropic, or OrcaRouter), then re-run /graft deep. ` +
392
+ `${GRAFT_PACKAGE} receives its credential only in that child process.`;
@@ -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
+ });