gentle-pi 3.2.1 → 3.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 (71) hide show
  1. package/docs/gentle-shell.md +27 -14
  2. package/docs/readme-reference.md +41 -7
  3. package/docs/review-integration.md +22 -17
  4. package/extensions/gentle-agents.ts +85 -17
  5. package/extensions/gentle-ai.ts +123 -21
  6. package/extensions/gentle-shell.ts +379 -31
  7. package/extensions/gentle-todo.ts +19 -1
  8. package/lib/agents-view.ts +41 -14
  9. package/lib/agents-widget.ts +84 -13
  10. package/lib/command-palette-catalog.ts +1 -0
  11. package/lib/double-esc-cancel-policy.ts +138 -0
  12. package/lib/inprocess-reviewer.ts +260 -0
  13. package/lib/native-review-cli.ts +14 -0
  14. package/lib/odd-runtime-delegation-gate.ts +88 -0
  15. package/lib/review-host-relay.ts +246 -171
  16. package/lib/review-integration-v2.ts +110 -26
  17. package/lib/shell-bar.ts +150 -75
  18. package/lib/shell-card.ts +19 -9
  19. package/lib/shell-changes-view.ts +43 -5
  20. package/lib/shell-changes.ts +92 -5
  21. package/lib/shell-hover.ts +39 -0
  22. package/lib/shell-prompt.ts +10 -1
  23. package/lib/shell-sidebar-layout.ts +111 -15
  24. package/lib/shell-sidebar.ts +16 -0
  25. package/lib/shell-todo.ts +7 -1
  26. package/lib/shell-usage-view.ts +98 -10
  27. package/package.json +1 -1
  28. package/runtime/native-review-cli.mjs +14 -0
  29. package/runtime/review-integration-v2.mjs +110 -26
  30. package/scripts/gentle-ai-installer.mjs +10 -10
  31. package/scripts/maintainer/provider-relay-matrix.mjs +118 -47
  32. package/scripts/verify-package-files.mjs +3 -3
  33. package/tests/agents-grouping.test.ts +75 -18
  34. package/tests/agents-view.test.ts +28 -18
  35. package/tests/agents-widget.test.ts +100 -12
  36. package/tests/command-palette.test.ts +1 -0
  37. package/tests/devbinary/pi-host-relay.devtest.ts +176 -138
  38. package/tests/double-esc-cancel-policy.test.ts +194 -0
  39. package/tests/gentle-agents.test.ts +528 -5
  40. package/tests/gentle-ai-binary.test.ts +1 -1
  41. package/tests/gentle-ai-installer.test.ts +47 -47
  42. package/tests/gentle-ai.test.ts +69 -5
  43. package/tests/gentle-shell.test.ts +795 -23
  44. package/tests/gentle-todo.test.ts +17 -4
  45. package/tests/inprocess-reviewer.test.ts +368 -0
  46. package/tests/maintainer/provider-relay.maintest.ts +101 -143
  47. package/tests/native-review-capability-contract.test.ts +19 -1
  48. package/tests/odd-runtime-delegation-gate.test.ts +212 -0
  49. package/tests/orchestrator-rdd-ownership.test.ts +3 -3
  50. package/tests/package-manifest.test.ts +6 -6
  51. package/tests/review-host-relay-routing.test.ts +77 -0
  52. package/tests/review-host-relay.test.ts +277 -300
  53. package/tests/review-integration-v2-forward.test.ts +61 -0
  54. package/tests/review-integration-v2.test.ts +116 -1
  55. package/tests/review-relay-transport-agent.test.ts +22 -24
  56. package/tests/runtime-harness.mjs +11 -0
  57. package/tests/session-changes-shell.test.ts +27 -0
  58. package/tests/session-worktree-registry.test.ts +41 -0
  59. package/tests/shell-bar.test.ts +192 -124
  60. package/tests/shell-card.test.ts +5 -3
  61. package/tests/shell-changes-view.test.ts +47 -0
  62. package/tests/shell-changes.test.ts +177 -0
  63. package/tests/shell-hover.test.ts +19 -0
  64. package/tests/shell-prompt.test.ts +20 -0
  65. package/tests/shell-sidebar-fullscreen.test.ts +59 -0
  66. package/tests/shell-sidebar-layout.test.ts +243 -5
  67. package/tests/shell-sidebar.test.ts +25 -1
  68. package/tests/shell-todo.test.ts +36 -0
  69. package/tests/shell-usage-view.test.ts +120 -1
  70. package/lib/opaque-pi-reviewer-adapter.ts +0 -404
  71. package/tests/opaque-pi-reviewer-adapter.test.ts +0 -410
@@ -1004,6 +1004,20 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
1004
1004
  // remain dark because neither is proven to reach the negotiated START
1005
1005
  // path Pi consumes.
1006
1006
  "3.2.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1007
+ // v3.4.0 (gentle-pi never pinned the intervening v3.3.0 tag, so it gets no
1008
+ // row here) added capabilities/v2.6 and status/v8-v9, and extended
1009
+ // `review assess` with review_due/review_due_reason/next_transition
1010
+ // (gentle-ai #4714 follow-up). Ground-truthed by diffing
1011
+ // contracts/review-integration/v2 and contracts/review-provider-contract
1012
+ // between the v3.2.1 and v3.4.0 tags in the gentle-ai source tree: the
1013
+ // provider contract stays byte-identical at 1.2.0, and every
1014
+ // review-integration/v2 change is an additive superset (new optional
1015
+ // schema/fields) that decodeReviewStatusV3 and the capabilities
1016
+ // negotiator already accept without touching the closed START/STATUS
1017
+ // fields this row negotiates, so it repeats 3.2.1 exactly. riskEvidence
1018
+ // and hint remain dark because neither is proven to reach the negotiated
1019
+ // START path Pi consumes.
1020
+ "3.4.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1007
1021
  });
1008
1022
 
1009
1023
  export interface NativeReviewProcessDiagnostics {
@@ -0,0 +1,88 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ import { resolveSessionWorktree } from "./session-worktree-registry.ts";
4
+
5
+ export interface OddDelegationRefusal {
6
+ block: true;
7
+ reason: string;
8
+ }
9
+
10
+ interface SessionState {
11
+ primary: boolean;
12
+ childDepth: number;
13
+ firstSuccessfulPath?: string;
14
+ }
15
+
16
+ function canonicalTarget(path: string): string {
17
+ let candidate = path;
18
+ const missing: string[] = [];
19
+ while (true) {
20
+ try { return resolve(realpathSync(candidate), ...missing); }
21
+ catch {
22
+ const parent = dirname(candidate);
23
+ if (parent === candidate) return path;
24
+ missing.unshift(basename(candidate));
25
+ candidate = parent;
26
+ }
27
+ }
28
+ }
29
+
30
+ function sessionPath(toolName: string, input: unknown, cwd: string): string | undefined {
31
+ if ((toolName !== "edit" && toolName !== "write") || !input || typeof input !== "object") return undefined;
32
+ const path = (input as { path?: unknown }).path;
33
+ if (typeof path !== "string" || path.trim().length === 0) return undefined;
34
+ const root = resolveSessionWorktree(cwd, cwd)?.root;
35
+ if (!root) return undefined;
36
+ const spelling = path.replace(/^@/, "");
37
+ const canonicalCwd = realpathSync(cwd);
38
+ const lexicalTarget = isAbsolute(spelling)
39
+ ? resolve(spelling)
40
+ : resolve(canonicalCwd, spelling);
41
+ const target = canonicalTarget(lexicalTarget);
42
+ const repositoryPath = relative(root, target);
43
+ if (repositoryPath === "" || repositoryPath === ".." || repositoryPath.startsWith(`..${sep}`) || isAbsolute(repositoryPath)) return undefined;
44
+ const canonical = repositoryPath.split(sep).join("/");
45
+ return canonical === "odd/tasks" || canonical.startsWith("odd/tasks/") ? undefined : canonical;
46
+ }
47
+
48
+ export class OddRuntimeDelegationGate {
49
+ private readonly sessions = new Map<string, SessionState>();
50
+
51
+ start(sessionId: string, primary: boolean): void {
52
+ const state = this.sessions.get(sessionId);
53
+ if (primary) this.sessions.set(sessionId, { primary: true, childDepth: 0 });
54
+ else if (state?.primary) state.childDepth += 1;
55
+ else this.sessions.set(sessionId, { primary: false, childDepth: 1 });
56
+ }
57
+
58
+ endChild(sessionId: string): void {
59
+ const state = this.sessions.get(sessionId);
60
+ if (state && state.childDepth > 0) state.childDepth -= 1;
61
+ }
62
+
63
+ beforeTool(
64
+ sessionId: string,
65
+ toolName: string,
66
+ input: unknown,
67
+ cwd: string,
68
+ availableTools: readonly string[],
69
+ ): OddDelegationRefusal | undefined {
70
+ const state = this.sessions.get(sessionId);
71
+ if (!state?.primary || state.childDepth > 0) return undefined;
72
+ const path = sessionPath(toolName, input, cwd);
73
+ if (!path || !state.firstSuccessfulPath || path === state.firstSuccessfulPath) return undefined;
74
+ return {
75
+ block: true,
76
+ reason: availableTools.includes("subagent_run")
77
+ ? `ODD multi-file write refused before mutation: direct edits already changed ${state.firstSuccessfulPath}. Delegate this additional file through subagent_run, preferring gentle-ai-worker and then worker.`
78
+ : "ODD multi-file write refused before mutation: direct edits already changed another file, but subagent_run is not callable. Stop and report that no delegation mechanism is callable.",
79
+ };
80
+ }
81
+
82
+ recordSuccess(sessionId: string, toolName: string, input: unknown, cwd: string): void {
83
+ const state = this.sessions.get(sessionId);
84
+ if (!state?.primary || state.childDepth > 0 || state.firstSuccessfulPath) return;
85
+ const path = sessionPath(toolName, input, cwd);
86
+ if (path) state.firstSuccessfulPath = path;
87
+ }
88
+ }