@selesai/code 0.13.21 → 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.
- package/CHANGELOG.md +18 -0
- package/dist/extensions/capability-gateway/index.ts +28 -29
- package/dist/extensions/capability-gateway/integration.test.ts +43 -6
- package/dist/extensions/capability-gateway/package.json +1 -0
- package/dist/extensions/package.json +2 -0
- package/dist/extensions/pi-graft/cli.test.ts +408 -0
- package/dist/extensions/pi-graft/cli.ts +547 -0
- package/dist/extensions/pi-graft/commands.test.ts +478 -0
- package/dist/extensions/pi-graft/commands.ts +392 -0
- package/dist/extensions/pi-graft/graft.integration.test.ts +153 -0
- package/dist/extensions/pi-graft/index.test.ts +167 -0
- package/dist/extensions/pi-graft/index.ts +521 -0
- package/dist/extensions/pi-graft/lifecycle.test.ts +609 -0
- package/dist/extensions/pi-graft/prompt.test.ts +213 -0
- package/dist/extensions/pi-graft/prompt.ts +216 -0
- package/dist/extensions/pi-graft/state.test.ts +258 -0
- package/dist/extensions/pi-graft/state.ts +335 -0
- package/dist/extensions/pi-graft/test-support.ts +199 -0
- package/dist/extensions/pi-graft/tools.test.ts +330 -0
- package/dist/extensions/pi-graft/tools.ts +333 -0
- package/dist/extensions/pi-graft/tsconfig.json +16 -0
- package/dist/extensions/pi-subagents/src/agents/builtin-agent-augmentations.ts +71 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +9 -3
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-augmentations.test.ts +44 -0
- package/dist/extensions/tps.test.ts +9 -1
- package/dist/extensions/tps.ts +9 -3
- package/package.json +2 -2
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { GraftInstallRun, GraftRun } from "./cli.ts";
|
|
3
|
+
import {
|
|
4
|
+
buildEffects,
|
|
5
|
+
DEEP_PROVIDER_HINT,
|
|
6
|
+
DOCTOR_USAGE,
|
|
7
|
+
formatDoctor,
|
|
8
|
+
formatStatus,
|
|
9
|
+
PROVIDER_ENV_NAMES,
|
|
10
|
+
registerGraftCommands,
|
|
11
|
+
type ArgumentCompletion,
|
|
12
|
+
type DoctorInput,
|
|
13
|
+
type GraftCommandRuntime,
|
|
14
|
+
} from "./commands.ts";
|
|
15
|
+
import { MODE_ENTRY_TYPE, type GraftState } from "./state.ts";
|
|
16
|
+
import { notified, makeCtx, makePi, type CtxStub } from "./test-support.ts";
|
|
17
|
+
|
|
18
|
+
const HEALTHY: GraftState = { name: "fresh-structural", version: "0.18.0" };
|
|
19
|
+
|
|
20
|
+
function installOf(overrides: Partial<GraftInstallRun> = {}): GraftInstallRun {
|
|
21
|
+
return {
|
|
22
|
+
argv: ["npm", "install", "-g", "@nanonets/graft@^0.18"],
|
|
23
|
+
cwd: "/repo",
|
|
24
|
+
code: 0,
|
|
25
|
+
stdout: "",
|
|
26
|
+
stderr: "",
|
|
27
|
+
killed: false,
|
|
28
|
+
cancelled: false,
|
|
29
|
+
timedOut: false,
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runOf(overrides: Partial<GraftRun> = {}): GraftRun {
|
|
35
|
+
return {
|
|
36
|
+
op: { kind: "build", deep: false },
|
|
37
|
+
argv: ["graft", "build"],
|
|
38
|
+
cwd: "/repo",
|
|
39
|
+
code: 0,
|
|
40
|
+
stdout: "",
|
|
41
|
+
stderr: "",
|
|
42
|
+
killed: false,
|
|
43
|
+
cancelled: false,
|
|
44
|
+
timedOut: false,
|
|
45
|
+
...overrides,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface RuntimeHarness {
|
|
50
|
+
runtime: GraftCommandRuntime;
|
|
51
|
+
recheck: ReturnType<typeof vi.fn>;
|
|
52
|
+
installCli: ReturnType<typeof vi.fn>;
|
|
53
|
+
runBuild: ReturnType<typeof vi.fn>;
|
|
54
|
+
runRefresh: ReturnType<typeof vi.fn>;
|
|
55
|
+
setMode: ReturnType<typeof vi.fn>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeRuntime(
|
|
59
|
+
overrides: Partial<{
|
|
60
|
+
state: GraftState;
|
|
61
|
+
postInstallState: GraftState;
|
|
62
|
+
built: boolean;
|
|
63
|
+
deep: boolean;
|
|
64
|
+
mode: "pull" | "push" | "hybrid";
|
|
65
|
+
settings: Record<string, unknown>;
|
|
66
|
+
installResult: GraftInstallRun;
|
|
67
|
+
buildResult: GraftRun;
|
|
68
|
+
refreshResult: GraftRun | undefined;
|
|
69
|
+
repoRoot: string | undefined;
|
|
70
|
+
providerEnvNames: string[];
|
|
71
|
+
telemetryDefaultApplied: boolean;
|
|
72
|
+
}> = {},
|
|
73
|
+
): RuntimeHarness {
|
|
74
|
+
let state = overrides.state ?? HEALTHY;
|
|
75
|
+
const recheck = vi.fn(async () => {});
|
|
76
|
+
const installCli = vi.fn(async () => {
|
|
77
|
+
const result = overrides.installResult ?? installOf();
|
|
78
|
+
if (result.code === 0 && !result.killed) state = overrides.postInstallState ?? { name: "unbuilt", version: "0.18.0" };
|
|
79
|
+
return result;
|
|
80
|
+
});
|
|
81
|
+
const runBuild = vi.fn(async () => overrides.buildResult ?? runOf());
|
|
82
|
+
const runRefresh = vi.fn(async () => ("refreshResult" in overrides ? overrides.refreshResult : runOf()));
|
|
83
|
+
const setMode = vi.fn();
|
|
84
|
+
const presence = { built: overrides.built ?? true, deep: overrides.deep ?? false };
|
|
85
|
+
return {
|
|
86
|
+
recheck,
|
|
87
|
+
installCli,
|
|
88
|
+
runBuild,
|
|
89
|
+
runRefresh,
|
|
90
|
+
setMode,
|
|
91
|
+
runtime: {
|
|
92
|
+
recheck,
|
|
93
|
+
state: () => state,
|
|
94
|
+
settings: () => overrides.settings ?? {},
|
|
95
|
+
mode: () => overrides.mode ?? "pull",
|
|
96
|
+
setMode,
|
|
97
|
+
repoRoot: () => ("repoRoot" in overrides ? overrides.repoRoot : "/repo"),
|
|
98
|
+
graphPresence: () => presence,
|
|
99
|
+
installCli: installCli as never,
|
|
100
|
+
runBuild: runBuild as never,
|
|
101
|
+
runRefresh: runRefresh as never,
|
|
102
|
+
providerEnvNames: () => overrides.providerEnvNames ?? [],
|
|
103
|
+
telemetryDefaultApplied: () => overrides.telemetryDefaultApplied ?? true,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function invoke(
|
|
109
|
+
runtime: GraftCommandRuntime,
|
|
110
|
+
args: string,
|
|
111
|
+
ctx: CtxStub,
|
|
112
|
+
): Promise<{ pi: ReturnType<typeof makePi>["pi"]; entries: { customType: string; data: unknown }[] }> {
|
|
113
|
+
const { pi, commands, entries } = makePi();
|
|
114
|
+
registerGraftCommands(pi as never, runtime);
|
|
115
|
+
const handler = commands.get("graft")!.handler as (args: string, ctx: unknown) => Promise<void>;
|
|
116
|
+
await handler(args, ctx);
|
|
117
|
+
return { pi, entries };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
afterEach(() => vi.restoreAllMocks());
|
|
121
|
+
|
|
122
|
+
describe("formatDoctor", () => {
|
|
123
|
+
const base: DoctorInput = {
|
|
124
|
+
repoRoot: "/repo",
|
|
125
|
+
gitRepo: true,
|
|
126
|
+
state: HEALTHY,
|
|
127
|
+
presence: { built: true, deep: false },
|
|
128
|
+
mode: "pull",
|
|
129
|
+
settings: {},
|
|
130
|
+
telemetryDefault: true,
|
|
131
|
+
providerEnv: [],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
it("reports repository, executable, graph, state, mode, telemetry and provider boundary", () => {
|
|
135
|
+
const text = formatDoctor(base);
|
|
136
|
+
expect(text).toContain("/repo (git ✓)");
|
|
137
|
+
expect(text).toContain("graft 0.18.0");
|
|
138
|
+
expect(text).toContain("structural only");
|
|
139
|
+
expect(text).toContain("fresh-structural");
|
|
140
|
+
expect(text).toContain("nothing is injected");
|
|
141
|
+
expect(text).toContain("DO_NOT_TRACK=1 set by this extension");
|
|
142
|
+
expect(text).toContain("deep builds would need Graft's own provider configuration");
|
|
143
|
+
expect(text).toContain("graft deep adds provider-backed summaries");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("shows only which provider variables are set, never their values", () => {
|
|
147
|
+
const text = formatDoctor({ ...base, providerEnv: ["GRAFT_PROVIDER", "GRAFT_MODEL"] });
|
|
148
|
+
expect(text).toContain("GRAFT_PROVIDER, GRAFT_MODEL set");
|
|
149
|
+
expect(text).toContain("values never read or copied");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("reports whether this session actually applied the telemetry default", () => {
|
|
153
|
+
expect(formatDoctor({ ...base, telemetryDefault: false })).toContain("left to your environment");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("gives a recovery step for each broken state", () => {
|
|
157
|
+
expect(formatDoctor({ ...base, state: { name: "unavailable" }, presence: { built: false, deep: false } })).toContain(
|
|
158
|
+
"npm install -g @nanonets/graft",
|
|
159
|
+
);
|
|
160
|
+
expect(formatDoctor({ ...base, state: { name: "unbuilt" }, presence: { built: false, deep: false } })).toContain(
|
|
161
|
+
"/graft build",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("says plainly when there is nothing to fix", () => {
|
|
166
|
+
expect(formatDoctor({ ...base, state: { name: "fresh-deep" }, presence: { built: true, deep: true } })).toContain(
|
|
167
|
+
"nothing to fix",
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("describes an untrusted or ungit directory honestly", () => {
|
|
172
|
+
const text = formatDoctor({ ...base, repoRoot: undefined, gitRepo: false, state: { name: "unsupported" } });
|
|
173
|
+
expect(text).toContain("unresolved");
|
|
174
|
+
expect(text).toContain("git init");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("formatStatus", () => {
|
|
179
|
+
it("reports the state, strategy and graph quality on three lines", () => {
|
|
180
|
+
const text = formatStatus({
|
|
181
|
+
repoRoot: "/repo",
|
|
182
|
+
gitRepo: true,
|
|
183
|
+
state: HEALTHY,
|
|
184
|
+
presence: { built: true, deep: false },
|
|
185
|
+
mode: "hybrid",
|
|
186
|
+
settings: {},
|
|
187
|
+
telemetryDefault: true,
|
|
188
|
+
providerEnv: [],
|
|
189
|
+
});
|
|
190
|
+
expect(text.split("\n")).toHaveLength(4);
|
|
191
|
+
expect(text).toContain("graft: ● structural v0.18.0");
|
|
192
|
+
expect(text).toContain("hybrid");
|
|
193
|
+
expect(text).toContain("root: /repo");
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("buildEffects", () => {
|
|
198
|
+
it("names every local file a structural build can touch and never the agent-wiring command", () => {
|
|
199
|
+
const text = buildEffects(false);
|
|
200
|
+
expect(text).toContain("./graft/");
|
|
201
|
+
expect(text).toContain(".gitignore");
|
|
202
|
+
expect(text).toContain(".graft/config.json");
|
|
203
|
+
expect(text).not.toContain("GRAFT_API_KEY");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("states the provider boundary and the cache for a deep build", () => {
|
|
207
|
+
const text = buildEffects(true);
|
|
208
|
+
expect(text).toContain("GRAFT_PROVIDER");
|
|
209
|
+
expect(text).toContain("Graft's own environment");
|
|
210
|
+
expect(text).toContain("caches those summaries under graft/");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("the /graft command surface", () => {
|
|
215
|
+
it("registers one command with argument completions covering every subcommand and mode", () => {
|
|
216
|
+
const { pi, commands } = makePi();
|
|
217
|
+
registerGraftCommands(pi as never, makeRuntime().runtime);
|
|
218
|
+
const command = commands.get("graft")!;
|
|
219
|
+
expect(command).toBeDefined();
|
|
220
|
+
const completer = command.getArgumentCompletions as (prefix: string) => ArgumentCompletion[];
|
|
221
|
+
const values = completer("").map((item) => item.value);
|
|
222
|
+
for (const sub of ["status", "doctor", "setup", "build", "deep", "refresh", "mode", "help"]) {
|
|
223
|
+
expect(values).toContain(sub);
|
|
224
|
+
}
|
|
225
|
+
expect(values).toContain("mode hybrid");
|
|
226
|
+
expect(completer("d").map((item) => item.value)).toEqual(["doctor", "deep"]);
|
|
227
|
+
expect(completer("zzz")).toBeNull();
|
|
228
|
+
expect(pi).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("defaults to status and re-resolves the environment first", async () => {
|
|
232
|
+
const harness = makeRuntime();
|
|
233
|
+
const ctx = makeCtx();
|
|
234
|
+
await invoke(harness.runtime, "", ctx);
|
|
235
|
+
expect(harness.recheck).toHaveBeenCalledTimes(1);
|
|
236
|
+
expect(notified(ctx)).toContain("graft: ● structural");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("prints usage for help and rejects an unknown subcommand without side effects", async () => {
|
|
240
|
+
const harness = makeRuntime();
|
|
241
|
+
const help = makeCtx();
|
|
242
|
+
await invoke(harness.runtime, "help", help);
|
|
243
|
+
expect(notified(help)).toContain("status current availability");
|
|
244
|
+
|
|
245
|
+
const unknown = makeCtx();
|
|
246
|
+
await invoke(harness.runtime, "frobnicate", unknown);
|
|
247
|
+
expect(notified(unknown)).toContain('Unknown command "frobnicate"');
|
|
248
|
+
expect(harness.runBuild).not.toHaveBeenCalled();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("/graft doctor", () => {
|
|
253
|
+
it("reports health and surfaces the recovery step a second time", async () => {
|
|
254
|
+
const harness = makeRuntime({ state: { name: "unavailable", detail: "not found" }, built: false });
|
|
255
|
+
const ctx = makeCtx();
|
|
256
|
+
await invoke(harness.runtime, "doctor", ctx);
|
|
257
|
+
expect(notified(ctx)).toContain("Graft doctor");
|
|
258
|
+
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("npm install -g @nanonets/graft"), "warning");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("does not warn when the graph is healthy, and points at the deep build instead", async () => {
|
|
262
|
+
const harness = makeRuntime();
|
|
263
|
+
const ctx = makeCtx();
|
|
264
|
+
await invoke(harness.runtime, "doctor", ctx);
|
|
265
|
+
expect(notified(ctx)).toContain("structural context is active");
|
|
266
|
+
expect(ctx.ui.notify).toHaveBeenCalledTimes(1);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe("mutation-capable commands", () => {
|
|
271
|
+
it("refuses to build without a dialog UI and prints the exact manual command", async () => {
|
|
272
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
273
|
+
for (const subcommand of ["build", "setup", "deep"]) {
|
|
274
|
+
const harness = makeRuntime();
|
|
275
|
+
const ctx = makeCtx({ hasUI: false });
|
|
276
|
+
await invoke(harness.runtime, subcommand, ctx);
|
|
277
|
+
expect(ctx.ui.confirm, subcommand).not.toHaveBeenCalled();
|
|
278
|
+
expect(harness.runBuild, subcommand).not.toHaveBeenCalled();
|
|
279
|
+
expect(warn.mock.calls.flat().join("\n"), subcommand).toContain("graft build");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
{
|
|
283
|
+
// The deep variant is covered too, so no path reaches the provider
|
|
284
|
+
// without an interactive confirmation.
|
|
285
|
+
const harness = makeRuntime();
|
|
286
|
+
await invoke(harness.runtime, "build --deep", makeCtx({ hasUI: false }));
|
|
287
|
+
expect(harness.runBuild).not.toHaveBeenCalled();
|
|
288
|
+
expect(warn.mock.calls.flat().join("\n")).toContain("graft build --deep");
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("installs a missing CLI, rechecks it, then asks only before building", async () => {
|
|
293
|
+
const harness = makeRuntime({ state: { name: "unavailable", detail: "ENOENT" }, built: false });
|
|
294
|
+
const ctx = makeCtx();
|
|
295
|
+
await invoke(harness.runtime, "setup", ctx);
|
|
296
|
+
|
|
297
|
+
expect(harness.installCli).toHaveBeenCalledWith(ctx);
|
|
298
|
+
expect(harness.recheck).toHaveBeenCalledTimes(3);
|
|
299
|
+
expect(ctx.ui.confirm).toHaveBeenCalledTimes(1);
|
|
300
|
+
expect(ctx.ui.confirm.mock.calls[0]![0]).toBe("Set up Graft for this repository?");
|
|
301
|
+
expect(harness.runBuild).toHaveBeenCalledWith(false, ctx);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("does not build when installing the missing CLI fails", async () => {
|
|
305
|
+
const harness = makeRuntime({
|
|
306
|
+
state: { name: "unavailable", detail: "ENOENT" },
|
|
307
|
+
installResult: installOf({ code: 1, stderr: "npm access denied" }),
|
|
308
|
+
});
|
|
309
|
+
const ctx = makeCtx();
|
|
310
|
+
await invoke(harness.runtime, "build", ctx);
|
|
311
|
+
|
|
312
|
+
expect(harness.installCli).toHaveBeenCalledWith(ctx);
|
|
313
|
+
expect(harness.runBuild).not.toHaveBeenCalled();
|
|
314
|
+
expect(notified(ctx)).toContain("npm access denied");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("installs without a UI but still refuses the repository build", async () => {
|
|
318
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
319
|
+
const harness = makeRuntime({ state: { name: "unavailable", detail: "ENOENT" } });
|
|
320
|
+
const ctx = makeCtx({ hasUI: false });
|
|
321
|
+
await invoke(harness.runtime, "setup", ctx);
|
|
322
|
+
|
|
323
|
+
expect(ctx.ui.confirm).not.toHaveBeenCalled();
|
|
324
|
+
expect(harness.installCli).toHaveBeenCalledWith(ctx);
|
|
325
|
+
expect(harness.runBuild).not.toHaveBeenCalled();
|
|
326
|
+
expect(warn.mock.calls.flat().join("\n")).toContain("this command changes the repository");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("does nothing when the user declines", async () => {
|
|
330
|
+
const harness = makeRuntime();
|
|
331
|
+
const ctx = makeCtx({ confirm: false });
|
|
332
|
+
await invoke(harness.runtime, "build", ctx);
|
|
333
|
+
expect(harness.runBuild).not.toHaveBeenCalled();
|
|
334
|
+
expect(notified(ctx)).toContain("Cancelled. Nothing was written.");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("explains the local effects, including that Graft's own init is never run, then builds", async () => {
|
|
338
|
+
const harness = makeRuntime();
|
|
339
|
+
const ctx = makeCtx();
|
|
340
|
+
await invoke(harness.runtime, "build", ctx);
|
|
341
|
+
|
|
342
|
+
expect(ctx.ui.confirm).toHaveBeenCalledTimes(1);
|
|
343
|
+
const [, message] = ctx.ui.confirm.mock.calls[0] as [string, string];
|
|
344
|
+
expect(message).toContain("./graft/");
|
|
345
|
+
expect(message).toContain(".gitignore");
|
|
346
|
+
expect(message).toContain("init");
|
|
347
|
+
expect(message).toContain("never run");
|
|
348
|
+
expect(harness.runBuild).toHaveBeenCalledWith(false, ctx);
|
|
349
|
+
expect(notified(ctx)).toContain("Done.");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("does not run a deep build when the structural build was confirmed", async () => {
|
|
353
|
+
const harness = makeRuntime();
|
|
354
|
+
await invoke(harness.runtime, "build", makeCtx());
|
|
355
|
+
expect(harness.runBuild).toHaveBeenCalledWith(false, expect.anything());
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("gives `build --deep` the same provider consent as `/graft deep`", async () => {
|
|
359
|
+
const harness = makeRuntime();
|
|
360
|
+
const viaBuild = makeCtx();
|
|
361
|
+
await invoke(harness.runtime, "build --deep", viaBuild);
|
|
362
|
+
const viaDeep = makeCtx();
|
|
363
|
+
await invoke(harness.runtime, "deep", viaDeep);
|
|
364
|
+
|
|
365
|
+
const [, buildMessage] = viaBuild.ui.confirm.mock.calls[0] as [string, string];
|
|
366
|
+
const [deepTitle, deepMessage] = viaDeep.ui.confirm.mock.calls[0] as [string, string];
|
|
367
|
+
expect(viaBuild.ui.confirm.mock.calls[0]![0]).toBe(deepTitle);
|
|
368
|
+
expect(buildMessage).toBe(deepMessage);
|
|
369
|
+
expect(buildMessage).toContain("leaves this machine");
|
|
370
|
+
expect(harness.runBuild).toHaveBeenNthCalledWith(1, true, viaBuild);
|
|
371
|
+
expect(harness.runBuild).toHaveBeenNthCalledWith(2, true, viaDeep);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("treats setup as the structural build", async () => {
|
|
375
|
+
const harness = makeRuntime();
|
|
376
|
+
await invoke(harness.runtime, "setup", makeCtx());
|
|
377
|
+
expect(harness.runBuild).toHaveBeenCalledWith(false, expect.anything());
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("shows the provider boundary before a deep build and only sends source off-machine after consent", async () => {
|
|
381
|
+
const harness = makeRuntime();
|
|
382
|
+
const ctx = makeCtx();
|
|
383
|
+
await invoke(harness.runtime, "deep", ctx);
|
|
384
|
+
|
|
385
|
+
const [title, message] = ctx.ui.confirm.mock.calls[0] as [string, string];
|
|
386
|
+
expect(title).toContain("provider-backed");
|
|
387
|
+
expect(message).toContain("leaves this machine");
|
|
388
|
+
expect(message).toContain("does not read, copy, or forward");
|
|
389
|
+
expect(harness.runBuild).toHaveBeenCalledWith(true, ctx);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("reports a degraded deep build as a warning with the command's own explanation", async () => {
|
|
393
|
+
const harness = makeRuntime({
|
|
394
|
+
buildResult: runOf({ code: 1, stderr: "✗ the deep pass did not complete\n meaning coverage: 3/10" }),
|
|
395
|
+
});
|
|
396
|
+
const ctx = makeCtx();
|
|
397
|
+
await invoke(harness.runtime, "deep", ctx);
|
|
398
|
+
expect(notified(ctx)).toContain("meaning coverage: 3/10");
|
|
399
|
+
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("failed"), "warning");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("reports a structural build failure as an error", async () => {
|
|
403
|
+
const harness = makeRuntime({
|
|
404
|
+
buildResult: runOf({ code: 1, stderr: "boom" }),
|
|
405
|
+
state: { name: "failed", detail: "boom" },
|
|
406
|
+
});
|
|
407
|
+
const ctx = makeCtx();
|
|
408
|
+
await invoke(harness.runtime, "build", ctx);
|
|
409
|
+
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("boom"), "error");
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("/graft refresh", () => {
|
|
414
|
+
it("never creates a graph that does not exist", async () => {
|
|
415
|
+
const harness = makeRuntime({ built: false });
|
|
416
|
+
const ctx = makeCtx();
|
|
417
|
+
await invoke(harness.runtime, "refresh", ctx);
|
|
418
|
+
expect(harness.runRefresh).not.toHaveBeenCalled();
|
|
419
|
+
expect(notified(ctx)).toContain("No graft/ here yet");
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("re-indexes an existing graph without another consent prompt", async () => {
|
|
423
|
+
const harness = makeRuntime();
|
|
424
|
+
const ctx = makeCtx();
|
|
425
|
+
await invoke(harness.runtime, "refresh", ctx);
|
|
426
|
+
expect(ctx.ui.confirm).not.toHaveBeenCalled();
|
|
427
|
+
expect(harness.runRefresh).toHaveBeenCalledTimes(1);
|
|
428
|
+
expect(notified(ctx)).toContain("Done.");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("says nothing needed refreshing when the runtime reports no work", async () => {
|
|
432
|
+
const harness = makeRuntime({ refreshResult: undefined });
|
|
433
|
+
const ctx = makeCtx();
|
|
434
|
+
await invoke(harness.runtime, "refresh", ctx);
|
|
435
|
+
expect(notified(ctx)).toContain("Nothing to refresh");
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe("/graft mode", () => {
|
|
440
|
+
it("shows the current strategy and the usage when asked without an argument", async () => {
|
|
441
|
+
const harness = makeRuntime({ mode: "hybrid" });
|
|
442
|
+
const ctx = makeCtx();
|
|
443
|
+
await invoke(harness.runtime, "mode", ctx);
|
|
444
|
+
expect(notified(ctx)).toContain("hybrid —");
|
|
445
|
+
expect(notified(ctx)).toContain("/graft <command>");
|
|
446
|
+
expect(harness.setMode).not.toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("sets the strategy and records it as a session entry so resume and fork keep it", async () => {
|
|
450
|
+
const harness = makeRuntime();
|
|
451
|
+
const ctx = makeCtx();
|
|
452
|
+
const { entries } = await invoke(harness.runtime, "mode push", ctx);
|
|
453
|
+
expect(harness.setMode).toHaveBeenCalledWith("push", ctx);
|
|
454
|
+
expect(entries).toContainEqual({ customType: MODE_ENTRY_TYPE, data: { mode: "push" } });
|
|
455
|
+
expect(notified(ctx)).toContain("push —");
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("rejects an unknown strategy without changing anything", async () => {
|
|
459
|
+
const harness = makeRuntime();
|
|
460
|
+
const ctx = makeCtx();
|
|
461
|
+
const { entries } = await invoke(harness.runtime, "mode turbo", ctx);
|
|
462
|
+
expect(harness.setMode).not.toHaveBeenCalled();
|
|
463
|
+
expect(entries).toHaveLength(0);
|
|
464
|
+
expect(notified(ctx)).toContain('Unknown mode "turbo"');
|
|
465
|
+
expect(notified(ctx)).toContain("pull, push, hybrid");
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe("runtime contract", () => {
|
|
470
|
+
it("asks the runtime for provider variable names but never for values", async () => {
|
|
471
|
+
const harness = makeRuntime({ providerEnvNames: ["GRAFT_API_KEY"] });
|
|
472
|
+
const ctx = makeCtx();
|
|
473
|
+
await invoke(harness.runtime, "status", ctx);
|
|
474
|
+
expect(PROVIDER_ENV_NAMES).toContain("GRAFT_API_KEY");
|
|
475
|
+
expect(DEEP_PROVIDER_HINT).toContain("never forwards Selesai credentials");
|
|
476
|
+
expect(DOCTOR_USAGE).toContain("mode [pull|push|hybrid]");
|
|
477
|
+
});
|
|
478
|
+
});
|