gentle-pi 2.5.0 → 2.6.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 (189) hide show
  1. package/README.md +139 -30
  2. package/assets/agents/gentle-ai-worker.md +4 -0
  3. package/assets/agents/jd-fix-agent.md +18 -0
  4. package/assets/agents/jd-judge-a.md +1 -1
  5. package/assets/agents/jd-judge-b.md +1 -1
  6. package/assets/agents/sdd-apply.md +7 -5
  7. package/assets/agents/sdd-archive.md +5 -3
  8. package/assets/agents/sdd-design.md +4 -0
  9. package/assets/agents/sdd-explore.md +4 -0
  10. package/assets/agents/sdd-init.md +4 -0
  11. package/assets/agents/sdd-onboard.md +4 -0
  12. package/assets/agents/sdd-proposal.md +4 -0
  13. package/assets/agents/sdd-remediate.md +37 -0
  14. package/assets/agents/sdd-research.md +26 -3
  15. package/assets/agents/sdd-spec.md +4 -0
  16. package/assets/agents/sdd-status.md +9 -75
  17. package/assets/agents/sdd-sync.md +4 -0
  18. package/assets/agents/sdd-tasks.md +4 -0
  19. package/assets/agents/sdd-verify.md +5 -3
  20. package/assets/chains/sdd-full.chain.md +4 -0
  21. package/assets/chains/sdd-plan.chain.md +4 -0
  22. package/assets/chains/sdd-verify.chain.md +4 -0
  23. package/assets/migrations/managed-assets-v2.5.0.json +7 -0
  24. package/assets/orchestrator-delegation.md +21 -3
  25. package/assets/sdd-orchestrator-workflow.md +54 -21
  26. package/assets/support/sdd-status-contract.md +34 -90
  27. package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
  28. package/docs/telemetry.md +57 -1
  29. package/docs/windows-startup-console-visibility.md +18 -0
  30. package/extensions/ask-user-choice.ts +143 -15
  31. package/extensions/codegraph-tools.ts +1 -0
  32. package/extensions/gentle-agents.ts +799 -50
  33. package/extensions/gentle-ai.ts +2033 -322
  34. package/extensions/gentle-shell.ts +145 -42
  35. package/extensions/gentle-todo.ts +47 -12
  36. package/extensions/quiet-tools.ts +1 -0
  37. package/extensions/runtime-metrics.ts +130 -0
  38. package/extensions/sdd-init.ts +2 -2
  39. package/extensions/startup-banner.ts +52 -75
  40. package/lib/agent-profiles.ts +550 -0
  41. package/lib/agents-completion-delivery.ts +72 -0
  42. package/lib/agents-config.ts +7 -10
  43. package/lib/agents-history.ts +9 -1
  44. package/lib/agents-messaging.ts +187 -0
  45. package/lib/agents-protocol.ts +77 -5
  46. package/lib/agents-runner.ts +548 -26
  47. package/lib/agents-thread-view.ts +57 -0
  48. package/lib/agents-view-layout.ts +40 -0
  49. package/lib/agents-view.ts +548 -191
  50. package/lib/agents-widget.ts +33 -14
  51. package/lib/gentle-ai-binary.ts +3 -1
  52. package/lib/gentle-ai-renderer.ts +8 -6
  53. package/lib/native-review-cli.ts +283 -1
  54. package/lib/orchestrator-presence.ts +337 -0
  55. package/lib/profiles-orchestrator.ts +203 -0
  56. package/lib/review-candidate-view-owner.ts +296 -46
  57. package/lib/review-candidate-view.ts +30 -20
  58. package/lib/review-consent-component.ts +247 -0
  59. package/lib/review-consent-ui.ts +53 -8
  60. package/lib/review-host-relay.ts +28 -0
  61. package/lib/review-integration-v2.ts +187 -5
  62. package/lib/review-last-event-controller.ts +7 -4
  63. package/lib/review-reminder-receipt.ts +74 -0
  64. package/lib/review-session-standing-permission.ts +27 -6
  65. package/lib/runtime-metrics-children.ts +199 -0
  66. package/lib/runtime-metrics-delivery.ts +68 -0
  67. package/lib/runtime-metrics-native.ts +166 -0
  68. package/lib/runtime-metrics-pi-identity.ts +113 -0
  69. package/lib/runtime-metrics-policy.ts +51 -0
  70. package/lib/runtime-metrics.ts +255 -0
  71. package/lib/sdd-preflight.ts +362 -81
  72. package/lib/sdd-research-capabilities.ts +228 -0
  73. package/lib/sdd-status.ts +29 -7
  74. package/lib/session-worktree-registry.ts +118 -0
  75. package/lib/shell-bar.ts +47 -1
  76. package/lib/shell-card.ts +1 -4
  77. package/lib/shell-changes-view.ts +362 -37
  78. package/lib/shell-changes.ts +81 -1
  79. package/lib/shell-prompt.ts +11 -15
  80. package/lib/shell-sidebar-banner.ts +11 -0
  81. package/lib/shell-sidebar-layout.ts +213 -0
  82. package/lib/shell-sidebar.ts +41 -0
  83. package/lib/shell-todo.ts +28 -11
  84. package/lib/telemetry-trigger.ts +2 -0
  85. package/package.json +6 -3
  86. package/runtime/gentle-ai-binary.mjs +3 -1
  87. package/runtime/native-review-cli.mjs +283 -1
  88. package/runtime/review-integration-v2.mjs +187 -5
  89. package/runtime/telemetry-trigger.mjs +2 -0
  90. package/scripts/build-runtime-modules.mjs +9 -1
  91. package/scripts/check-types.mjs +125 -0
  92. package/scripts/gentle-ai-installer.mjs +10 -10
  93. package/scripts/install-gentle-ai.mjs +12 -0
  94. package/scripts/install-tui-mode-setting.mjs +114 -0
  95. package/scripts/test-packed-runner.mjs +16 -2
  96. package/scripts/types-baseline.json +99 -0
  97. package/scripts/verify-package-files.mjs +4 -2
  98. package/skills/_shared/review-ledger-contract.md +17 -1
  99. package/skills/issue-creation/SKILL.md +3 -3
  100. package/skills/judgment-day/SKILL.md +17 -3
  101. package/skills/judgment-day/references/prompts-and-formats.md +14 -3
  102. package/tests/agent-profiles.test.ts +722 -0
  103. package/tests/agents-completion-delivery.test.ts +94 -0
  104. package/tests/agents-config.test.ts +62 -0
  105. package/tests/agents-fake-child.ts +15 -1
  106. package/tests/agents-grouping.test.ts +179 -0
  107. package/tests/agents-integration.test.ts +100 -0
  108. package/tests/agents-messaging.test.ts +94 -0
  109. package/tests/agents-protocol.test.ts +45 -0
  110. package/tests/agents-queries.test.ts +190 -0
  111. package/tests/agents-responsive.test.ts +43 -0
  112. package/tests/agents-runner.test.ts +571 -14
  113. package/tests/agents-thread-view.test.ts +45 -0
  114. package/tests/agents-view.test.ts +476 -65
  115. package/tests/agents-widget.test.ts +31 -1
  116. package/tests/artifact-language.test.ts +25 -2
  117. package/tests/ask-user-choice.test.ts +169 -3
  118. package/tests/asset-installation-runtime.test.ts +108 -0
  119. package/tests/autonomous-guard.test.ts +116 -1
  120. package/tests/codegraph-tools.test.ts +2 -1
  121. package/tests/delegated-key-learnings-contract.test.ts +1 -1
  122. package/tests/devbinary/native-review-parity.devtest.ts +2 -0
  123. package/tests/feature-request-form.test.ts +67 -0
  124. package/tests/fixtures/agents-messaging-child.mjs +5 -0
  125. package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
  126. package/tests/gentle-agents.test.ts +1460 -33
  127. package/tests/gentle-ai-binary.test.ts +7 -2
  128. package/tests/gentle-ai-installer.test.ts +47 -47
  129. package/tests/gentle-ai-renderer.test.ts +38 -0
  130. package/tests/gentle-ai.test.ts +944 -4
  131. package/tests/gentle-shell.test.ts +303 -12
  132. package/tests/gentle-todo.test.ts +54 -10
  133. package/tests/install-tui-mode-setting.test.ts +324 -0
  134. package/tests/issue-creation-skill.test.ts +22 -0
  135. package/tests/model-routing-authority.test.ts +12 -0
  136. package/tests/native-review-capability-contract.test.ts +12 -1
  137. package/tests/native-review-cli.test.ts +277 -3
  138. package/tests/native-review-parity.test.ts +14 -7
  139. package/tests/native-sdd-attempt-authority.test.ts +7 -2
  140. package/tests/orchestrator-presence.test.ts +389 -0
  141. package/tests/package-manifest.test.ts +232 -7
  142. package/tests/profiles-orchestrator.test.ts +208 -0
  143. package/tests/quiet-tool-rendering.test.ts +1 -0
  144. package/tests/rdd-aware-verification-contract.test.ts +10 -0
  145. package/tests/review-agent-end-preflight.test.ts +332 -24
  146. package/tests/review-candidate-view.test.ts +304 -6
  147. package/tests/review-consent-ui.test.ts +352 -0
  148. package/tests/review-contract-prompt.test.ts +14 -0
  149. package/tests/review-controller-native-routing.test.ts +563 -3
  150. package/tests/review-controller.test.ts +1 -1
  151. package/tests/review-host-relay-restart-parity.test.ts +142 -1
  152. package/tests/review-host-relay-routing.test.ts +364 -4
  153. package/tests/review-host-relay.test.ts +29 -0
  154. package/tests/review-integration-v2-forward.test.ts +44 -0
  155. package/tests/review-integration-v2.test.ts +164 -0
  156. package/tests/review-last-event-closure.test.ts +105 -1
  157. package/tests/review-ledger-contract.test.ts +61 -6
  158. package/tests/review-reminder-receipt.test.ts +62 -0
  159. package/tests/review-session-standing-permission-controller.test.ts +52 -4
  160. package/tests/review-session-standing-permission.test.ts +30 -0
  161. package/tests/runtime-harness.mjs +447 -39
  162. package/tests/runtime-metrics-children.test.ts +206 -0
  163. package/tests/runtime-metrics-delivery.test.ts +85 -0
  164. package/tests/runtime-metrics-extension.test.ts +187 -0
  165. package/tests/runtime-metrics-native.test.ts +209 -0
  166. package/tests/runtime-metrics-pi-identity.test.ts +113 -0
  167. package/tests/runtime-metrics-policy.test.ts +62 -0
  168. package/tests/runtime-metrics.test.ts +184 -0
  169. package/tests/sdd-agent-tools.test.ts +10 -1
  170. package/tests/sdd-execution-routing-contract.test.ts +28 -0
  171. package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
  172. package/tests/sdd-native-managed-uptake.test.ts +253 -0
  173. package/tests/sdd-planning-routing-contract.test.ts +45 -0
  174. package/tests/sdd-preflight.test.ts +252 -8
  175. package/tests/sdd-research-capabilities.test.ts +256 -0
  176. package/tests/sdd-research-live.test.ts +241 -0
  177. package/tests/sdd-selection-transport.test.ts +504 -0
  178. package/tests/sdd-status.test.ts +51 -0
  179. package/tests/session-worktree-registry.test.ts +135 -0
  180. package/tests/shell-card.test.ts +24 -3
  181. package/tests/shell-changes-view.test.ts +471 -8
  182. package/tests/shell-changes.test.ts +168 -0
  183. package/tests/shell-prompt.test.ts +28 -6
  184. package/tests/shell-sidebar-banner.test.ts +23 -0
  185. package/tests/shell-sidebar-layout.test.ts +387 -0
  186. package/tests/shell-sidebar.test.ts +50 -0
  187. package/tests/shell-todo.test.ts +100 -11
  188. package/tests/startup-banner.test.ts +126 -0
  189. package/tests/telemetry-trigger.test.ts +3 -1
@@ -0,0 +1,135 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import test from "node:test";
7
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
8
+ import { SessionWorktreeRegistry, resolveSessionWorktree, resolveSessionWorktreeWithGit, SESSION_WORKTREE_ENTRY, toolWorktreePath, worktreeGitEnvironment } from "../lib/session-worktree-registry.ts";
9
+
10
+ // All Git and session writes belong to unique fixtures, never the live clone.
11
+ function fixture(t: test.TestContext) {
12
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "session-worktrees-")));
13
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
14
+ const main = join(dir, "main");
15
+ const linked = join(dir, "linked");
16
+ const other = join(dir, "other");
17
+ const empty = join(dir, "empty");
18
+ mkdirSync(empty);
19
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_")));
20
+ Object.assign(env, { GIT_CONFIG_GLOBAL: join(empty, "config"), GIT_CONFIG_NOSYSTEM: "1", GIT_ATTR_NOSYSTEM: "1" });
21
+ writeFileSync(join(empty, "config"), "");
22
+ const git = (cwd: string, args: string[]) => execFileSync("git", ["-C", cwd, "-c", `core.hooksPath=${empty}`, "-c", "commit.gpgsign=false", ...args], { env, stdio: ["ignore", "pipe", "pipe"] });
23
+ git(dir, ["init", "--initial-branch=main", `--template=${empty}`, main]);
24
+ git(main, ["-c", "user.name=Test", "-c", "user.email=test@example.invalid", "commit", "--allow-empty", "-m", "Fixture"]);
25
+ git(main, ["worktree", "add", "-b", "linked", linked]);
26
+ git(dir, ["init", `--template=${empty}`, other]);
27
+ mkdirSync(join(linked, "src"));
28
+ writeFileSync(join(linked, "src", "file.ts"), "preexisting\n");
29
+ const alias = join(dir, "alias");
30
+ symlinkSync(linked, alias, "dir");
31
+ return { dir, main, linked, other, alias };
32
+ }
33
+
34
+ function host(cwd: string, session = SessionManager.inMemory(cwd)) {
35
+ const emitted: unknown[] = [];
36
+ const pi = { appendEntry: (type: string, data: unknown) => { session.appendCustomEntry(type, data); }, events: { emit: (_name: string, data: unknown) => emitted.push(data) } };
37
+ return { pi, session, emitted, registry: () => new SessionWorktreeRegistry(pi, session, cwd) };
38
+ }
39
+
40
+ test("canonical roots include cwd, dedupe aliases immediately and reject unrelated clones", (t) => {
41
+ const f = fixture(t);
42
+ const h = host(f.main);
43
+ const registry = h.registry();
44
+ registry.start();
45
+ assert.deepEqual(registry.roots(), [f.main]);
46
+ assert.equal(registry.register(join(f.alias, "src", "file.ts"), "tool:read"), f.linked);
47
+ registry.register(f.linked, "explicit");
48
+ assert.deepEqual(registry.roots(), [f.main, f.linked]);
49
+ assert.equal(h.session.getEntries().length, 2);
50
+ assert.throws(() => registry.register(f.other, "explicit"), /same Git clone/);
51
+ assert.throws(() => registry.register(join(f.dir, "missing"), "explicit"), /worktree/);
52
+ assert.equal(resolveSessionWorktree(f.alias, f.main)?.root, f.linked);
53
+ });
54
+
55
+ test("restore reads the whole session tree, resumes the same id, and ignores new/fork/clone ids", (t) => {
56
+ const f = fixture(t);
57
+ const session = SessionManager.create(f.main, join(f.dir, "sessions"));
58
+ const first = session.appendMessage({ role: "user", content: "start", timestamp: 1 });
59
+ // Pi persists entries after the first assistant message.
60
+ session.appendMessage({ role: "assistant", content: [], api: "openai-responses", provider: "test", model: "test", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop", timestamp: 2 });
61
+ const h = host(f.main, session);
62
+ h.registry().start();
63
+ h.registry().register(f.linked, "explicit");
64
+ session.branch(first);
65
+ assert.deepEqual(h.registry().roots(), [f.main, f.linked], "off-branch registrations remain session-wide");
66
+ const resumed = host(f.main, SessionManager.open(session.getSessionFile()!));
67
+ assert.deepEqual(resumed.registry().roots(), [f.main, f.linked]);
68
+ const forkPath = session.createBranchedSession(session.getEntries().at(-1)!.id)!;
69
+ const fork = host(f.main, SessionManager.open(forkPath));
70
+ fork.registry().start();
71
+ assert.deepEqual(fork.registry().roots(), [f.main], "inherited custom entries carry the old id");
72
+ const fresh = host(f.main);
73
+ for (const entry of resumed.session.getEntries()) {
74
+ if (entry.type === "custom") fresh.session.appendCustomEntry(entry.customType, entry.data);
75
+ }
76
+ fresh.registry().start();
77
+ assert.deepEqual(fresh.registry().roots(), [f.main], "cloned entries cannot expand a new session registry");
78
+ });
79
+
80
+ test("durable append works without a shell listener and independent registries do not double append", (t) => {
81
+ const f = fixture(t);
82
+ const h = host(f.main);
83
+ const shell = h.registry();
84
+ shell.start();
85
+ const agents = h.registry();
86
+ agents.register(f.linked, "subagent:spawn");
87
+ assert.deepEqual(shell.roots(), [f.main, f.linked]);
88
+ shell.register(f.alias, "explicit");
89
+ assert.equal(h.session.getEntries().filter((e) => e.type === "custom" && e.customType === SESSION_WORKTREE_ENTRY).length, 2);
90
+ agents.close();
91
+ assert.throws(() => agents.register(f.main, "explicit"), /inactive session/);
92
+ });
93
+
94
+ test("restoration validates entries and hides missing roots without deleting their registrations", (t) => {
95
+ const f = fixture(t);
96
+ const h = host(f.main);
97
+ const registry = h.registry();
98
+ registry.start();
99
+ registry.register(f.linked, "explicit");
100
+ h.session.appendCustomEntry(SESSION_WORKTREE_ENTRY, { sessionId: h.session.getSessionId(), root: f.other, evidence: "explicit" });
101
+ h.session.appendCustomEntry(SESSION_WORKTREE_ENTRY, { sessionId: h.session.getSessionId(), root: 42 });
102
+ rmSync(f.linked, { recursive: true });
103
+ assert.deepEqual(registry.roots(), [f.main]);
104
+ assert.ok(h.session.getEntries().some((entry) => entry.type === "custom" && (entry.data as { root?: string })?.root === f.linked));
105
+ });
106
+
107
+ test("Git child environment removes routing and config overrides without mutating its source", () => {
108
+ const env = { PATH: "/tools", HOME: "/fixture", GIT_DIR: "/foreign/.git", GIT_WORK_TREE: "/foreign", GIT_INDEX_FILE: "/foreign/index", GIT_COMMON_DIR: "/foreign/common", GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "core.worktree", GIT_CONFIG_VALUE_0: "/foreign", git_dir: "/windows-case-alias" };
109
+ const before = { ...env };
110
+ assert.deepEqual(worktreeGitEnvironment(env), { PATH: "/tools", HOME: "/fixture" });
111
+ assert.deepEqual(env, before);
112
+ });
113
+
114
+ test("session startup identity lookup hides its direct Git children", (t) => {
115
+ const f = fixture(t);
116
+ const calls: Array<{ command: string; args: readonly string[]; options: Record<string, unknown> }> = [];
117
+ const run = ((command: string, args: readonly string[], options: Record<string, unknown>) => {
118
+ calls.push({ command, args, options });
119
+ return args.at(-1) === "--show-toplevel" ? `${f.main}\n` : `${join(f.main, ".git")}\n`;
120
+ }) as typeof import("node:child_process").execFileSync;
121
+ assert.equal(resolveSessionWorktreeWithGit(f.main, f.main, run)?.root, f.main);
122
+ assert.equal(calls.length, 2);
123
+ for (const call of calls) {
124
+ assert.equal(call.command, "git");
125
+ assert.equal(call.options.shell, false);
126
+ assert.equal(call.options.windowsHide, true);
127
+ }
128
+ });
129
+
130
+ test("only standard path-bearing calls have registration candidates; shell and prose never do", () => {
131
+ for (const name of ["read", "write", "edit", "grep", "find", "ls"]) assert.equal(toolWorktreePath(name, { path: "../linked/file" }), "../linked/file");
132
+ for (const name of ["grep", "find", "ls"]) assert.equal(toolWorktreePath(name, {}), ".");
133
+ for (const name of ["bash", "powershell", "custom", "subagent_run"]) assert.equal(toolWorktreePath(name, { path: "/linked", command: "cd /linked", task: "/linked" }), undefined);
134
+ assert.equal(toolWorktreePath("read", { path: 42 }), undefined);
135
+ });
@@ -70,13 +70,34 @@ test("renderCard keeps the rail and its corners in the tone and the rest of the
70
70
  assert.equal(CARD_TONE.SUCCESS, "success");
71
71
  });
72
72
 
73
- test("renderCard places a hint at the right end of the top rule and can paint every line", () => {
73
+ test("renderCard places a hint at the right end of the top rule without background fill", () => {
74
74
  const lines = renderCard(card(), plainTheme, 60, { expanded: false, hint: "ctrl+o expand" });
75
75
  assert.match(stripAnsi(lines[0]), /^╭─ ✿ Gentle AI · review preflight ─+ ctrl\+o expand ╮$/);
76
76
  assert.equal(visibleWidth(lines[0]), 60);
77
77
 
78
- const painted = renderCard(card(), plainTheme, 60, { expanded: false, paint: (line) => `[bg]${line}[/bg]` });
79
- assert.ok(painted.every((line) => line.startsWith("[bg]") && line.endsWith("[/bg]")));
78
+ assert.doesNotMatch(lines.join("\n"), /\x1b\[44m/);
79
+ });
80
+
81
+ test("cards remain transparent across content resets and narrow widths", () => {
82
+ for (const width of [0, 1, 2, 3, 4, 8, 40]) {
83
+ const lines = renderCard(card({ body: ["red\x1b[0m blue", ""] }), ansiTheme, width, { expanded: true });
84
+ for (const [row, line] of lines.entries()) {
85
+ let painted = false, column = 0;
86
+ for (const token of line.match(/\x1b\[[\d;]*m|[^\x1b]/gu) ?? []) {
87
+ if (token.startsWith("\x1b")) {
88
+ for (const code of token.slice(2, -1).split(";").map(Number)) {
89
+ if (code === 0 || code === 49) painted = false;
90
+ if (code === 44) painted = true;
91
+ }
92
+ } else {
93
+ assert.equal(painted, false, `row ${row}, cell ${column}`);
94
+ column += visibleWidth(token);
95
+ }
96
+ }
97
+ assert.equal(painted, false, "background must not leak into host padding");
98
+ assert.equal(visibleWidth(line), width);
99
+ }
100
+ }
80
101
  });
81
102
 
82
103
  test("renderCard accepts a custom glyph and an empty body", () => {