@ilya-lesikov/pi-pi 0.1.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 (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,286 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join, resolve as resolvePath } from "node:path";
6
+ import {
7
+ getDefaultBranch,
8
+ getFileContentsForDiff,
9
+ getGitContext,
10
+ listRecentCommits,
11
+ parseWorktreeDiffType,
12
+ runGitDiff,
13
+ type DiffType,
14
+ type ReviewGitRuntime,
15
+ } from "./review-core";
16
+
17
+ const tempDirs: string[] = [];
18
+
19
+ function makeTempDir(prefix: string): string {
20
+ const dir = mkdtempSync(join(tmpdir(), prefix));
21
+ tempDirs.push(dir);
22
+ return dir;
23
+ }
24
+
25
+ function git(cwd: string, args: string[]): string {
26
+ const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
27
+ if (result.status !== 0) {
28
+ throw new Error(result.stderr || `git ${args.join(" ")} failed`);
29
+ }
30
+ return result.stdout.trim();
31
+ }
32
+
33
+ function makeRuntime(baseCwd: string): ReviewGitRuntime {
34
+ return {
35
+ async runGit(args: string[], options?: { cwd?: string }) {
36
+ const result = spawnSync("git", args, {
37
+ cwd: options?.cwd ?? baseCwd,
38
+ encoding: "utf-8",
39
+ });
40
+
41
+ return {
42
+ stdout: result.stdout ?? "",
43
+ stderr: result.stderr ?? "",
44
+ exitCode: result.status ?? (result.error ? 1 : 0),
45
+ };
46
+ },
47
+
48
+ async readTextFile(path: string) {
49
+ try {
50
+ const fullPath = path.startsWith("/") ? path : resolvePath(baseCwd, path);
51
+ return readFileSync(fullPath, "utf-8");
52
+ } catch {
53
+ return null;
54
+ }
55
+ },
56
+ };
57
+ }
58
+
59
+ function initRepo(initialBranch = "main"): string {
60
+ const repoDir = makeTempDir("plannotator-review-core-");
61
+ git(repoDir, ["init"]);
62
+ git(repoDir, ["branch", "-M", initialBranch]);
63
+ git(repoDir, ["config", "user.email", "review-core@example.com"]);
64
+ git(repoDir, ["config", "user.name", "Review Core"]);
65
+
66
+ writeFileSync(join(repoDir, "tracked.txt"), "before\n", "utf-8");
67
+ git(repoDir, ["add", "tracked.txt"]);
68
+ git(repoDir, ["commit", "-m", "initial"]);
69
+
70
+ return repoDir;
71
+ }
72
+
73
+ afterEach(() => {
74
+ for (const dir of tempDirs.splice(0)) {
75
+ rmSync(dir, { recursive: true, force: true });
76
+ }
77
+ });
78
+
79
+ describe("review-core", () => {
80
+ test("uncommitted diff includes tracked and untracked files", async () => {
81
+ const repoDir = initRepo();
82
+ const runtime = makeRuntime(repoDir);
83
+
84
+ writeFileSync(join(repoDir, "tracked.txt"), "after\n", "utf-8");
85
+ writeFileSync(join(repoDir, "untracked.txt"), "brand new\n", "utf-8");
86
+
87
+ const result = await runGitDiff(runtime, "uncommitted", "main");
88
+
89
+ expect(result.label).toBe("Uncommitted changes");
90
+ expect(result.patch).toContain("diff --git a/tracked.txt b/tracked.txt");
91
+ expect(result.patch).toContain("diff --git a/untracked.txt b/untracked.txt");
92
+ expect(result.patch).toContain("+++ b/untracked.txt");
93
+ });
94
+
95
+ test("uncommitted diff includes untracked files when CWD is a subdirectory", async () => {
96
+ const repoDir = initRepo();
97
+
98
+ mkdirSync(join(repoDir, "packages", "infra", "lib"), { recursive: true });
99
+ writeFileSync(join(repoDir, "packages", "infra", "lib", "Stack.ts"), "new stack\n", "utf-8");
100
+
101
+ writeFileSync(join(repoDir, "root-new.txt"), "root untracked\n", "utf-8");
102
+ mkdirSync(join(repoDir, ".github", "workflows"), { recursive: true });
103
+ writeFileSync(join(repoDir, ".github", "workflows", "ci.yml"), "name: CI\n", "utf-8");
104
+
105
+ writeFileSync(join(repoDir, "tracked.txt"), "after\n", "utf-8");
106
+
107
+ // Runtime whose default CWD is a subdirectory (simulates a hook process
108
+ // that inherits an agent CWD inside a monorepo package)
109
+ const subCwd = join(repoDir, "packages", "infra");
110
+ const runtime = makeRuntime(subCwd);
111
+
112
+ const result = await runGitDiff(runtime, "uncommitted", "main");
113
+
114
+ expect(result.patch).toContain("diff --git a/tracked.txt b/tracked.txt");
115
+ expect(result.patch).toContain("diff --git a/packages/infra/lib/Stack.ts b/packages/infra/lib/Stack.ts");
116
+ expect(result.patch).toContain("diff --git a/root-new.txt b/root-new.txt");
117
+ expect(result.patch).toContain("diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml");
118
+ });
119
+
120
+ test("unstaged diff includes untracked files", async () => {
121
+ const repoDir = initRepo();
122
+ const runtime = makeRuntime(repoDir);
123
+
124
+ writeFileSync(join(repoDir, "tracked.txt"), "after\n", "utf-8");
125
+ git(repoDir, ["add", "tracked.txt"]);
126
+ writeFileSync(join(repoDir, "tracked.txt"), "after again\n", "utf-8");
127
+ writeFileSync(join(repoDir, "scratch.txt"), "tmp\n", "utf-8");
128
+
129
+ const result = await runGitDiff(runtime, "unstaged", "main");
130
+
131
+ expect(result.label).toBe("Unstaged changes");
132
+ expect(result.patch).toContain("diff --git a/tracked.txt b/tracked.txt");
133
+ expect(result.patch).toContain("diff --git a/scratch.txt b/scratch.txt");
134
+ });
135
+
136
+ test("staged diff excludes untracked files until they are staged", async () => {
137
+ const repoDir = initRepo();
138
+ const runtime = makeRuntime(repoDir);
139
+
140
+ writeFileSync(join(repoDir, "tracked.txt"), "staged change\n", "utf-8");
141
+ git(repoDir, ["add", "tracked.txt"]);
142
+ writeFileSync(join(repoDir, "draft.txt"), "not staged yet\n", "utf-8");
143
+
144
+ const stagedOnly = await runGitDiff(runtime, "staged", "main");
145
+ expect(stagedOnly.patch).toContain("diff --git a/tracked.txt b/tracked.txt");
146
+ expect(stagedOnly.patch).not.toContain("draft.txt");
147
+
148
+ git(repoDir, ["add", "draft.txt"]);
149
+ const stagedWithNewFile = await runGitDiff(runtime, "staged", "main");
150
+ expect(stagedWithNewFile.patch).toContain("diff --git a/draft.txt b/draft.txt");
151
+ });
152
+
153
+ test("branch diff returns an error when the default branch ref is invalid", async () => {
154
+ const repoDir = initRepo("trunk");
155
+ const runtime = makeRuntime(repoDir);
156
+
157
+ writeFileSync(join(repoDir, "tracked.txt"), "after\n", "utf-8");
158
+
159
+ const context = await getGitContext(runtime);
160
+ expect(context.defaultBranch).toBe("master");
161
+
162
+ const result = await runGitDiff(runtime, "branch", context.defaultBranch);
163
+
164
+ expect(result.patch).toBe("");
165
+ expect(result.label).toBe("Error: branch");
166
+ // Error is derived from the argv — assert the meaningful parts rather
167
+ // than the exact string so harmless argv reorders (e.g. --end-of-options)
168
+ // don't break it.
169
+ expect(result.error).toContain("git diff");
170
+ expect(result.error).toContain("master..HEAD");
171
+ });
172
+
173
+ test("git context lists worktrees and file content lookup returns old/new content", async () => {
174
+ const repoDir = initRepo();
175
+ const runtime = makeRuntime(repoDir);
176
+
177
+ const worktreeParent = makeTempDir("plannotator-review-core-worktree-");
178
+ const worktreeDir = join(worktreeParent, "feature-worktree");
179
+ git(repoDir, ["worktree", "add", "-b", "feature/review-core", worktreeDir]);
180
+
181
+ writeFileSync(join(repoDir, "tracked.txt"), "after\n", "utf-8");
182
+ writeFileSync(join(repoDir, "new-file.txt"), "brand new\n", "utf-8");
183
+
184
+ const context = await getGitContext(runtime);
185
+ expect(context.diffOptions.map((option) => option.id)).toEqual(
186
+ expect.arrayContaining(["uncommitted", "staged", "unstaged", "last-commit"]),
187
+ );
188
+ expect(
189
+ context.worktrees.some((worktree) => worktree.path.endsWith("/feature-worktree")),
190
+ ).toBe(true);
191
+
192
+ const trackedContents = await getFileContentsForDiff(
193
+ runtime,
194
+ "uncommitted",
195
+ context.defaultBranch,
196
+ "tracked.txt",
197
+ );
198
+ expect(trackedContents.oldContent).toBe("before\n");
199
+ expect(trackedContents.newContent).toBe("after\n");
200
+
201
+ const newFileContents = await getFileContentsForDiff(
202
+ runtime,
203
+ "uncommitted",
204
+ context.defaultBranch,
205
+ "new-file.txt",
206
+ );
207
+ expect(newFileContents.oldContent).toBeNull();
208
+ expect(newFileContents.newContent).toBe("brand new\n");
209
+ });
210
+
211
+ test("getDefaultBranch falls back to local when origin/HEAD points at an unfetched ref", () => {
212
+ // Simulates a narrow / partial clone where origin/HEAD is configured but
213
+ // the target ref was never fetched. Before the verify step, the server
214
+ // would return "origin/phantom" and every branch/merge-base diff would
215
+ // fail with "unknown revision". With the verify step we fall back to
216
+ // local main.
217
+ const repoDir = initRepo();
218
+
219
+ // Manually set origin/HEAD → origin/phantom without ever fetching it.
220
+ git(repoDir, ["symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/phantom"]);
221
+
222
+ const runtime = makeRuntime(repoDir);
223
+ return getDefaultBranch(runtime).then((result) => {
224
+ expect(result).toBe("main");
225
+ });
226
+ });
227
+
228
+ test("listRecentCommits returns HEAD ancestry with shortSha and subject", async () => {
229
+ const repoDir = initRepo();
230
+ writeFileSync(join(repoDir, "tracked.txt"), "second\n", "utf-8");
231
+ git(repoDir, ["add", "tracked.txt"]);
232
+ git(repoDir, ["commit", "-m", "second commit"]);
233
+ writeFileSync(join(repoDir, "tracked.txt"), "third\n", "utf-8");
234
+ git(repoDir, ["add", "tracked.txt"]);
235
+ git(repoDir, ["commit", "-m", "third commit"]);
236
+
237
+ const runtime = makeRuntime(repoDir);
238
+ const commits = await listRecentCommits(runtime, repoDir, 10);
239
+
240
+ expect(commits.length).toBe(3);
241
+ expect(commits[0].subject).toBe("third commit");
242
+ expect(commits[1].subject).toBe("second commit");
243
+ expect(commits[2].subject).toBe("initial");
244
+ for (const c of commits) {
245
+ expect(c.sha).toMatch(/^[0-9a-f]{40}$/);
246
+ expect(c.shortSha.length).toBeGreaterThanOrEqual(7);
247
+ expect(c.sha.startsWith(c.shortSha)).toBe(true);
248
+ expect(c.author).toBe("Review Core");
249
+ expect(c.relativeDate.length).toBeGreaterThan(0);
250
+ }
251
+ });
252
+
253
+ test("getGitContext includes recentCommits for the picker", async () => {
254
+ const repoDir = initRepo();
255
+ writeFileSync(join(repoDir, "tracked.txt"), "second\n", "utf-8");
256
+ git(repoDir, ["add", "tracked.txt"]);
257
+ git(repoDir, ["commit", "-m", "second commit"]);
258
+
259
+ const runtime = makeRuntime(repoDir);
260
+ const context = await getGitContext(runtime, repoDir);
261
+
262
+ expect(context.recentCommits).toBeDefined();
263
+ expect(context.recentCommits!.length).toBe(2);
264
+ expect(context.recentCommits![0].subject).toBe("second commit");
265
+ });
266
+
267
+ test("parseWorktreeDiffType recognises every DiffType suffix, including merge-base", () => {
268
+ // Regression guard: every local diff type must round-trip through the
269
+ // worktree-prefixed form. Missing `merge-base` here previously routed
270
+ // "worktree:/path:merge-base" to { path: "/path:merge-base", subType: "uncommitted" }
271
+ // which pointed git at a non-existent cwd and silently collapsed the diff mode.
272
+ const subTypes = [
273
+ "uncommitted",
274
+ "staged",
275
+ "unstaged",
276
+ "last-commit",
277
+ "branch",
278
+ "merge-base",
279
+ ] as const;
280
+ for (const sub of subTypes) {
281
+ const composite = `worktree:/tmp/my-worktree:${sub}` as DiffType;
282
+ const parsed = parseWorktreeDiffType(composite);
283
+ expect(parsed).toEqual({ path: "/tmp/my-worktree", subType: sub });
284
+ }
285
+ });
286
+ });