@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,107 @@
1
+ /**
2
+ * Parse CLI-style args arriving as a single whitespace-delimited string.
3
+ *
4
+ * Extracts known annotate flags from the remainder, which is treated as the
5
+ * target path. Leading `@` is
6
+ * stripped via the shared at-reference helper — reference-mode is primary.
7
+ * Scoped-package-style literal `@` paths are handled by a fallback that the
8
+ * downstream resolver opts into (see at-reference.ts).
9
+ *
10
+ * Used by the OpenCode plugin and Pi extension, where the whole args string
11
+ * arrives pre-joined from the harness slash-command dispatcher. The Claude
12
+ * Code binary parses argv directly with indexOf/splice and does not use
13
+ * this helper.
14
+ *
15
+ * Implementation: walks the raw string once, preserving whitespace runs and
16
+ * non-whitespace tokens as separate segments. Only known flag tokens
17
+ * (whole-word match) plus one adjacent whitespace run are removed.
18
+ * This keeps double-spaces and tabs inside file paths intact — which
19
+ * matches the pre-PR behavior on `main`, where OpenCode and Pi passed
20
+ * the raw args string straight through to the filesystem resolver.
21
+ *
22
+ * Remaining edge: if a path literally contains a known flag as a standalone
23
+ * whitespace-separated token (e.g. `"Feature --gate spec.md"`), that token
24
+ * is stripped. Supporting this would need shell-style quoting, which isn't
25
+ * worth the complexity for a vanishingly rare naming pattern.
26
+ */
27
+
28
+ import { stripAtPrefix } from "./at-reference";
29
+ import { stripWrappingQuotes } from "./resolve-file";
30
+
31
+ export interface ParsedAnnotateArgs {
32
+ /**
33
+ * Primary resolution path with any leading `@` stripped (reference-mode
34
+ * convention). Most call sites should use this directly.
35
+ */
36
+ filePath: string;
37
+ /**
38
+ * Raw path with the `@` prefix preserved (if the user supplied one).
39
+ * Callers that want the literal-`@` fallback for scoped-package-style
40
+ * paths pair this with `resolveAtReference` from at-reference.ts.
41
+ */
42
+ rawFilePath: string;
43
+ gate: boolean;
44
+ json: boolean;
45
+ hook: boolean;
46
+ renderHtml: boolean;
47
+ noJina: boolean;
48
+ }
49
+
50
+ type Segment = { type: "ws" | "tok"; text: string };
51
+
52
+ const FLAG_MAP = {
53
+ "--gate": "gate",
54
+ "--json": "json",
55
+ "--hook": "hook",
56
+ "--render-html": "renderHtml",
57
+ "--no-jina": "noJina",
58
+ } as const satisfies Record<string, keyof Omit<ParsedAnnotateArgs, "filePath" | "rawFilePath">>;
59
+
60
+ export function parseAnnotateArgs(raw: string): ParsedAnnotateArgs {
61
+ const s = (raw ?? "").trim();
62
+ const flags = { gate: false, json: false, hook: false, renderHtml: false, noJina: false };
63
+
64
+ const segments: Segment[] = [];
65
+ for (let i = 0; i < s.length;) {
66
+ const isWs = /\s/.test(s[i]);
67
+ const start = i;
68
+ while (i < s.length && /\s/.test(s[i]) === isWs) i++;
69
+ segments.push({ type: isWs ? "ws" : "tok", text: s.slice(start, i) });
70
+ }
71
+
72
+ const keep = segments.map(() => true);
73
+ for (let j = 0; j < segments.length; j++) {
74
+ const seg = segments[j];
75
+ if (seg.type !== "tok") continue;
76
+ const key = FLAG_MAP[seg.text as keyof typeof FLAG_MAP];
77
+ if (!key) continue;
78
+
79
+ flags[key] = true;
80
+ keep[j] = false;
81
+
82
+ // Drop one adjacent whitespace run so removed flags don't leave dangling
83
+ // spaces. Prefer trailing whitespace; fall back to leading if at the end.
84
+ if (j + 1 < segments.length && segments[j + 1].type === "ws") {
85
+ keep[j + 1] = false;
86
+ } else if (j > 0 && segments[j - 1].type === "ws") {
87
+ keep[j - 1] = false;
88
+ }
89
+ }
90
+
91
+ // Trim covers the case where two adjacent flags (`... --gate --json`)
92
+ // both claim the single whitespace between them, leaving a trailing space
93
+ // after the kept token. Wrapping quotes come from OpenCode/Pi users who
94
+ // quote paths with spaces (shell muscle memory); strip them here so
95
+ // downstream callers never see tokenization artifacts.
96
+ const rawFilePath = stripWrappingQuotes(
97
+ segments
98
+ .filter((_, j) => keep[j])
99
+ .map((seg) => seg.text)
100
+ .join("")
101
+ .trim(),
102
+ );
103
+
104
+ if (flags.hook) flags.gate = true;
105
+
106
+ return { filePath: stripAtPrefix(rawFilePath), rawFilePath, ...flags };
107
+ }
@@ -0,0 +1,99 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { stripAtPrefix, resolveAtReference } from "./at-reference";
3
+
4
+ // The `@foo.md` convention — popularised by Claude Code but supported by
5
+ // several harnesses — treats `@` as a reference marker, not part of the
6
+ // filename. These helpers exist so every harness strips the same way and
7
+ // optionally falls back to the literal path for scoped-package-style names.
8
+
9
+ describe("stripAtPrefix", () => {
10
+ test("removes a single leading @", () => {
11
+ expect(stripAtPrefix("@foo.md")).toBe("foo.md");
12
+ });
13
+
14
+ test("removes only one @ (does not recurse)", () => {
15
+ expect(stripAtPrefix("@@foo.md")).toBe("@foo.md");
16
+ });
17
+
18
+ test("leaves paths without @ unchanged", () => {
19
+ expect(stripAtPrefix("foo.md")).toBe("foo.md");
20
+ });
21
+
22
+ test("leaves @ that is not at the start unchanged", () => {
23
+ expect(stripAtPrefix("dir/@foo.md")).toBe("dir/@foo.md");
24
+ });
25
+
26
+ test("strips @ from scoped-package-style paths", () => {
27
+ expect(stripAtPrefix("@scope/pkg/README.md")).toBe("scope/pkg/README.md");
28
+ });
29
+
30
+ test("handles empty string", () => {
31
+ expect(stripAtPrefix("")).toBe("");
32
+ });
33
+
34
+ // Wrapping quotes come from harnesses that tokenize on whitespace (OpenCode,
35
+ // Pi). Users have to quote paths with spaces: `"@My Notes.md"`. Without
36
+ // unwrapping the quotes first, stripAtPrefix would never see the `@`.
37
+ test("strips wrapping double quotes before stripping @", () => {
38
+ expect(stripAtPrefix(`"@foo.md"`)).toBe("foo.md");
39
+ });
40
+
41
+ test("strips wrapping single quotes before stripping @", () => {
42
+ expect(stripAtPrefix(`'@foo.md'`)).toBe("foo.md");
43
+ });
44
+
45
+ test("strips wrapping quotes around a path with spaces", () => {
46
+ expect(stripAtPrefix(`"@My Notes.md"`)).toBe("My Notes.md");
47
+ });
48
+
49
+ test("strips wrapping quotes when no @ present", () => {
50
+ expect(stripAtPrefix(`"foo.md"`)).toBe("foo.md");
51
+ });
52
+
53
+ test("leaves mismatched quotes alone (not wrapping)", () => {
54
+ expect(stripAtPrefix(`"@foo.md`)).toBe(`"@foo.md`);
55
+ expect(stripAtPrefix(`@foo.md"`)).toBe(`foo.md"`);
56
+ });
57
+ });
58
+
59
+ describe("resolveAtReference", () => {
60
+ // Primary behavior: stripped form wins if it resolves.
61
+ test("returns the stripped path when it resolves", () => {
62
+ const exists = (p: string) => p === "foo.md";
63
+ expect(resolveAtReference("@foo.md", exists)).toBe("foo.md");
64
+ });
65
+
66
+ // Fallback: literal path used only when stripped form doesn't resolve.
67
+ test("falls back to the literal path when only that resolves", () => {
68
+ const exists = (p: string) => p === "@scope/pkg/README.md";
69
+ expect(resolveAtReference("@scope/pkg/README.md", exists)).toBe("@scope/pkg/README.md");
70
+ });
71
+
72
+ // When BOTH resolve, stripped form wins (reference-mode primacy).
73
+ test("prefers the stripped path when both resolve (reference wins)", () => {
74
+ const exists = (_p: string) => true;
75
+ expect(resolveAtReference("@foo.md", exists)).toBe("foo.md");
76
+ });
77
+
78
+ // Returns null when neither candidate resolves — caller handles the error.
79
+ test("returns null when neither candidate resolves", () => {
80
+ const exists = (_p: string) => false;
81
+ expect(resolveAtReference("@nope.md", exists)).toBeNull();
82
+ });
83
+
84
+ // Inputs without @ have no fallback, just a single existence check.
85
+ test("handles non-@ inputs with a single check", () => {
86
+ let calls = 0;
87
+ const exists = (p: string) => { calls++; return p === "plain.md"; };
88
+ expect(resolveAtReference("plain.md", exists)).toBe("plain.md");
89
+ expect(calls).toBe(1);
90
+ });
91
+
92
+ // Non-@ input that doesn't resolve returns null without a retry.
93
+ test("returns null for non-@ input that doesn't resolve", () => {
94
+ let calls = 0;
95
+ const exists = (_p: string) => { calls++; return false; };
96
+ expect(resolveAtReference("missing.md", exists)).toBeNull();
97
+ expect(calls).toBe(1);
98
+ });
99
+ });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `@`-reference handling for user-provided paths.
3
+ *
4
+ * Several agent harnesses (Claude Code, OpenCode, Pi) let users reference
5
+ * files with an `@` prefix, e.g. `@README.md`. The `@` is the team's
6
+ * reference marker, not part of the filename. Stripping it is the primary
7
+ * resolution path — that's the common case and it's supported first-class.
8
+ *
9
+ * The secondary path handles scoped-package-style names like
10
+ * `@scope/pkg/README.md`: if the stripped form doesn't resolve, fall back
11
+ * to the literal form so those paths still open.
12
+ *
13
+ * Both functions are pure and take any filesystem-ish predicate via a
14
+ * callback, so they're trivial to unit-test without stubbing anything.
15
+ */
16
+
17
+ import { stripWrappingQuotes } from "./resolve-file";
18
+
19
+ /**
20
+ * Normalize a user-typed path reference by unwrapping matching `"..."` or
21
+ * `'...'` quotes and removing a single leading `@`. Quotes come from
22
+ * harnesses that tokenize on whitespace (OpenCode, Pi), where paths
23
+ * containing spaces have to be quoted. The quote-stripping has to run
24
+ * first so the `@` check sees the real first character.
25
+ *
26
+ * Non-`@` inputs are returned unchanged except for quote unwrapping.
27
+ * Does not recurse: `@@foo` becomes `@foo`, not `foo`.
28
+ */
29
+ export function stripAtPrefix(input: string): string {
30
+ const unquoted = stripWrappingQuotes(input);
31
+ return unquoted.startsWith("@") ? unquoted.slice(1) : unquoted;
32
+ }
33
+
34
+ /**
35
+ * Resolve an `@`-prefixed user input by trying the stripped form first
36
+ * (reference mode, primary) and falling back to the literal form if the
37
+ * stripped form doesn't resolve. Returns the candidate that resolves, or
38
+ * null if neither does.
39
+ *
40
+ * `exists` defines what "resolves" means — use `existsSync` for a bare
41
+ * filesystem check, or wrap `resolveMarkdownFile` / `statSync` for richer
42
+ * predicates. The helper itself is filesystem-agnostic.
43
+ */
44
+ export function resolveAtReference(
45
+ input: string,
46
+ exists: (candidate: string) => boolean,
47
+ ): string | null {
48
+ const stripped = stripAtPrefix(input);
49
+ if (exists(stripped)) return stripped;
50
+ if (stripped !== input && exists(input)) return input;
51
+ return null;
52
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Checklist parsing and progress tracking utilities.
3
+ *
4
+ * Shared between Pi extension and OpenCode plugin for plan execution tracking.
5
+ */
6
+
7
+ export interface ChecklistItem {
8
+ /** 1-based step number, compatible with markCompletedSteps/extractDoneSteps. */
9
+ step: number;
10
+ text: string;
11
+ completed: boolean;
12
+ }
13
+
14
+ /**
15
+ * Parse standard markdown checkboxes from file content.
16
+ *
17
+ * Matches lines like:
18
+ * - [ ] Step description
19
+ * - [x] Completed step
20
+ * * [ ] Alternative bullet
21
+ */
22
+ export function parseChecklist(content: string): ChecklistItem[] {
23
+ const items: ChecklistItem[] = [];
24
+ const pattern = /^[-*]\s*\[([ xX])\]\s+(.+)$/gm;
25
+
26
+ for (const match of content.matchAll(pattern)) {
27
+ const completed = match[1] !== " ";
28
+ const text = match[2].trim();
29
+ if (text.length > 0) {
30
+ items.push({ step: items.length + 1, text, completed });
31
+ }
32
+ }
33
+ return items;
34
+ }
35
+
36
+ export function extractDoneSteps(message: string): number[] {
37
+ const steps: number[] = [];
38
+ for (const match of message.matchAll(/\[DONE:(\d+)\]/gi)) {
39
+ const step = Number(match[1]);
40
+ if (Number.isFinite(step)) steps.push(step);
41
+ }
42
+ return steps;
43
+ }
44
+
45
+ export function markCompletedSteps(text: string, items: ChecklistItem[]): number {
46
+ const doneSteps = extractDoneSteps(text);
47
+ for (const step of doneSteps) {
48
+ const item = items.find((t) => t.step === step);
49
+ if (item) item.completed = true;
50
+ }
51
+ return doneSteps.length;
52
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ isCodeFilePath,
4
+ isCodeFilePathStrict,
5
+ isPlausibleCodeFilePath,
6
+ CODE_PATH_BARE_REGEX,
7
+ } from './code-file';
8
+
9
+ describe('isCodeFilePath', () => {
10
+ test('matches common extensions', () => {
11
+ expect(isCodeFilePath('button.tsx')).toBe(true);
12
+ expect(isCodeFilePath('utils.ts')).toBe(true);
13
+ expect(isCodeFilePath('main.py')).toBe(true);
14
+ expect(isCodeFilePath('lib.rs')).toBe(true);
15
+ expect(isCodeFilePath('config.json')).toBe(true);
16
+ expect(isCodeFilePath('styles.css')).toBe(true);
17
+ });
18
+
19
+ test('matches paths with directories', () => {
20
+ expect(isCodeFilePath('src/components/Button.tsx')).toBe(true);
21
+ expect(isCodeFilePath('./utils/helpers.ts')).toBe(true);
22
+ expect(isCodeFilePath('../lib/main.py')).toBe(true);
23
+ });
24
+
25
+ test('matches special filenames', () => {
26
+ expect(isCodeFilePath('Dockerfile')).toBe(true);
27
+ expect(isCodeFilePath('Makefile')).toBe(true);
28
+ expect(isCodeFilePath('path/to/Dockerfile')).toBe(true);
29
+ });
30
+
31
+ test('strips hash fragments', () => {
32
+ expect(isCodeFilePath('src/foo.ts#L42')).toBe(true);
33
+ });
34
+
35
+ test('rejects URLs', () => {
36
+ expect(isCodeFilePath('https://github.com/foo.ts')).toBe(false);
37
+ expect(isCodeFilePath('http://example.com/main.py')).toBe(false);
38
+ });
39
+
40
+ test('rejects non-code files', () => {
41
+ expect(isCodeFilePath('.env')).toBe(false);
42
+ expect(isCodeFilePath('readme.txt')).toBe(false);
43
+ expect(isCodeFilePath('npm install')).toBe(false);
44
+ });
45
+ });
46
+
47
+ describe('isCodeFilePathStrict', () => {
48
+ test('requires a / separator', () => {
49
+ expect(isCodeFilePathStrict('button.tsx')).toBe(false);
50
+ expect(isCodeFilePathStrict('Dockerfile')).toBe(false);
51
+ expect(isCodeFilePathStrict('package.json')).toBe(false);
52
+ });
53
+
54
+ test('matches paths with directories', () => {
55
+ expect(isCodeFilePathStrict('src/components/Button.tsx')).toBe(true);
56
+ expect(isCodeFilePathStrict('./utils/helpers.ts')).toBe(true);
57
+ expect(isCodeFilePathStrict('../lib/main.py')).toBe(true);
58
+ expect(isCodeFilePathStrict('path/to/Makefile')).toBe(true);
59
+ });
60
+
61
+ test('rejects URLs even with /', () => {
62
+ expect(isCodeFilePathStrict('https://github.com/foo.ts')).toBe(false);
63
+ });
64
+
65
+ test('rejects non-code paths with /', () => {
66
+ expect(isCodeFilePathStrict('path/to/readme.txt')).toBe(false);
67
+ expect(isCodeFilePathStrict('some/dir/.env')).toBe(false);
68
+ });
69
+
70
+ test('rejects shape-implausible bare prose', () => {
71
+ expect(isCodeFilePathStrict('packages/ui/{a,b}.ts')).toBe(false);
72
+ });
73
+ });
74
+
75
+ describe('isPlausibleCodeFilePath', () => {
76
+ test('accepts plain code paths', () => {
77
+ expect(isPlausibleCodeFilePath('packages/editor/App.tsx')).toBe(true);
78
+ expect(isPlausibleCodeFilePath('foo.ts')).toBe(true);
79
+ });
80
+
81
+ test('accepts Next.js dynamic routes', () => {
82
+ expect(isPlausibleCodeFilePath('app/[slug]/page.tsx')).toBe(true);
83
+ expect(isPlausibleCodeFilePath('app/[...rest]/page.tsx')).toBe(true);
84
+ });
85
+
86
+ test('rejects shell brace expansion', () => {
87
+ expect(isPlausibleCodeFilePath('packages/ui/{a,b,c}.ts')).toBe(false);
88
+ });
89
+
90
+ test('rejects glob wildcards', () => {
91
+ expect(isPlausibleCodeFilePath('src/*.ts')).toBe(false);
92
+ expect(isPlausibleCodeFilePath('src/foo?.ts')).toBe(false);
93
+ });
94
+
95
+ test('rejects whitespace', () => {
96
+ expect(isPlausibleCodeFilePath('path with space.ts')).toBe(false);
97
+ });
98
+ });
99
+
100
+ describe('CODE_PATH_BARE_REGEX', () => {
101
+ test('matches abbreviated paths', () => {
102
+ const re = new RegExp(CODE_PATH_BARE_REGEX.source, 'g');
103
+ const m = 'see editor/App.tsx for details'.match(re);
104
+ expect(m).toContain('editor/App.tsx');
105
+ });
106
+
107
+ test('matches Next.js dynamic-route paths', () => {
108
+ const re = new RegExp(CODE_PATH_BARE_REGEX.source, 'g');
109
+ const m = 'visit app/[slug]/page.tsx'.match(re);
110
+ expect(m).toContain('app/[slug]/page.tsx');
111
+ });
112
+ });
@@ -0,0 +1,41 @@
1
+ export const CODE_FILE_REGEX = /(?:\.(tsx?|jsx?|py|rb|go|rs|java|c|cpp|h|hpp|cs|swift|kt|scala|sh|bash|zsh|sql|graphql|json|ya?ml|toml|ini|css|scss|less|xml|tf|lua|r|dart|ex|exs|vue|svelte|astro|zig|proto)|(?:^|\/)(Dockerfile|Makefile|Rakefile|Gemfile|Procfile|Vagrantfile|Brewfile|Justfile))$/i;
2
+
3
+ export const CODE_PATH_BARE_REGEX = /(?:\.{0,2}\/)?(?:[a-zA-Z0-9_@.\-\[\]]+\/)+[a-zA-Z0-9_.\-\[\]]+\.[a-zA-Z0-9]+(?::\d+(?:-\d+)?)?/g;
4
+
5
+ const IMPLAUSIBLE_CHARS = /[{},*?\s]/;
6
+
7
+ export function isPlausibleCodeFilePath(input: string): boolean {
8
+ return !IMPLAUSIBLE_CHARS.test(input);
9
+ }
10
+
11
+ export interface ParsedCodePath {
12
+ filePath: string;
13
+ line?: number;
14
+ lineEnd?: number;
15
+ }
16
+
17
+ const LINE_SUFFIX_RE = /:(\d+)(?:-(\d+))?$/;
18
+
19
+ export function parseCodePath(input: string): ParsedCodePath {
20
+ const clean = input.replace(/#.*$/, '');
21
+ const m = clean.match(LINE_SUFFIX_RE);
22
+ if (!m) return { filePath: clean };
23
+ let line = Number.parseInt(m[1], 10);
24
+ let lineEnd = m[2] ? Number.parseInt(m[2], 10) : undefined;
25
+ if (lineEnd != null && lineEnd < line) { const tmp = line; line = lineEnd; lineEnd = tmp; }
26
+ return { filePath: clean.replace(LINE_SUFFIX_RE, ''), line, lineEnd };
27
+ }
28
+
29
+ export function stripLineRef(input: string): string {
30
+ return input.replace(/#.*$/, '').replace(LINE_SUFFIX_RE, '');
31
+ }
32
+
33
+ export function isCodeFilePath(input: string): boolean {
34
+ if (!isPlausibleCodeFilePath(input)) return false;
35
+ return CODE_FILE_REGEX.test(stripLineRef(input))
36
+ && !input.startsWith('http://') && !input.startsWith('https://');
37
+ }
38
+
39
+ export function isCodeFilePathStrict(input: string): boolean {
40
+ return input.includes('/') && isCodeFilePath(input);
41
+ }