gentle-pi 2.2.0 → 2.3.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 (130) hide show
  1. package/README.md +51 -80
  2. package/assets/agents/review-readability.md +0 -2
  3. package/assets/agents/review-reliability.md +0 -2
  4. package/assets/agents/review-resilience.md +0 -2
  5. package/assets/agents/review-risk.md +0 -2
  6. package/assets/agents/sdd-apply.md +5 -3
  7. package/assets/agents/sdd-proposal.md +2 -0
  8. package/assets/agents/sdd-research.md +54 -0
  9. package/assets/agents/sdd-status.md +4 -4
  10. package/assets/agents/sdd-tasks.md +2 -3
  11. package/assets/agents/sdd-verify.md +21 -1
  12. package/assets/chains/sdd-full.chain.md +1 -1
  13. package/assets/chains/sdd-verify.chain.md +1 -1
  14. package/assets/orchestrator-delegation.md +54 -248
  15. package/assets/orchestrator-memory.md +2 -0
  16. package/assets/orchestrator.md +22 -38
  17. package/assets/sdd-orchestrator-workflow.md +30 -37
  18. package/assets/support/sdd-status-contract.md +7 -7
  19. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  20. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  21. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  22. package/docs/native-authority-architecture.md +9 -11
  23. package/docs/review-integration.md +27 -373
  24. package/extensions/ask-user-choice.ts +151 -0
  25. package/extensions/gentle-ai.ts +1778 -3673
  26. package/extensions/quiet-tools.ts +515 -32
  27. package/extensions/sdd-init.ts +4 -8
  28. package/lib/gentle-ai-renderer.ts +70 -0
  29. package/lib/model-routing-authority.ts +133 -0
  30. package/lib/native-review-cli.ts +372 -940
  31. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  32. package/lib/review-candidate-view.ts +341 -132
  33. package/lib/review-host-relay.ts +210 -68
  34. package/lib/review-integration-v2.ts +839 -307
  35. package/lib/review-last-event-controller.ts +35 -0
  36. package/lib/sdd-preflight.ts +177 -66
  37. package/lib/sdd-status.ts +66 -111
  38. package/lib/terminal-theme.ts +1 -1
  39. package/package.json +83 -82
  40. package/runtime/gentle-ai-binary.mjs +1 -1
  41. package/runtime/native-review-cli.mjs +322 -890
  42. package/runtime/review-integration-v2.mjs +789 -257
  43. package/runtime/review-relay-contract.mjs +1 -1
  44. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  45. package/scripts/gentle-ai-installer.mjs +75 -22
  46. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  47. package/scripts/test-packed-runner.mjs +4 -7
  48. package/scripts/verify-package-files.mjs +12 -13
  49. package/skills/_shared/review-ledger-contract.md +8 -14
  50. package/skills/chained-pr/SKILL.md +3 -0
  51. package/skills/cognitive-doc-design/SKILL.md +1 -1
  52. package/skills/comment-writer/SKILL.md +1 -1
  53. package/skills/gentle-ai/SKILL.md +7 -74
  54. package/skills/judgment-day/SKILL.md +5 -7
  55. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  56. package/skills/release/SKILL.md +3 -3
  57. package/skills/skill-registry/SKILL.md +1 -1
  58. package/skills/work-unit-commits/SKILL.md +3 -1
  59. package/tests/artifact-language.test.ts +24 -11
  60. package/tests/ask-user-choice.test.ts +264 -0
  61. package/tests/codegraph-tools.test.ts +3 -3
  62. package/tests/crosslane/cross-lane.mjs +15 -1168
  63. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  64. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  65. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  66. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  67. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  68. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  69. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  70. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  71. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  72. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  73. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  74. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  75. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  76. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  77. package/tests/gentle-ai-binary.test.ts +83 -13
  78. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  79. package/tests/gentle-ai-installer.test.ts +137 -47
  80. package/tests/gentle-ai.test.ts +820 -131
  81. package/tests/gentle-theme.test.ts +133 -0
  82. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  83. package/tests/model-routing-authority.test.ts +257 -0
  84. package/tests/native-review-capability-contract.test.ts +28 -1
  85. package/tests/native-review-cli.test.ts +586 -962
  86. package/tests/native-review-consent.test.ts +150 -66
  87. package/tests/native-review-parity-runtime.test.ts +80 -359
  88. package/tests/native-review-parity.test.ts +674 -925
  89. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  90. package/tests/orchestrator-budget.test.ts +132 -88
  91. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  92. package/tests/package-manifest.test.ts +37 -35
  93. package/tests/provider-defect-handoff.test.ts +15 -118
  94. package/tests/quiet-tool-rendering.test.ts +1055 -28
  95. package/tests/review-authority-recovery-docs.test.ts +1 -2
  96. package/tests/review-candidate-view.test.ts +409 -3
  97. package/tests/review-compact-contract.test.ts +4 -30
  98. package/tests/review-controller-lock-status.test.ts +2 -2
  99. package/tests/review-controller-native-recovery.test.ts +363 -1450
  100. package/tests/review-controller-native-routing.test.ts +1251 -5642
  101. package/tests/review-controller-retired-ops.test.ts +1 -1
  102. package/tests/review-controller-workspace-root.test.ts +222 -71
  103. package/tests/review-controller.test.ts +26 -816
  104. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  105. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  106. package/tests/review-gate.test.ts +0 -45
  107. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  108. package/tests/review-host-relay-routing.test.ts +117 -82
  109. package/tests/review-host-relay.test.ts +242 -8
  110. package/tests/review-integration-v2-forward.test.ts +576 -553
  111. package/tests/review-integration-v2.test.ts +239 -165
  112. package/tests/review-last-event-closure.test.ts +408 -0
  113. package/tests/review-ledger-contract.test.ts +97 -35
  114. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  115. package/tests/review-relay-transport-agent.test.ts +119 -46
  116. package/tests/review-snapshot.test.ts +3 -2
  117. package/tests/runtime-harness.mjs +361 -196
  118. package/tests/sdd-agent-tools.test.ts +36 -0
  119. package/tests/sdd-preflight.test.ts +81 -15
  120. package/tests/sdd-status.test.ts +109 -110
  121. package/tests/skill-collision-prefixes.test.ts +5 -8
  122. package/tests/writer-edit-surface-scope.test.ts +230 -0
  123. package/themes/Gentleman-Cute.json +94 -0
  124. package/themes/Gentleman-Sexy.json +92 -0
  125. package/lib/git-commit-transaction.ts +0 -861
  126. package/runtime/git-commit-transaction.mjs +0 -862
  127. package/scripts/run-git-commit-transaction.mjs +0 -35
  128. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  129. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  130. package/tests/git-commit-transaction.test.ts +0 -530
@@ -0,0 +1,284 @@
1
+ import { spawn } from "node:child_process";
2
+ import { chmod, mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join, posix, win32 } from "node:path";
5
+
6
+ export const OPAQUE_PI_REVIEWER_ARGV = Object.freeze([
7
+ "--print",
8
+ "--mode", "text",
9
+ "--no-session",
10
+ "--no-tools",
11
+ "--no-extensions",
12
+ "--no-skills",
13
+ "--no-prompt-templates",
14
+ "--no-themes",
15
+ "--no-context-files",
16
+ "--no-approve",
17
+ ] as const);
18
+
19
+ export const OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE = {
20
+ SCRATCH_FAILED: "scratch-failed",
21
+ LAUNCH_FAILED: "launch-failed",
22
+ CANCELLED: "cancelled",
23
+ TIMED_OUT: "timed-out",
24
+ NONZERO_EXIT: "nonzero-exit",
25
+ EMPTY_OUTPUT: "empty-output",
26
+ CLEANUP_FAILED: "cleanup-failed",
27
+ } as const;
28
+ export type OpaquePiReviewerTransportFailureKind = (typeof OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE)[keyof typeof OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE];
29
+
30
+ export interface OpaquePiReviewerOptions {
31
+ readonly piExecutable?: string;
32
+ readonly environment?: NodeJS.ProcessEnv;
33
+ readonly timeoutMs?: number;
34
+ readonly signal?: AbortSignal;
35
+ }
36
+
37
+ export interface OpaquePiReviewerResult {
38
+ readonly stdout: Buffer;
39
+ readonly promptByteLength: number;
40
+ readonly stdoutByteLength: number;
41
+ }
42
+
43
+ export interface OpaquePiReviewerTransportDetails {
44
+ readonly exitCode?: number | null;
45
+ readonly stderr?: Buffer;
46
+ readonly timedOut?: boolean;
47
+ readonly cancelled?: boolean;
48
+ /** Wall time for a launched Pi process; absent when no process was launched. */
49
+ readonly elapsedMs?: number;
50
+ /** The bound applied to a launched Pi process; absent when no process was launched. */
51
+ readonly timeoutMs?: number;
52
+ }
53
+
54
+ export class OpaquePiReviewerTransportError extends Error {
55
+ readonly kind: OpaquePiReviewerTransportFailureKind;
56
+ readonly exitCode: number | null;
57
+ readonly stderr: Buffer;
58
+ readonly timedOut: boolean;
59
+ readonly cancelled: boolean;
60
+ readonly elapsedMs: number | null;
61
+ readonly timeoutMs: number | null;
62
+
63
+ constructor(kind: OpaquePiReviewerTransportFailureKind, message: string, details: OpaquePiReviewerTransportDetails = {}) {
64
+ super(message);
65
+ this.name = "OpaquePiReviewerTransportError";
66
+ this.kind = kind;
67
+ this.exitCode = details.exitCode ?? null;
68
+ this.stderr = details.stderr ?? Buffer.alloc(0);
69
+ this.timedOut = details.timedOut ?? false;
70
+ this.cancelled = details.cancelled ?? false;
71
+ this.elapsedMs = details.elapsedMs ?? null;
72
+ this.timeoutMs = details.timeoutMs ?? null;
73
+ }
74
+ }
75
+
76
+ interface OpaquePiProcessResult {
77
+ readonly stdout: Buffer;
78
+ readonly stderr: Buffer;
79
+ readonly exitCode: number | null;
80
+ readonly timedOut: boolean;
81
+ readonly cancelled: boolean;
82
+ readonly elapsedMs: number;
83
+ readonly timeoutMs: number;
84
+ }
85
+
86
+ const DEFAULT_OPAQUE_PI_TIMEOUT_MS = 600_000;
87
+
88
+ export interface PiLaunch {
89
+ readonly file: string;
90
+ readonly arguments: readonly string[];
91
+ }
92
+
93
+ interface PiHostProcess {
94
+ readonly execPath: string;
95
+ readonly entry: string | undefined;
96
+ }
97
+
98
+ /**
99
+ * The exact spawn shape for the fresh Pi process. A bare `pi` on Windows
100
+ * resolves to pi.cmd, pi.ps1, or a POSIX shim, none of which Node can spawn
101
+ * with shell:false (EINVAL or ENOENT). This adapter already runs inside Pi, so
102
+ * on win32 the host's own JavaScript entry is spawned through the host's
103
+ * process.execPath instead; a shell is never enabled. Every other platform,
104
+ * and every explicit launcher, keeps the exact shape it always had.
105
+ */
106
+ export function resolvePiLaunch(
107
+ piExecutable: string | undefined,
108
+ platform: NodeJS.Platform = process.platform,
109
+ host: PiHostProcess = { execPath: process.execPath, entry: process.argv[1] },
110
+ ): PiLaunch {
111
+ if (piExecutable !== undefined) return { file: piExecutable, arguments: [...OPAQUE_PI_REVIEWER_ARGV] };
112
+ if (platform !== "win32") return { file: "pi", arguments: [...OPAQUE_PI_REVIEWER_ARGV] };
113
+ if (typeof host.entry !== "string" || host.entry.length === 0 || !(platform === "win32" ? win32 : posix).isAbsolute(host.entry)) {
114
+ throw new Error(`Pi host entry could not be resolved from the running process (received ${JSON.stringify(host.entry ?? null)}); a bare pi launcher cannot be spawned on Windows without a shell`);
115
+ }
116
+ return { file: host.execPath, arguments: [host.entry, ...OPAQUE_PI_REVIEWER_ARGV] };
117
+ }
118
+
119
+ function errorMessage(error: unknown): string {
120
+ return error instanceof Error ? error.message : String(error);
121
+ }
122
+
123
+ function runPiProcess(prompt: Buffer, scratchDirectory: string, options: OpaquePiReviewerOptions): Promise<OpaquePiProcessResult> {
124
+ return new Promise((resolve, reject) => {
125
+ const startedAt = Date.now();
126
+ let launch: PiLaunch;
127
+ try {
128
+ launch = resolvePiLaunch(options.piExecutable);
129
+ } catch (error) {
130
+ reject(error);
131
+ return;
132
+ }
133
+ const child = spawn(launch.file, [...launch.arguments], {
134
+ cwd: scratchDirectory,
135
+ env: options.environment ?? process.env,
136
+ stdio: ["pipe", "pipe", "pipe"],
137
+ shell: false,
138
+ windowsHide: true,
139
+ });
140
+ const stdout: Buffer[] = [];
141
+ const stderr: Buffer[] = [];
142
+ const timeoutMs = options.timeoutMs ?? DEFAULT_OPAQUE_PI_TIMEOUT_MS;
143
+ let timedOut = false;
144
+ let cancelled = false;
145
+ let settled = false;
146
+ const timer = timeoutMs > 0
147
+ ? setTimeout(() => {
148
+ timedOut = true;
149
+ child.kill("SIGKILL");
150
+ }, timeoutMs)
151
+ : undefined;
152
+ timer?.unref();
153
+ const cancel = () => {
154
+ cancelled = true;
155
+ child.kill("SIGKILL");
156
+ };
157
+ const clear = () => {
158
+ if (timer !== undefined) clearTimeout(timer);
159
+ options.signal?.removeEventListener("abort", cancel);
160
+ };
161
+
162
+ child.stdout.on("data", (chunk: Buffer) => stdout.push(chunk));
163
+ child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk));
164
+ child.on("error", (error) => {
165
+ if (settled) return;
166
+ settled = true;
167
+ clear();
168
+ reject(error);
169
+ });
170
+ child.on("close", (code) => {
171
+ if (settled) return;
172
+ settled = true;
173
+ clear();
174
+ resolve({
175
+ stdout: Buffer.concat(stdout),
176
+ stderr: Buffer.concat(stderr),
177
+ exitCode: code,
178
+ timedOut,
179
+ cancelled,
180
+ elapsedMs: Date.now() - startedAt,
181
+ timeoutMs,
182
+ });
183
+ });
184
+ if (options.signal?.aborted) cancel();
185
+ else options.signal?.addEventListener("abort", cancel, { once: true });
186
+ child.stdin.on("error", () => undefined);
187
+ child.stdin.end(prompt);
188
+ });
189
+ }
190
+
191
+ /** Runs raw prompt bytes through one fixed, isolated Pi process. */
192
+ export async function runOpaquePiReviewer(prompt: Buffer, options: OpaquePiReviewerOptions = {}): Promise<OpaquePiReviewerResult> {
193
+ if (options.signal?.aborted) {
194
+ throw new OpaquePiReviewerTransportError(
195
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.CANCELLED,
196
+ "Pi process was cancelled before launch",
197
+ { cancelled: true },
198
+ );
199
+ }
200
+
201
+ let scratchDirectory: string | undefined;
202
+ let primaryFailure = false;
203
+ try {
204
+ try {
205
+ scratchDirectory = await mkdtemp(join(tmpdir(), "gentle-pi-opaque-reviewer-"));
206
+ await chmod(scratchDirectory, 0o700);
207
+ } catch (error) {
208
+ throw new OpaquePiReviewerTransportError(
209
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.SCRATCH_FAILED,
210
+ `Pi scratch directory could not be prepared: ${errorMessage(error)}`,
211
+ );
212
+ }
213
+
214
+ let processResult: OpaquePiProcessResult;
215
+ try {
216
+ processResult = await runPiProcess(prompt, scratchDirectory, options);
217
+ } catch (error) {
218
+ if (options.signal?.aborted) {
219
+ throw new OpaquePiReviewerTransportError(
220
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.CANCELLED,
221
+ "Pi process was cancelled",
222
+ { cancelled: true },
223
+ );
224
+ }
225
+ throw new OpaquePiReviewerTransportError(
226
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.LAUNCH_FAILED,
227
+ `Pi process could not start: ${errorMessage(error)}`,
228
+ );
229
+ }
230
+ const timing = {
231
+ elapsedMs: processResult.elapsedMs,
232
+ timeoutMs: processResult.timeoutMs,
233
+ };
234
+ if (processResult.timedOut) {
235
+ throw new OpaquePiReviewerTransportError(
236
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.TIMED_OUT,
237
+ "Pi process timed out",
238
+ { exitCode: processResult.exitCode, stderr: processResult.stderr, timedOut: true, ...timing },
239
+ );
240
+ }
241
+ if (processResult.cancelled) {
242
+ throw new OpaquePiReviewerTransportError(
243
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.CANCELLED,
244
+ "Pi process was cancelled",
245
+ { exitCode: processResult.exitCode, stderr: processResult.stderr, cancelled: true, ...timing },
246
+ );
247
+ }
248
+ if (processResult.exitCode !== 0) {
249
+ throw new OpaquePiReviewerTransportError(
250
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.NONZERO_EXIT,
251
+ "Pi process failed",
252
+ { exitCode: processResult.exitCode, stderr: processResult.stderr, ...timing },
253
+ );
254
+ }
255
+ if (processResult.stdout.length === 0) {
256
+ throw new OpaquePiReviewerTransportError(
257
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.EMPTY_OUTPUT,
258
+ "Pi process produced no output bytes",
259
+ { exitCode: 0, stderr: processResult.stderr, ...timing },
260
+ );
261
+ }
262
+ return {
263
+ stdout: processResult.stdout,
264
+ promptByteLength: prompt.length,
265
+ stdoutByteLength: processResult.stdout.length,
266
+ };
267
+ } catch (error) {
268
+ primaryFailure = true;
269
+ throw error;
270
+ } finally {
271
+ if (scratchDirectory !== undefined) {
272
+ try {
273
+ await rm(scratchDirectory, { recursive: true, force: true });
274
+ } catch (error) {
275
+ if (!primaryFailure) {
276
+ throw new OpaquePiReviewerTransportError(
277
+ OPAQUE_PI_REVIEWER_TRANSPORT_FAILURE.CLEANUP_FAILED,
278
+ `Pi scratch directory cleanup failed: ${errorMessage(error)}`,
279
+ );
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }