gentle-pi 2.7.0 → 3.0.0

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.
Files changed (122) hide show
  1. package/README.md +24 -6
  2. package/assets/agents/gentle-ai-worker.md +5 -1
  3. package/assets/agents/sdd-apply.md +9 -7
  4. package/assets/agents/sdd-archive.md +42 -23
  5. package/assets/agents/sdd-proposal.md +2 -2
  6. package/assets/agents/sdd-remediate.md +4 -4
  7. package/assets/agents/sdd-research.md +20 -48
  8. package/assets/agents/sdd-tasks.md +5 -5
  9. package/assets/agents/sdd-verify.md +6 -28
  10. package/assets/chains/sdd-full.chain.md +4 -22
  11. package/assets/chains/sdd-verify.chain.md +3 -12
  12. package/assets/orchestrator-delegation.md +33 -3
  13. package/assets/orchestrator-memory.md +20 -7
  14. package/assets/orchestrator.md +5 -3
  15. package/assets/sdd-orchestrator-workflow.md +25 -58
  16. package/assets/support/sdd-status-contract.md +9 -12
  17. package/docs/gentle-shell.md +14 -4
  18. package/docs/readme-reference.md +171 -32
  19. package/extensions/codegraph-tools.ts +2 -0
  20. package/extensions/gentle-agents.ts +281 -361
  21. package/extensions/gentle-ai.ts +588 -117
  22. package/extensions/gentle-shell.ts +74 -30
  23. package/extensions/pi-pretty.ts +63 -14
  24. package/extensions/quiet-tools.ts +1 -2
  25. package/extensions/startup-banner.ts +10 -9
  26. package/lib/agent-home.ts +8 -0
  27. package/lib/agent-profile-pin.ts +336 -0
  28. package/lib/agent-profiles.ts +28 -8
  29. package/lib/agents-config.ts +24 -2
  30. package/lib/agents-history.ts +3 -97
  31. package/lib/agents-keys.ts +27 -0
  32. package/lib/agents-protocol.ts +2 -15
  33. package/lib/agents-runner.ts +67 -111
  34. package/lib/agents-session-transport.ts +691 -0
  35. package/lib/command-palette-catalog.ts +87 -0
  36. package/lib/command-palette.ts +346 -0
  37. package/lib/native-choice-list.ts +5 -0
  38. package/lib/native-review-cli.ts +10 -97
  39. package/lib/review-publication-gate.ts +11 -1
  40. package/lib/review-repository.ts +1 -1
  41. package/lib/review-snapshot.ts +1 -0
  42. package/lib/review-transaction.ts +4 -2
  43. package/lib/sdd-preflight.ts +2 -1
  44. package/lib/sdd-research-capabilities.ts +18 -152
  45. package/lib/sdd-status.ts +7 -779
  46. package/lib/session-change-capture.ts +2 -1
  47. package/lib/session-changes.ts +8 -1
  48. package/lib/shell-bar.ts +21 -12
  49. package/lib/shell-card.ts +8 -12
  50. package/lib/shell-changes.ts +3 -2
  51. package/lib/shell-prompt.ts +25 -8
  52. package/lib/shell-sidebar-banner.ts +2 -2
  53. package/lib/shell-sidebar-layout.ts +5 -2
  54. package/lib/windows-session-transport.ts +877 -0
  55. package/package.json +3 -3
  56. package/runtime/native-review-cli.mjs +9 -96
  57. package/runtime/windows-session-transport.ps1 +791 -0
  58. package/scripts/test-packed-runner.mjs +1668 -20
  59. package/scripts/verify-package-files.mjs +0 -1
  60. package/tests/agent-home.test.ts +52 -0
  61. package/tests/agent-profiles.test.ts +30 -1
  62. package/tests/agents-config.test.ts +44 -0
  63. package/tests/agents-history.test.ts +12 -24
  64. package/tests/agents-runner.test.ts +307 -58
  65. package/tests/agents-session-transport-process.test.ts +249 -0
  66. package/tests/agents-session-transport.test.ts +823 -0
  67. package/tests/artifact-language.test.ts +10 -7
  68. package/tests/command-palette.test.ts +378 -0
  69. package/tests/delegated-key-learnings-contract.test.ts +2 -2
  70. package/tests/fixtures/agents-session-transport-process.mjs +108 -0
  71. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +54 -0
  72. package/tests/fixtures/windows-session-bootstrap.ps1 +129 -0
  73. package/tests/fixtures/windows-session-compile.ps1 +110 -0
  74. package/tests/gentle-agents.test.ts +849 -356
  75. package/tests/gentle-ai.test.ts +472 -4
  76. package/tests/gentle-shell.test.ts +201 -8
  77. package/tests/native-choice-list.test.ts +13 -0
  78. package/tests/native-review-cli.test.ts +0 -33
  79. package/tests/odd-routing-contract.test.ts +208 -0
  80. package/tests/orchestrator-budget.test.ts +17 -2
  81. package/tests/package-manifest.test.ts +115 -27
  82. package/tests/persona-single-channel.test.ts +3 -3
  83. package/tests/pi-pretty.test.ts +45 -0
  84. package/tests/profile-pin.test.ts +370 -0
  85. package/tests/quiet-tool-rendering.test.ts +32 -5
  86. package/tests/review-contract-prompt.test.ts +9 -0
  87. package/tests/review-controller.test.ts +0 -44
  88. package/tests/review-session-standing-permission-ipc.test.ts +427 -13
  89. package/tests/runtime-harness.mjs +4 -4
  90. package/tests/sdd-agent-tools.test.ts +15 -36
  91. package/tests/sdd-archive-replay.test.ts +82 -0
  92. package/tests/sdd-classical-continuation.test.ts +74 -0
  93. package/tests/sdd-execution-routing-contract.test.ts +18 -2
  94. package/tests/sdd-managed-runtime-settlement.test.ts +42 -330
  95. package/tests/sdd-native-managed-uptake.test.ts +11 -21
  96. package/tests/sdd-no-attempts-contract.test.ts +15 -0
  97. package/tests/sdd-odd-integration.test.ts +33 -0
  98. package/tests/sdd-optional-research.test.ts +124 -0
  99. package/tests/sdd-planning-routing-contract.test.ts +1 -1
  100. package/tests/sdd-preflight-rpc-input.test.ts +125 -0
  101. package/tests/sdd-preflight.test.ts +1 -1
  102. package/tests/sdd-research-capabilities.test.ts +20 -162
  103. package/tests/sdd-selection-transport.test.ts +180 -88
  104. package/tests/sdd-status.test.ts +5 -778
  105. package/tests/sdd-task-truth.test.ts +43 -0
  106. package/tests/session-change-capture.test.ts +20 -2
  107. package/tests/session-changes.test.ts +11 -0
  108. package/tests/shell-bar.test.ts +21 -0
  109. package/tests/shell-card.test.ts +8 -6
  110. package/tests/shell-changes.test.ts +8 -0
  111. package/tests/shell-prompt.test.ts +41 -7
  112. package/tests/shell-sidebar-banner.test.ts +4 -4
  113. package/tests/shell-sidebar-layout.test.ts +97 -13
  114. package/tests/startup-banner.test.ts +55 -2
  115. package/tests/windows-hidden-processes.test.ts +303 -0
  116. package/tests/windows-session-bootstrap.test.ts +1772 -0
  117. package/tests/windows-session-compile.test.ts +170 -0
  118. package/tests/windows-session-transport.test.ts +754 -0
  119. package/assets/agents/sdd-sync.md +0 -146
  120. package/lib/openspec-guardrails.ts +0 -99
  121. package/tests/native-sdd-attempt-authority.test.ts +0 -240
  122. package/tests/openspec-guardrails.test.ts +0 -71
@@ -0,0 +1,303 @@
1
+ import assert from "node:assert/strict";
2
+ import { createRequire, syncBuiltinESMExports } from "node:module";
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import type { TestContext } from "node:test";
8
+ import test from "node:test";
9
+ import type { ReceiptBodyV1 } from "../lib/review-transaction.ts";
10
+
11
+ type Options = Record<string, unknown>;
12
+ type Capture = {
13
+ kind: "execFileSync" | "execFile" | "spawnSync";
14
+ command: string;
15
+ args: readonly string[];
16
+ options: Options;
17
+ };
18
+ type Trace = { label: string; captures: Capture[] };
19
+ type ExpectedCall = Pick<Capture, "kind" | "command" | "args">;
20
+
21
+ async function trace<T>(captures: Capture[], traces: Trace[], label: string, action: () => T | Promise<T>, expected: readonly ExpectedCall[]): Promise<T> {
22
+ const start = captures.length;
23
+ const result = await action();
24
+ const route = captures.slice(start);
25
+ assert.deepEqual(route.map(({ kind, command, args }) => ({ kind, command, args })), expected, `${label} must use its documented public route`);
26
+ traces.push({ label, captures: route });
27
+ return result;
28
+ }
29
+
30
+ function childProcess(): typeof import("node:child_process") {
31
+ return createRequire(import.meta.url)("node:child_process") as typeof import("node:child_process");
32
+ }
33
+
34
+ function fakeGitResult(command: string, args: readonly string[], cwd: string): string {
35
+ if (command !== "git") return "";
36
+ if (args.includes("--show-toplevel")) return cwd;
37
+ if (args.includes("--git-common-dir")) return `${cwd}/.git`;
38
+ if (args.includes("--show-object-format")) return "sha1";
39
+ if (args.includes("--is-shallow-repository")) return "false";
40
+ if (args.includes("--git-path")) return join(cwd, ".git", "gentle-ai", "reviews");
41
+ if (args.includes("--max-parents=0")) return "a".repeat(40);
42
+ if (args[0] === "cat-file" && args[1] === "-t") return "tree";
43
+ if (args.includes("--verify")) {
44
+ const object = args.find((value) => value.endsWith("^{object}"));
45
+ return object === `${"b".repeat(40)}^{object}` ? "b".repeat(40) : "b".repeat(40);
46
+ }
47
+ if (args.includes("ls-files")) return "tests/windows-hidden-processes.test.ts\0";
48
+ throw new Error(`windows-hidden fixture: unexpected execFileSync git argv ${args.join(" ")}`);
49
+ }
50
+
51
+ function approvedReceipt(
52
+ transaction: typeof import("../lib/review-transaction.ts"),
53
+ fixtures: typeof import("./review-test-fixtures.ts"),
54
+ tree: string,
55
+ ) {
56
+ const current = transaction.createReviewState({
57
+ lineageId: "windows-hidden-gate",
58
+ mode: "ordinary",
59
+ snapshot: fixtures.testSnapshot({
60
+ baseTree: "1".repeat(40),
61
+ completeTree: "2".repeat(40),
62
+ initialTree: "3".repeat(40),
63
+ route: "standard",
64
+ lenses: ["review-risk"],
65
+ }),
66
+ evidenceHash: "b".repeat(64),
67
+ budget: { review_batches: 1, review_actors: 1, refuter_batches: 1, fix_batches: 1, validator_runs: 1, final_verifications: 1, judgment_rounds: 0, judge_runs: 0 },
68
+ });
69
+ const body: ReceiptBodyV1 = {
70
+ schema: "gentle-ai.review-receipt-body/v1",
71
+ lineage_id: current.lineage_id,
72
+ mode: current.mode,
73
+ base_tree: current.base_tree,
74
+ complete_snapshot_tree: current.complete_snapshot_tree,
75
+ review_projection: current.review_projection,
76
+ initial_review_tree: current.initial_review_tree,
77
+ final_candidate_tree: tree,
78
+ route: current.route,
79
+ lenses: current.lenses,
80
+ policy_hash: current.policy_hash,
81
+ frozen_ledger_hash: "c".repeat(64),
82
+ evidence_hash: current.evidence_hash,
83
+ budget: current.budget,
84
+ counters: current.counters,
85
+ terminal_state: "approved",
86
+ };
87
+ return transaction.createReceiptEnvelope(body);
88
+ }
89
+
90
+ function fakeSpawnResult(command: string, args: readonly string[], cwd: string): { status: number; stdout: string } {
91
+ if (command !== "git") return { status: 0, stdout: "" };
92
+ if (args.join(" ") === "rev-parse --show-toplevel") return { status: 0, stdout: `${cwd}\n` };
93
+ if (args.length === 3 && args[0] === "-C" && args[2] === "remote") return { status: 0, stdout: "origin\n" };
94
+ if (args.includes("get-url")) return { status: 0, stdout: "https://example.invalid/repo.git\n" };
95
+ if (args.includes("check-ref-format")) return { status: 0, stdout: "" };
96
+ if (args.includes("ls-remote")) return { status: 0, stdout: `${"c".repeat(40)}\trefs/heads/main\n` };
97
+ if (args[2] === "config") return { status: 0, stdout: "" };
98
+ throw new Error(`windows-hidden fixture: unexpected spawnSync git argv ${args.join(" ")}`);
99
+ }
100
+
101
+ async function captureOwnedRoutes(t: TestContext): Promise<{ captures: Capture[]; traces: Trace[] }> {
102
+ const cp = childProcess();
103
+ const original = { execFileSync: cp.execFileSync, execFile: cp.execFile, spawnSync: cp.spawnSync };
104
+ const captures: Capture[] = [];
105
+ const cwd = mkdtempSync(join(tmpdir(), "gentle-pi-windows-hidden-workspace-"));
106
+ t.after(() => rmSync(cwd, { recursive: true, force: true }));
107
+ mkdirSync(join(cwd, ".git"), { recursive: true });
108
+ const fakeSync = ((command: string, args: readonly string[], options: Options = {}) => {
109
+ captures.push({ kind: "execFileSync", command, args, options });
110
+ return fakeGitResult(command, args, String(options.cwd ?? cwd));
111
+ }) as unknown as typeof cp.execFileSync;
112
+ const fakeAsync = function(command: string, args: readonly string[], options: Options, callback: (error: null, stdout: string, stderr: string) => void) {
113
+ captures.push({ kind: "execFile", command, args, options });
114
+ if (process.platform === "win32" && command === "codegraph") {
115
+ callback(Object.assign(new Error("fixture shim missing"), { code: "ENOENT" }) as never, "", "");
116
+ } else {
117
+ callback(null, "indexed", "");
118
+ }
119
+ return undefined;
120
+ } as unknown as typeof cp.execFile;
121
+ (fakeAsync as unknown as { [promisify.custom]?: unknown })[promisify.custom] = async (command: string, args: readonly string[], options: Options) => {
122
+ captures.push({ kind: "execFile", command, args, options });
123
+ if (process.platform === "win32" && command === "codegraph") throw Object.assign(new Error("fixture shim missing"), { code: "ENOENT" });
124
+ return { stdout: "indexed", stderr: "" };
125
+ };
126
+ const fakeSpawn = ((command: string, args: readonly string[], options: Options = {}) => {
127
+ const result = fakeSpawnResult(command, args, String(options.cwd ?? cwd));
128
+ captures.push({ kind: "spawnSync", command, args, options });
129
+ if (command === "gh") return { status: 0, stdout: '{"total_count":0,"returned":0,"checks":[]}' };
130
+ return { ...result, stderr: "" };
131
+ }) as unknown as typeof cp.spawnSync;
132
+ try {
133
+ cp.execFileSync = fakeSync;
134
+ cp.execFile = fakeAsync;
135
+ cp.spawnSync = fakeSpawn;
136
+ syncBuiltinESMExports();
137
+ let fallbackRoot: string | undefined;
138
+ let fallbackCalls: ExpectedCall[] = [];
139
+ if (process.platform === "win32") {
140
+ fallbackRoot = mkdtempSync(join(tmpdir(), "gentle-pi-codegraph-contract-"));
141
+ t.after(() => rmSync(fallbackRoot!, { recursive: true, force: true }));
142
+ const packageRoot = join(fallbackRoot, "node_modules", "@colbymchenry", "codegraph");
143
+ mkdirSync(packageRoot, { recursive: true });
144
+ writeFileSync(join(fallbackRoot, "codegraph.cmd"), "@ECHO off\n");
145
+ writeFileSync(join(packageRoot, "package.json"), JSON.stringify({ bin: { codegraph: "entry.js" } }));
146
+ writeFileSync(join(packageRoot, "entry.js"), "// fixture entry\n");
147
+ const previousPath = process.env.Path;
148
+ const previousUpperPath = process.env.PATH;
149
+ t.after(() => { if (previousPath === undefined) delete process.env.Path; else process.env.Path = previousPath; });
150
+ t.after(() => { if (previousUpperPath === undefined) delete process.env.PATH; else process.env.PATH = previousUpperPath; });
151
+ process.env.Path = fallbackRoot;
152
+ process.env.PATH = fallbackRoot;
153
+ fallbackCalls = [{ kind: "execFile", command: process.execPath, args: [join(fallbackRoot, "node_modules", "@colbymchenry", "codegraph", "entry.js"), "init", cwd] }];
154
+ }
155
+ // The fixed query prevents accidental cache misses from becoming a fake proof.
156
+ const query = "?windows-hidden-contract";
157
+ const codegraph = await import(`../extensions/codegraph-tools.ts${query}`);
158
+ const repository = await import(`../lib/review-repository.ts${query}`);
159
+ const snapshot = await import(`../lib/review-snapshot.ts${query}`);
160
+ const transaction = await import(`../lib/review-transaction.ts${query}`);
161
+ const fixtures = await import(`./review-test-fixtures.ts${query}`);
162
+ const publication = await import(`../lib/review-publication-gate.ts${query}`);
163
+ const gentleAi = await import(`../extensions/gentle-ai.ts${query}`);
164
+
165
+ const traces: Trace[] = [];
166
+ const graph = codegraph.createCodeGraphTool();
167
+ assert.equal(typeof graph.execute, "function", "CodeGraph public handler must be registered");
168
+ const graphResult = await trace(captures, traces, "CodeGraph init", () => graph.execute("capture", { operation: "init" }, undefined, undefined, { cwd } as never), [
169
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--show-toplevel"] },
170
+ { kind: "execFile", command: "codegraph", args: ["init", cwd] },
171
+ ...fallbackCalls,
172
+ ]);
173
+ const graphText = String((graphResult as { content?: Array<{ text?: string }> }).content?.[0]?.text ?? "");
174
+ assert.match(graphText, /indexed/, "CodeGraph must return the fixture command result");
175
+
176
+ const tools = new Map<string, { execute: (...args: unknown[]) => Promise<unknown> }>();
177
+ gentleAi.createGentleAiExtension({ nativeReviewCli: { targetStatus: async () => ({}) } as never })({
178
+ registerTool(definition: { name: string; execute: (...args: unknown[]) => Promise<unknown> }) { tools.set(definition.name, definition); },
179
+ registerCommand() {}, on() {},
180
+ } as never);
181
+ const review = tools.get("gentle_review");
182
+ assert.ok(review, "gentle_review public handler must be registered");
183
+ await trace(captures, traces, "gentle_review inspect", () => review.execute("capture", { operation: "inspect", workspaceRoot: cwd }, undefined, undefined, { cwd, hasUI: false } as never), [
184
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--show-toplevel"] },
185
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--git-common-dir"] },
186
+ ]);
187
+
188
+ const authority = await trace(captures, traces, "repository authority", () => repository.reviewStoreRootForRepositoryV1(cwd), [
189
+ { kind: "execFileSync", command: "git", args: ["-C", cwd, "rev-parse", "--path-format=absolute", "--git-common-dir"] },
190
+ { kind: "execFileSync", command: "git", args: ["-C", cwd, "rev-parse", "--show-object-format"] },
191
+ { kind: "execFileSync", command: "git", args: ["-C", cwd, "rev-parse", "--is-shallow-repository"] },
192
+ { kind: "execFileSync", command: "git", args: ["-C", cwd, "rev-list", "--max-parents=0", "--all"] },
193
+ ]);
194
+ assert.equal(authority, join(cwd, ".git", "gentle-ai", "reviews"));
195
+ const untracked = await trace(captures, traces, "snapshot untracked discovery", () => snapshot.discoverReviewUntrackedPaths(cwd), [
196
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--show-toplevel"] },
197
+ { kind: "execFileSync", command: "git", args: ["ls-files", "--others", "--exclude-standard", "-z"] },
198
+ ]);
199
+ assert.deepEqual(untracked, ["tests/windows-hidden-processes.test.ts"]);
200
+ const storeRoot = await trace(captures, traces, "transaction store root", () => transaction.reviewStoreRootForRepository(cwd), [
201
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--show-toplevel"] },
202
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--git-path", "gentle-ai/reviews"] },
203
+ ]);
204
+ assert.equal(storeRoot, join(cwd, ".git", "gentle-ai", "reviews"));
205
+ const gateTree = "b".repeat(40);
206
+ const gate = await trace(captures, traces, "transaction intended-commit gate", () => transaction.evaluateGateTarget(
207
+ approvedReceipt(transaction, fixtures, gateTree),
208
+ { kind: publication.GATE_TARGET_KIND.INTENDED_COMMIT, intended_commit_tree: gateTree }, cwd, gateTree,
209
+ ), [
210
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--show-toplevel"] },
211
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--verify", `${gateTree}^{object}`] },
212
+ { kind: "execFileSync", command: "git", args: ["cat-file", "-t", gateTree] },
213
+ { kind: "execFileSync", command: "git", args: ["rev-parse", "--verify", `${gateTree}^{object}`] },
214
+ { kind: "execFileSync", command: "git", args: ["cat-file", "-t", gateTree] },
215
+ ]);
216
+ assert.equal(gate.status, publication.GATE_RESULT.ALLOW);
217
+ const destination = await trace(captures, traces, "publication configured destination", () => publication.resolveConfiguredPushDestinationV1(cwd, "origin"), [
218
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote"] },
219
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote", "get-url", "--push", "--all", "origin"] },
220
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "config", "--get-all", "remote.origin.pushurl"] },
221
+ ]);
222
+ assert.equal(destination.url, "https://example.invalid/repo.git");
223
+ await trace(captures, traces, "publication remote ref", () => publication.resolvePushRemoteRefV1(cwd, "origin", "refs/heads/main", "remote ref"), [
224
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote"] },
225
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote", "get-url", "--push", "--all", "origin"] },
226
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "config", "--get-all", "remote.origin.pushurl"] },
227
+ { kind: "spawnSync", command: "git", args: ["ls-remote", "--refs", "https://example.invalid/repo.git", "refs/heads/main"] },
228
+ ]);
229
+ await trace(captures, traces, "publication destination ref", () => publication.resolvePushDestinationRefV1(cwd, "origin", "main", "refs/heads/main", "destination"), [
230
+ { kind: "spawnSync", command: "git", args: ["check-ref-format", "refs/main"] },
231
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote"] },
232
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote", "get-url", "--push", "--all", "origin"] },
233
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "config", "--get-all", "remote.origin.pushurl"] },
234
+ { kind: "spawnSync", command: "git", args: ["ls-remote", "--refs", "https://example.invalid/repo.git"] },
235
+ ]);
236
+ await trace(captures, traces, "publication advertised object", () => publication.pushRemoteAdvertisesObjectV1(cwd, "origin", destination.destination_id, "c".repeat(40)), [
237
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote"] },
238
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote", "get-url", "--push", "--all", "origin"] },
239
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "config", "--get-all", "remote.origin.pushurl"] },
240
+ { kind: "spawnSync", command: "git", args: ["ls-remote", "--refs", "https://example.invalid/repo.git"] },
241
+ ]);
242
+ const remoteHead = await trace(captures, traces, "publication remote head", () => publication.recheckReleaseFastPathRemoteHeadV1({ repositoryCwd: cwd, remote: "origin", expectedRemoteHead: "c".repeat(40) }), [
243
+ { kind: "spawnSync", command: "git", args: ["rev-parse", "--show-toplevel"] },
244
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote"] },
245
+ { kind: "spawnSync", command: "git", args: ["-C", cwd, "remote", "get-url", "origin"] },
246
+ { kind: "spawnSync", command: "git", args: ["ls-remote", "--refs", "https://example.invalid/repo.git", "refs/heads/main"] },
247
+ ]);
248
+ assert.deepEqual(remoteHead, { advanced: false, remote_head: "c".repeat(40) });
249
+ const ci = await trace(captures, traces, "publication GH CI", () => publication.recheckReleaseFastPathCiStatusV1({ repositoryCwd: cwd, sha: "f".repeat(40), expectedStatus: "success" }), [
250
+ { kind: "spawnSync", command: "gh", args: ["api", "repos/{owner}/{repo}/commits/ffffffffffffffffffffffffffffffffffffffff/check-runs?per_page=100", "--jq", "{total_count, returned: (.check_runs | length), checks: [.check_runs[] | [.status, .conclusion]]}"] },
251
+ { kind: "spawnSync", command: "gh", args: ["api", "repos/{owner}/{repo}/commits/ffffffffffffffffffffffffffffffffffffffff/status", "--jq", ".state"] },
252
+ ]);
253
+ assert.deepEqual(ci, { proven: false, status: null });
254
+ return { captures, traces };
255
+ } finally {
256
+ cp.execFileSync = original.execFileSync;
257
+ cp.execFile = original.execFile;
258
+ cp.spawnSync = original.spawnSync;
259
+ syncBuiltinESMExports();
260
+ t.after(() => {
261
+ cp.execFileSync = original.execFileSync;
262
+ cp.execFile = original.execFile;
263
+ cp.spawnSync = original.spawnSync;
264
+ syncBuiltinESMExports();
265
+ });
266
+ }
267
+ }
268
+
269
+ test("owned public adapters pass windowsHide at every mapped Node child-process boundary", async (t) => {
270
+ const { traces } = await captureOwnedRoutes(t);
271
+ assert.deepEqual(traces.map(({ label }) => label), [
272
+ "CodeGraph init",
273
+ "gentle_review inspect",
274
+ "repository authority",
275
+ "snapshot untracked discovery",
276
+ "transaction store root",
277
+ "transaction intended-commit gate",
278
+ "publication configured destination",
279
+ "publication remote ref",
280
+ "publication destination ref",
281
+ "publication advertised object",
282
+ "publication remote head",
283
+ "publication GH CI",
284
+ ], "all twelve mapped public actions must complete before flag assertions");
285
+ for (const trace of traces) {
286
+ for (const capture of trace.captures) {
287
+ assert.equal(capture.options.windowsHide, true, `${trace.label}: ${capture.kind} ${capture.command} must hide Windows consoles`);
288
+ }
289
+ }
290
+ });
291
+
292
+ test("external editor remains the explicit interactive exemption", async () => {
293
+ const shell = await import("../extensions/gentle-shell.ts");
294
+ let received: Options | undefined;
295
+ const host = { stop() {}, start() {}, requestRender() {} };
296
+ assert.equal(shell.openInExternalEditor(host, "editor.txt", { EDITOR: "fixture-editor" }, ((_command: string, _args: readonly string[], options: Options) => {
297
+ received = options;
298
+ return { status: 0 };
299
+ }) as never, process.cwd()), true);
300
+ assert.equal(received?.shell, process.platform === "win32");
301
+ assert.equal(received?.stdio, "inherit");
302
+ assert.equal(received?.windowsHide, undefined, "the interactive editor intentionally remains unhidden");
303
+ });