@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,150 @@
1
+ /**
2
+ * Remote Detection & Port Config Tests
3
+ *
4
+ * Run: bun test packages/server/remote.test.ts
5
+ */
6
+
7
+ import { afterEach, describe, expect, test } from "bun:test";
8
+ import { isRemoteSession, getServerHostname, getServerPort } from "./remote";
9
+
10
+ // Save and restore env between tests
11
+ const savedEnv: Record<string, string | undefined> = {};
12
+ const envKeys = ["PLANNOTATOR_REMOTE", "PLANNOTATOR_PORT", "SSH_TTY", "SSH_CONNECTION"];
13
+
14
+ function clearEnv() {
15
+ for (const key of envKeys) {
16
+ savedEnv[key] = process.env[key];
17
+ delete process.env[key];
18
+ }
19
+ }
20
+
21
+ afterEach(() => {
22
+ for (const key of envKeys) {
23
+ if (savedEnv[key] !== undefined) {
24
+ process.env[key] = savedEnv[key];
25
+ } else {
26
+ delete process.env[key];
27
+ }
28
+ }
29
+ });
30
+
31
+ describe("isRemoteSession", () => {
32
+ test("false by default (no env vars)", () => {
33
+ clearEnv();
34
+ expect(isRemoteSession()).toBe(false);
35
+ });
36
+
37
+ test("true when PLANNOTATOR_REMOTE=1", () => {
38
+ clearEnv();
39
+ process.env.PLANNOTATOR_REMOTE = "1";
40
+ expect(isRemoteSession()).toBe(true);
41
+ });
42
+
43
+ test("true when PLANNOTATOR_REMOTE=true", () => {
44
+ clearEnv();
45
+ process.env.PLANNOTATOR_REMOTE = "true";
46
+ expect(isRemoteSession()).toBe(true);
47
+ });
48
+
49
+ test("false when PLANNOTATOR_REMOTE=0", () => {
50
+ clearEnv();
51
+ process.env.PLANNOTATOR_REMOTE = "0";
52
+ expect(isRemoteSession()).toBe(false);
53
+ });
54
+
55
+ test("false when PLANNOTATOR_REMOTE=false", () => {
56
+ clearEnv();
57
+ process.env.PLANNOTATOR_REMOTE = "false";
58
+ expect(isRemoteSession()).toBe(false);
59
+ });
60
+
61
+ test("PLANNOTATOR_REMOTE=false overrides SSH_TTY", () => {
62
+ clearEnv();
63
+ process.env.PLANNOTATOR_REMOTE = "false";
64
+ process.env.SSH_TTY = "/dev/pts/0";
65
+ expect(isRemoteSession()).toBe(false);
66
+ });
67
+
68
+ test("PLANNOTATOR_REMOTE=0 overrides SSH_CONNECTION", () => {
69
+ clearEnv();
70
+ process.env.PLANNOTATOR_REMOTE = "0";
71
+ process.env.SSH_CONNECTION = "192.168.1.1 12345 192.168.1.2 22";
72
+ expect(isRemoteSession()).toBe(false);
73
+ });
74
+
75
+ test("true when SSH_TTY is set (legacy)", () => {
76
+ clearEnv();
77
+ process.env.SSH_TTY = "/dev/pts/0";
78
+ expect(isRemoteSession()).toBe(true);
79
+ });
80
+
81
+ test("true when SSH_CONNECTION is set (legacy)", () => {
82
+ clearEnv();
83
+ process.env.SSH_CONNECTION = "192.168.1.1 12345 192.168.1.2 22";
84
+ expect(isRemoteSession()).toBe(true);
85
+ });
86
+ });
87
+
88
+ describe("getServerPort", () => {
89
+ test("returns 0 for local session (random port)", () => {
90
+ clearEnv();
91
+ expect(getServerPort()).toBe(0);
92
+ });
93
+
94
+ test("returns 19432 for remote session", () => {
95
+ clearEnv();
96
+ process.env.PLANNOTATOR_REMOTE = "1";
97
+ expect(getServerPort()).toBe(19432);
98
+ });
99
+
100
+ test("returns 0 when PLANNOTATOR_REMOTE=false overrides SSH", () => {
101
+ clearEnv();
102
+ process.env.PLANNOTATOR_REMOTE = "false";
103
+ process.env.SSH_TTY = "/dev/pts/0";
104
+ expect(getServerPort()).toBe(0);
105
+ });
106
+
107
+ test("explicit PLANNOTATOR_PORT overrides everything", () => {
108
+ clearEnv();
109
+ process.env.PLANNOTATOR_PORT = "8080";
110
+ expect(getServerPort()).toBe(8080);
111
+ });
112
+
113
+ test("explicit port overrides remote default", () => {
114
+ clearEnv();
115
+ process.env.PLANNOTATOR_REMOTE = "1";
116
+ process.env.PLANNOTATOR_PORT = "3000";
117
+ expect(getServerPort()).toBe(3000);
118
+ });
119
+
120
+ test("ignores invalid port (falls back to default)", () => {
121
+ clearEnv();
122
+ process.env.PLANNOTATOR_PORT = "not-a-number";
123
+ expect(getServerPort()).toBe(0);
124
+ });
125
+
126
+ test("ignores out-of-range port", () => {
127
+ clearEnv();
128
+ process.env.PLANNOTATOR_PORT = "99999";
129
+ expect(getServerPort()).toBe(0);
130
+ });
131
+
132
+ test("ignores zero port", () => {
133
+ clearEnv();
134
+ process.env.PLANNOTATOR_PORT = "0";
135
+ expect(getServerPort()).toBe(0);
136
+ });
137
+ });
138
+
139
+ describe("getServerHostname", () => {
140
+ test("returns loopback for local sessions", () => {
141
+ clearEnv();
142
+ expect(getServerHostname()).toBe("127.0.0.1");
143
+ });
144
+
145
+ test("returns all interfaces for remote sessions", () => {
146
+ clearEnv();
147
+ process.env.PLANNOTATOR_REMOTE = "1";
148
+ expect(getServerHostname()).toBe("0.0.0.0");
149
+ });
150
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Remote session detection and port configuration
3
+ *
4
+ * Environment variables:
5
+ * PLANNOTATOR_REMOTE - Set to "1"/"true" to force remote, "0"/"false" to force local
6
+ * PLANNOTATOR_PORT - Fixed port to use (default: random locally, 19432 for remote)
7
+ *
8
+ * Legacy (still supported): SSH_TTY, SSH_CONNECTION
9
+ */
10
+
11
+ const DEFAULT_REMOTE_PORT = 19432;
12
+ const LOOPBACK_HOST = "127.0.0.1";
13
+
14
+ function getRemoteOverride(): boolean | null {
15
+ const remote = process.env.PLANNOTATOR_REMOTE;
16
+ if (remote === undefined) {
17
+ return null;
18
+ }
19
+
20
+ if (remote === "1" || remote?.toLowerCase() === "true") {
21
+ return true;
22
+ }
23
+
24
+ if (remote === "0" || remote?.toLowerCase() === "false") {
25
+ return false;
26
+ }
27
+
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * Check if running in a remote session (SSH, devcontainer, etc.)
33
+ */
34
+ export function isRemoteSession(): boolean {
35
+ const remoteOverride = getRemoteOverride();
36
+ if (remoteOverride !== null) {
37
+ return remoteOverride;
38
+ }
39
+
40
+ // Legacy: SSH_TTY/SSH_CONNECTION (deprecated, silent)
41
+ if (process.env.SSH_TTY || process.env.SSH_CONNECTION) {
42
+ return true;
43
+ }
44
+
45
+ return false;
46
+ }
47
+
48
+ /**
49
+ * Get the server port to use
50
+ */
51
+ export function getServerPort(): number {
52
+ // Explicit port from environment takes precedence
53
+ const envPort = process.env.PLANNOTATOR_PORT;
54
+ if (envPort) {
55
+ const parsed = parseInt(envPort, 10);
56
+ if (!isNaN(parsed) && parsed >= 0 && parsed < 65536) {
57
+ return parsed;
58
+ }
59
+ console.error(
60
+ `[Plannotator] Warning: Invalid PLANNOTATOR_PORT "${envPort}", using default`
61
+ );
62
+ }
63
+
64
+ // Remote sessions use fixed port for port forwarding; local uses random
65
+ return isRemoteSession() ? DEFAULT_REMOTE_PORT : 0;
66
+ }
67
+
68
+ /**
69
+ * Bind local sessions to loopback, but keep remote sessions reachable via the
70
+ * container or host network interface for SSH/devcontainer/Docker forwarding.
71
+ */
72
+ export function getServerHostname(): string {
73
+ return isRemoteSession() ? "0.0.0.0" : LOOPBACK_HOST;
74
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Repository Info Detection
3
+ *
4
+ * Detects org/repo from git remote with fallback chain.
5
+ * Priority: org/repo from remote → repo name → directory name
6
+ */
7
+
8
+ import { $ } from "bun";
9
+
10
+ import type { RepoInfo } from "@plannotator/shared/repo";
11
+ import { parseRemoteUrl, parseRemoteHost, getDirName } from "@plannotator/shared/repo";
12
+
13
+ /**
14
+ * Get current git branch
15
+ */
16
+ async function getCurrentBranch(): Promise<string | undefined> {
17
+ try {
18
+ const result = await $`git rev-parse --abbrev-ref HEAD`.quiet().nothrow();
19
+ if (result.exitCode === 0) {
20
+ const branch = result.stdout.toString().trim();
21
+ return branch && branch !== "HEAD" ? branch : undefined;
22
+ }
23
+ } catch {
24
+ // Not in a git repo
25
+ }
26
+ return undefined;
27
+ }
28
+
29
+ /**
30
+ * Detect repository info with fallback chain
31
+ *
32
+ * 1. Try org/repo from git remote origin
33
+ * 2. Fall back to git repo root directory name
34
+ * 3. Fall back to current working directory name
35
+ */
36
+ export async function getRepoInfo(): Promise<RepoInfo | null> {
37
+ let branch: string | undefined;
38
+
39
+ // Try git remote URL first
40
+ try {
41
+ const result = await $`git remote get-url origin`.quiet().nothrow();
42
+ if (result.exitCode === 0) {
43
+ const remoteUrl = result.stdout.toString().trim();
44
+ const orgRepo = parseRemoteUrl(remoteUrl);
45
+ if (orgRepo) {
46
+ branch = await getCurrentBranch();
47
+ const host = parseRemoteHost(remoteUrl) ?? undefined;
48
+ return { display: orgRepo, branch, host };
49
+ }
50
+ }
51
+ } catch {
52
+ // Git not available
53
+ }
54
+
55
+ // Fallback: git repo root name
56
+ try {
57
+ const result = await $`git rev-parse --show-toplevel`.quiet().nothrow();
58
+ if (result.exitCode === 0) {
59
+ const repoName = getDirName(result.stdout.toString());
60
+ if (repoName) {
61
+ branch = await getCurrentBranch();
62
+ return { display: repoName, branch };
63
+ }
64
+ }
65
+ } catch {
66
+ // Not in a git repo
67
+ }
68
+
69
+ // Final fallback: current directory (no branch - not a git repo)
70
+ try {
71
+ const dirName = getDirName(process.cwd());
72
+ if (dirName) {
73
+ return { display: dirName };
74
+ }
75
+ } catch {
76
+ // Shouldn't happen
77
+ }
78
+
79
+ return null;
80
+ }
@@ -0,0 +1,310 @@
1
+ /**
2
+ * File Resolution Tests
3
+ *
4
+ * Run: bun test packages/server/resolve-file.test.ts
5
+ */
6
+
7
+ import { afterEach, describe, expect, test } from "bun:test";
8
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { homedir, tmpdir } from "node:os";
10
+ import { join, resolve } from "node:path";
11
+ import {
12
+ expandHomePath,
13
+ isAbsoluteUserPath,
14
+ normalizeUserPathInput,
15
+ resolveMarkdownFile,
16
+ resolveUserPath,
17
+ } from "@plannotator/shared/resolve-file";
18
+
19
+ const tempDirs: string[] = [];
20
+
21
+ function createTempProject(
22
+ files: Record<string, string> = {},
23
+ baseDir = join(tmpdir(), "plannotator-resolve-file-"),
24
+ ): string {
25
+ const root = mkdtempSync(baseDir);
26
+ tempDirs.push(root);
27
+ for (const [relativePath, content] of Object.entries(files)) {
28
+ const full = join(root, relativePath);
29
+ mkdirSync(join(full, ".."), { recursive: true });
30
+ writeFileSync(full, content);
31
+ }
32
+ return root;
33
+ }
34
+
35
+ afterEach(() => {
36
+ for (const dir of tempDirs.splice(0)) {
37
+ rmSync(dir, { recursive: true, force: true });
38
+ }
39
+ });
40
+
41
+ // --- User path normalization ---
42
+
43
+ describe("normalizeUserPathInput", () => {
44
+ test("expands tilde paths before normalization", () => {
45
+ expect(normalizeUserPathInput("~/test-plan.md")).toBe(
46
+ join(homedir(), "test-plan.md"),
47
+ );
48
+ });
49
+
50
+ test("strips wrapping quotes", () => {
51
+ expect(normalizeUserPathInput('"~/test-plan.md"')).toBe(
52
+ join(homedir(), "test-plan.md"),
53
+ );
54
+ });
55
+
56
+ test("converts MSYS paths on Windows", () => {
57
+ expect(normalizeUserPathInput("/c/Users/dev/test-plan.md", "win32")).toBe(
58
+ "C:/Users/dev/test-plan.md",
59
+ );
60
+ });
61
+
62
+ test("converts Cygwin paths on Windows", () => {
63
+ expect(normalizeUserPathInput("/cygdrive/c/Users/dev/test-plan.md", "win32")).toBe(
64
+ "C:/Users/dev/test-plan.md",
65
+ );
66
+ });
67
+
68
+ test("leaves non-Windows paths unchanged", () => {
69
+ expect(normalizeUserPathInput("/Users/dev/test-plan.md", "darwin")).toBe(
70
+ "/Users/dev/test-plan.md",
71
+ );
72
+ });
73
+ });
74
+
75
+ describe("expandHomePath", () => {
76
+ test("expands bare home alias", () => {
77
+ expect(expandHomePath("~", "/tmp/home")).toBe("/tmp/home");
78
+ });
79
+
80
+ test("expands home-relative paths", () => {
81
+ expect(expandHomePath("~/docs/plan.md", "/tmp/home")).toBe(
82
+ join("/tmp/home", "docs/plan.md"),
83
+ );
84
+ });
85
+
86
+ test("does not expand tilde usernames", () => {
87
+ expect(expandHomePath("~alice/docs/plan.md", "/tmp/home")).toBe(
88
+ "~alice/docs/plan.md",
89
+ );
90
+ });
91
+ });
92
+
93
+ describe("isAbsoluteUserPath", () => {
94
+ test("detects Windows drive letter paths", () => {
95
+ expect(isAbsoluteUserPath("C:\\Users\\dev\\test-plan.md", "win32")).toBe(true);
96
+ expect(isAbsoluteUserPath("C:/Users/dev/test-plan.md", "win32")).toBe(true);
97
+ });
98
+
99
+ test("detects converted MSYS paths as absolute on Windows", () => {
100
+ expect(isAbsoluteUserPath("/c/Users/dev/test-plan.md", "win32")).toBe(true);
101
+ });
102
+ });
103
+
104
+ describe("resolveUserPath", () => {
105
+ test("resolves relative paths against a base directory", () => {
106
+ expect(resolveUserPath("docs/plan.md", "/tmp/project")).toBe(
107
+ resolve("/tmp/project", "docs/plan.md"),
108
+ );
109
+ });
110
+
111
+ test("resolves quoted tilde paths", () => {
112
+ expect(resolveUserPath('"~/docs/plan.md"')).toBe(
113
+ resolve(homedir(), "docs/plan.md"),
114
+ );
115
+ });
116
+
117
+ test("returns empty string for whitespace-only input", () => {
118
+ expect(resolveUserPath(" ", "/tmp/project")).toBe("");
119
+ expect(resolveUserPath("", "/tmp/project")).toBe("");
120
+ });
121
+ });
122
+
123
+ // --- Core resolution strategies ---
124
+
125
+ describe("resolveMarkdownFile", () => {
126
+ // Strategy 1: Absolute paths
127
+
128
+ test("resolves absolute path to existing file", async () => {
129
+ const root = createTempProject({ "plan.md": "# Plan" });
130
+ const absPath = resolve(root, "plan.md");
131
+ const result = resolveMarkdownFile(absPath, root);
132
+ expect(result).toEqual({ kind: "found", path: absPath });
133
+ });
134
+
135
+ test("resolves tilde-prefixed absolute paths", async () => {
136
+ const homeRoot = createTempProject({}, join(homedir(), ".plannotator-resolve-file-"));
137
+ const absPath = resolve(homeRoot, "plan.md");
138
+ writeFileSync(absPath, "# Plan");
139
+ const relativeToHome = absPath.slice(homedir().length + 1).replace(/\\/g, "/");
140
+ const result = resolveMarkdownFile(`~/${relativeToHome}`, "/unused");
141
+ expect(result).toEqual({ kind: "found", path: absPath });
142
+ });
143
+
144
+ test("returns not_found for absolute path that doesn't exist", async () => {
145
+ const root = createTempProject();
146
+ const result = resolveMarkdownFile("/nonexistent/path.md", root);
147
+ expect(result.kind).toBe("not_found");
148
+ });
149
+
150
+ // Strategy 2: Exact relative paths
151
+
152
+ test("resolves exact relative path", async () => {
153
+ const root = createTempProject({ "docs/guide.md": "# Guide" });
154
+ const result = resolveMarkdownFile("docs/guide.md", root);
155
+ expect(result).toEqual({
156
+ kind: "found",
157
+ path: resolve(root, "docs/guide.md"),
158
+ });
159
+ });
160
+
161
+ test("resolves bare filename in root", async () => {
162
+ const root = createTempProject({ "README.md": "# Hello" });
163
+ const result = resolveMarkdownFile("README.md", root);
164
+ expect(result).toEqual({
165
+ kind: "found",
166
+ path: resolve(root, "README.md"),
167
+ });
168
+ });
169
+
170
+ test("resolves @ filename via fallback when @ file does not exist", async () => {
171
+ const root = createTempProject({ "README.md": "# Hello" });
172
+ const result = resolveMarkdownFile("@README.md", root);
173
+ expect(result).toEqual({
174
+ kind: "found",
175
+ path: resolve(root, "README.md"),
176
+ });
177
+ });
178
+
179
+ test("prioritizes real @ filename before fallback", async () => {
180
+ const root = createTempProject({ "@README.md": "# At" });
181
+ const result = resolveMarkdownFile("@README.md", root);
182
+ expect(result).toEqual({
183
+ kind: "found",
184
+ path: resolve(root, "@README.md"),
185
+ });
186
+ });
187
+
188
+ test("resolves quoted @ filename", async () => {
189
+ const root = createTempProject({ "README.md": "# Hello" });
190
+ const result = resolveMarkdownFile('"@README.md"', root);
191
+ expect(result).toEqual({
192
+ kind: "found",
193
+ path: resolve(root, "README.md"),
194
+ });
195
+ });
196
+
197
+ test("resolves relative paths with Windows separators", async () => {
198
+ const root = createTempProject({ "docs/test-plan.md": "# Test plan\n" });
199
+ const result = resolveMarkdownFile("docs\\test-plan.md", root);
200
+ expect(result).toEqual({
201
+ kind: "found",
202
+ path: resolve(root, "docs/test-plan.md"),
203
+ });
204
+ });
205
+
206
+ // Strategy 3: Case-insensitive search
207
+
208
+ test("finds bare filenames case-insensitively", async () => {
209
+ const root = createTempProject({ "notes/Architecture.MD": "# Architecture\n" });
210
+ const result = resolveMarkdownFile("architecture.md", root);
211
+ expect(result).toEqual({
212
+ kind: "found",
213
+ path: resolve(root, "notes/Architecture.MD"),
214
+ });
215
+ });
216
+
217
+ test("finds relative paths case-insensitively", async () => {
218
+ const root = createTempProject({ "Docs/Specs/Design.MDX": "# Design\n" });
219
+ const result = resolveMarkdownFile("docs/specs/design.mdx", root);
220
+ expect(result.kind).toBe("found");
221
+ if (result.kind === "found") {
222
+ expect(await Bun.file(result.path).text()).toBe("# Design\n");
223
+ }
224
+ });
225
+
226
+ test("returns ambiguous when bare filename matches multiple files", async () => {
227
+ const root = createTempProject({
228
+ "docs/plan.md": "# Plan 1",
229
+ "api/plan.md": "# Plan 2",
230
+ });
231
+ const result = resolveMarkdownFile("plan.md", root);
232
+ expect(result.kind).toBe("ambiguous");
233
+ if (result.kind === "ambiguous") {
234
+ expect(result.matches).toHaveLength(2);
235
+ }
236
+ });
237
+
238
+ test("returns ambiguous in @ fallback when target exists multiple times", async () => {
239
+ const root = createTempProject({
240
+ "docs/plan.md": "# Plan 1",
241
+ "api/plan.md": "# Plan 2",
242
+ });
243
+ const result = resolveMarkdownFile("@plan.md", root);
244
+ expect(result.kind).toBe("ambiguous");
245
+ if (result.kind === "ambiguous") {
246
+ expect(result.input).toBe("@plan.md");
247
+ expect(result.matches).toHaveLength(2);
248
+ }
249
+ });
250
+
251
+ // Ignored directories
252
+
253
+ test("skips node_modules", async () => {
254
+ const root = createTempProject({
255
+ "node_modules/pkg/README.md": "# Pkg",
256
+ });
257
+ const result = resolveMarkdownFile("readme.md", root);
258
+ expect(result.kind).toBe("not_found");
259
+ });
260
+
261
+ test("skips .git directory", async () => {
262
+ const root = createTempProject({
263
+ ".git/hooks/pre-commit.md": "# hook",
264
+ });
265
+ const result = resolveMarkdownFile("pre-commit.md", root);
266
+ expect(result.kind).toBe("not_found");
267
+ });
268
+
269
+ // Extension filtering
270
+
271
+ test("rejects non-markdown files", async () => {
272
+ const root = createTempProject({ "script.ts": "export {}" });
273
+ const result = resolveMarkdownFile("script.ts", root);
274
+ expect(result.kind).toBe("not_found");
275
+ });
276
+
277
+ test("accepts .mdx files", async () => {
278
+ const root = createTempProject({ "page.mdx": "# Page" });
279
+ const result = resolveMarkdownFile("page.mdx", root);
280
+ expect(result).toEqual({
281
+ kind: "found",
282
+ path: resolve(root, "page.mdx"),
283
+ });
284
+ });
285
+
286
+ // Edge cases
287
+
288
+ test("returns not_found for nonexistent file", async () => {
289
+ const root = createTempProject();
290
+ const result = resolveMarkdownFile("nope.md", root);
291
+ expect(result.kind).toBe("not_found");
292
+ });
293
+
294
+ test("returns not_found for @ path that cannot be resolved", async () => {
295
+ const root = createTempProject();
296
+ const result = resolveMarkdownFile("@nope.md", root);
297
+ expect(result).toEqual({ kind: "not_found", input: "@nope.md" });
298
+ });
299
+
300
+ test("handles deeply nested files", async () => {
301
+ const root = createTempProject({
302
+ "a/b/c/d/deep.md": "# Deep",
303
+ });
304
+ const result = resolveMarkdownFile("deep.md", root);
305
+ expect(result).toEqual({
306
+ kind: "found",
307
+ path: resolve(root, "a/b/c/d/deep.md"),
308
+ });
309
+ });
310
+ });