pi-orchestrator-config 4.1.5

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 (175) hide show
  1. package/README.md +808 -0
  2. package/agents/api-documenter.md +32 -0
  3. package/agents/bash-expert.md +53 -0
  4. package/agents/code-reviewer-docs.md +145 -0
  5. package/agents/code-reviewer-guidelines.md +97 -0
  6. package/agents/code-reviewer-quality.md +142 -0
  7. package/agents/code-reviewer-security.md +107 -0
  8. package/agents/code-reviewer-spec.md +164 -0
  9. package/agents/debugger.md +63 -0
  10. package/agents/docker-expert.md +40 -0
  11. package/agents/docs-fetcher.md +48 -0
  12. package/agents/git-expert.md +63 -0
  13. package/agents/github-expert.md +83 -0
  14. package/agents/go-expert.md +62 -0
  15. package/agents/issue-reviewer-feasibility.md +60 -0
  16. package/agents/issue-reviewer-scope.md +63 -0
  17. package/agents/issue-reviewer-spec.md +77 -0
  18. package/agents/java-expert.md +31 -0
  19. package/agents/jenkins-expert.md +38 -0
  20. package/agents/kubernetes-expert.md +40 -0
  21. package/agents/planner.md +42 -0
  22. package/agents/python-expert.md +61 -0
  23. package/agents/reviewer.md +26 -0
  24. package/agents/scout.md +40 -0
  25. package/agents/security-auditor.md +254 -0
  26. package/agents/technical-documentation-writer.md +37 -0
  27. package/agents/test-automator.md +55 -0
  28. package/agents/test-runner.md +47 -0
  29. package/agents/ts-expert.md +23 -0
  30. package/agents/worker.md +30 -0
  31. package/extensions/acpx-provider/index.ts +104 -0
  32. package/extensions/acpx-provider/load-runtime.ts +99 -0
  33. package/extensions/acpx-provider/runtime-models.ts +40 -0
  34. package/extensions/cli-provider/agents/claude.ts +128 -0
  35. package/extensions/cli-provider/agents/cursor.ts +84 -0
  36. package/extensions/cli-provider/agents/gemini.ts +100 -0
  37. package/extensions/cli-provider/discover.ts +77 -0
  38. package/extensions/cli-provider/index.ts +57 -0
  39. package/extensions/cli-provider/parsers.ts +304 -0
  40. package/extensions/cli-provider/providers.ts +58 -0
  41. package/extensions/cli-provider/runner.ts +225 -0
  42. package/extensions/cli-provider/runtime-models.ts +32 -0
  43. package/extensions/cli-provider/session-reaper.ts +122 -0
  44. package/extensions/cli-provider/sessions.ts +533 -0
  45. package/extensions/cli-provider/shared/discover-cache.ts +235 -0
  46. package/extensions/cli-provider/types.ts +48 -0
  47. package/extensions/coms/coms-net-server.ts +1642 -0
  48. package/extensions/coms/coms-net-wrapper.ts +521 -0
  49. package/extensions/coms/coms-net.ts +1589 -0
  50. package/extensions/coms/coms-p2p.ts +1879 -0
  51. package/extensions/coms/coms-shared.ts +510 -0
  52. package/extensions/coms/coms-wrapper.ts +138 -0
  53. package/extensions/coms/index.ts +20 -0
  54. package/extensions/coms/themeMap.ts +147 -0
  55. package/extensions/image-gen/image-gen.ts +332 -0
  56. package/extensions/image-gen/index.ts +14 -0
  57. package/extensions/orchestrator/agents.ts +134 -0
  58. package/extensions/orchestrator/ask-user.ts +293 -0
  59. package/extensions/orchestrator/async-agents.ts +1037 -0
  60. package/extensions/orchestrator/async-capability.ts +129 -0
  61. package/extensions/orchestrator/async-runner.ts +324 -0
  62. package/extensions/orchestrator/async-status-parse.ts +31 -0
  63. package/extensions/orchestrator/async-status-ui.ts +201 -0
  64. package/extensions/orchestrator/async-wait.ts +36 -0
  65. package/extensions/orchestrator/btw.ts +210 -0
  66. package/extensions/orchestrator/cron-status-format.ts +126 -0
  67. package/extensions/orchestrator/cron-status-ui.ts +132 -0
  68. package/extensions/orchestrator/cron.ts +730 -0
  69. package/extensions/orchestrator/dreaming.ts +355 -0
  70. package/extensions/orchestrator/enforcement-helpers.ts +530 -0
  71. package/extensions/orchestrator/enforcement-rules.ts +329 -0
  72. package/extensions/orchestrator/enforcement.ts +828 -0
  73. package/extensions/orchestrator/extended-autocomplete.ts +593 -0
  74. package/extensions/orchestrator/git-helpers.ts +420 -0
  75. package/extensions/orchestrator/github-autocomplete.ts +250 -0
  76. package/extensions/orchestrator/icons.ts +17 -0
  77. package/extensions/orchestrator/index.ts +100 -0
  78. package/extensions/orchestrator/memory-embeddings.ts +281 -0
  79. package/extensions/orchestrator/memory-promotion.ts +449 -0
  80. package/extensions/orchestrator/memory-provenance.ts +80 -0
  81. package/extensions/orchestrator/memory-query-class.ts +90 -0
  82. package/extensions/orchestrator/memory-scoring.ts +434 -0
  83. package/extensions/orchestrator/memory-tools.ts +883 -0
  84. package/extensions/orchestrator/memory-tree.ts +212 -0
  85. package/extensions/orchestrator/nvim.ts +169 -0
  86. package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
  87. package/extensions/orchestrator/overlay-dashboard.ts +512 -0
  88. package/extensions/orchestrator/pi-config-review-state.ts +303 -0
  89. package/extensions/orchestrator/preference-extractor.ts +106 -0
  90. package/extensions/orchestrator/project-settings.ts +468 -0
  91. package/extensions/orchestrator/promotion-queue.ts +311 -0
  92. package/extensions/orchestrator/resolve-agent-model.ts +32 -0
  93. package/extensions/orchestrator/review-ui.ts +204 -0
  94. package/extensions/orchestrator/rule-placeholders.ts +12 -0
  95. package/extensions/orchestrator/rules.ts +481 -0
  96. package/extensions/orchestrator/session-search.ts +243 -0
  97. package/extensions/orchestrator/session-validation.ts +374 -0
  98. package/extensions/orchestrator/situation-report.ts +370 -0
  99. package/extensions/orchestrator/status-bar.ts +31 -0
  100. package/extensions/orchestrator/status-line.ts +192 -0
  101. package/extensions/orchestrator/status.ts +110 -0
  102. package/extensions/orchestrator/subagent-tool.ts +1748 -0
  103. package/extensions/orchestrator/task-lifecycle.ts +88 -0
  104. package/extensions/orchestrator/utils.ts +227 -0
  105. package/extensions/pidash/index.ts +15 -0
  106. package/extensions/pidash/pidash.ts +941 -0
  107. package/extensions/pidiff/index.ts +13 -0
  108. package/extensions/pidiff/pidiff.ts +369 -0
  109. package/extensions/providers/acpx-driver.ts +479 -0
  110. package/extensions/providers/built-in-drivers.ts +52 -0
  111. package/extensions/providers/claude-driver.ts +319 -0
  112. package/extensions/providers/cursor-acpx-driver.ts +398 -0
  113. package/extensions/providers/cursor-cli-driver.ts +484 -0
  114. package/extensions/providers/gemini-driver.ts +302 -0
  115. package/extensions/providers/index.ts +513 -0
  116. package/extensions/shared/build-system-prompt.ts +57 -0
  117. package/extensions/shared/create-runtime-provider.ts +190 -0
  118. package/extensions/shared/daemon-manager.ts +248 -0
  119. package/extensions/shared/file-logger.ts +115 -0
  120. package/extensions/shared/managed-refresh.ts +211 -0
  121. package/extensions/shared/provider-driver.ts +295 -0
  122. package/extensions/shared/provider-errors.ts +305 -0
  123. package/extensions/shared/provider-registry.ts +388 -0
  124. package/extensions/shared/stream-builder.ts +275 -0
  125. package/extensions/shared/types.ts +20 -0
  126. package/extensions/shared/ui/README.md +10 -0
  127. package/extensions/shared/ui/badge.tsx +52 -0
  128. package/extensions/shared/ui/button.tsx +58 -0
  129. package/extensions/shared/ui/collapsible.tsx +21 -0
  130. package/extensions/shared/ui/dropdown-menu.tsx +266 -0
  131. package/extensions/shared/ui/input.tsx +20 -0
  132. package/extensions/shared/ui/popover.tsx +114 -0
  133. package/extensions/shared/ui/resizable.tsx +48 -0
  134. package/extensions/shared/ui/scroll-area.tsx +53 -0
  135. package/extensions/shared/ui/select.tsx +201 -0
  136. package/extensions/shared/ui/separator.tsx +25 -0
  137. package/extensions/shared/ui/tooltip.tsx +64 -0
  138. package/extensions/shared/ws-client.ts +74 -0
  139. package/package.json +59 -0
  140. package/prompts/coderabbit-rate-limit.md +95 -0
  141. package/prompts/create-coms-feature-manager.md +186 -0
  142. package/prompts/create-skill.md +128 -0
  143. package/prompts/domain-model.md +109 -0
  144. package/prompts/external-ai.md +665 -0
  145. package/prompts/implement-and-review.md +25 -0
  146. package/prompts/implement.md +26 -0
  147. package/prompts/issue-review.md +256 -0
  148. package/prompts/pr-review.md +753 -0
  149. package/prompts/query-db.md +154 -0
  150. package/prompts/refine-review.md +208 -0
  151. package/prompts/release.md +235 -0
  152. package/prompts/remember.md +33 -0
  153. package/prompts/review-handler-status.md +27 -0
  154. package/prompts/review-handler.md +677 -0
  155. package/prompts/review-local.md +159 -0
  156. package/prompts/scout-and-plan.md +21 -0
  157. package/rules/00-orchestrator-core.md +42 -0
  158. package/rules/05-issue-first-workflow.md +119 -0
  159. package/rules/10-agent-routing.md +58 -0
  160. package/rules/15-mcp-launchpad.md +46 -0
  161. package/rules/20-code-review-loop.md +234 -0
  162. package/rules/25-documentation-updates.md +15 -0
  163. package/rules/30-prompt-templates.md +19 -0
  164. package/rules/35-memory.md +240 -0
  165. package/rules/40-critical-rules.md +124 -0
  166. package/rules/45-file-preview.md +29 -0
  167. package/rules/50-agent-bug-reporting.md +90 -0
  168. package/rules/55-coms-protocol.md +63 -0
  169. package/rules/60-task-tracking.md +66 -0
  170. package/skills/conflict-resolver/SKILL.md +213 -0
  171. package/skills/skill-creator/SKILL.md +85 -0
  172. package/skills/systematic-debugging/SKILL.md +353 -0
  173. package/skills/test-driven-development/SKILL.md +335 -0
  174. package/skills/update-agents-md/SKILL.md +282 -0
  175. package/templates/coms-feature-manager-prompt.md +313 -0
@@ -0,0 +1,593 @@
1
+ /**
2
+ * Extended autocomplete — argument completions for slash commands.
3
+ *
4
+ * Two mechanisms:
5
+ * 1. getArgumentCompletions on extension commands (dream-auto, etc.)
6
+ * — injected via registerCommand wrapping
7
+ * 2. addAutocompleteProvider for prompt templates (external-ai, review-local, etc.)
8
+ * — stacked provider that intercepts /command <arg> patterns
9
+ *
10
+ * Completions:
11
+ * /external-ai <Tab> → ai-cli provider names + --fix, --peer, --cli-flags
12
+ * /pr-review <Tab> → open PR numbers
13
+ * /coderabbit-rate-limit <Tab> → open PR numbers
14
+ * /review-local <Tab> → git branch names
15
+ * /release <Tab> → recent git tags + --dry-run, --prerelease, --draft, --target <branch>, --tag-match <pattern>
16
+ * /review-handler <Tab> → --autorabbit, --autoqodo
17
+ * /review-status <Tab> → active worktree paths
18
+ * /create-skill <Tab> → (free-text name)
19
+ * /cron <Tab> → add, list, list-all, remove
20
+ * /dream-auto <Tab> → on, off
21
+ * /async-kill <Tab> → all (or type name / id prefix)
22
+ */
23
+
24
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
25
+ import type { AutocompleteItem, AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
26
+ import { fuzzyFilter } from "@earendil-works/pi-tui";
27
+ import * as fs from "node:fs";
28
+ import { execFileSync } from "node:child_process";
29
+ import * as path from "node:path";
30
+ import { getCronFilePath } from "./cron.js";
31
+
32
+ // ── Cache infrastructure ────────────────────────────────────────────
33
+
34
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
35
+
36
+ interface Cache<T> {
37
+ data: T | undefined;
38
+ timestamp: number;
39
+ loading: boolean;
40
+ }
41
+
42
+ function createCache<T>(): Cache<T> {
43
+ return { data: undefined, timestamp: 0, loading: false };
44
+ }
45
+
46
+ function isFresh<T>(cache: Cache<T>): boolean {
47
+ return !!cache.data && Date.now() - cache.timestamp < CACHE_TTL_MS;
48
+ }
49
+
50
+ // ── Static completions ──────────────────────────────────────────────
51
+
52
+ const AI_CLI_PROVIDERS: AutocompleteItem[] = [
53
+ "cursor", "claude", "gemini",
54
+ ].map((a) => ({ value: a, label: a, description: "ai-cli provider" }));
55
+
56
+ const AI_CLI_FLAGS: AutocompleteItem[] = [
57
+ { value: "--fix", label: "--fix", description: "Agent can modify files" },
58
+ { value: "--peer", label: "--peer", description: "AI-to-AI peer review loop" },
59
+ { value: "--resume", label: "--resume", description: "Continue most recent session" },
60
+ { value: "--session-id ", label: "--session-id", description: "Resume a specific session by ID" },
61
+ { value: "--model ", label: "--model", description: "Set model (e.g., gpt-5.4-high)" },
62
+ { value: "--cli-flags ", label: "--cli-flags", description: "Extra flags passed to AI CLI binary" },
63
+ ];
64
+
65
+ // ── Filter helper ───────────────────────────────────────────────────
66
+
67
+ const MAX_SUGGESTIONS = 20;
68
+
69
+ function filter(items: AutocompleteItem[], prefix: string): AutocompleteItem[] | null {
70
+ if (!prefix.trim()) {
71
+ const result = items.slice(0, MAX_SUGGESTIONS);
72
+ return result.length > 0 ? result : null;
73
+ }
74
+ const filtered = fuzzyFilter(items, prefix, (item) => `${item.label} ${item.description || ""}`)
75
+ .slice(0, MAX_SUGGESTIONS);
76
+ return filtered.length > 0 ? filtered : null;
77
+ }
78
+
79
+ // ── Shared types ────────────────────────────────────────────────────
80
+
81
+ type CompletionFn = (prefix: string) => AutocompleteItem[] | null;
82
+
83
+ interface AutocompleteContext {
84
+ prCache: Cache<AutocompleteItem[]>;
85
+ prUrlMap: Map<string, string>;
86
+ issueCache: Cache<AutocompleteItem[]>;
87
+ branchCache: Cache<AutocompleteItem[]>;
88
+ tagCache: Cache<AutocompleteItem[]>;
89
+ modelCaches: Map<string, Cache<AutocompleteItem[]>>;
90
+ lastCwd: string;
91
+ fetchOpenPRs(cwd: string): Promise<void>;
92
+ fetchOpenIssues(cwd: string): Promise<void>;
93
+ fetchBranches(cwd: string): Promise<void>;
94
+ fetchModels(provider: string, cwd: string): Promise<void>;
95
+ fetchTags(cwd: string): Promise<void>;
96
+ }
97
+
98
+ // ── Completions registration ────────────────────────────────────────
99
+
100
+ function registerCompletions(
101
+ pi: ExtensionAPI,
102
+ ctx: AutocompleteContext,
103
+ ): Record<string, CompletionFn> {
104
+
105
+ // ── Completion definitions ──────────────────────────────────────
106
+
107
+ const completions: Record<string, CompletionFn> = {
108
+ "external-ai": (prefix: string) => {
109
+ const parts = prefix.split(/\s+/);
110
+ const lastPart = parts[parts.length - 1] || "";
111
+ const prevPart = parts.length >= 2 ? parts[parts.length - 2] : "";
112
+
113
+ // After --model: show model completions for the detected provider
114
+ // After --cli-flags or --session-id: free-text, no completions
115
+ if (prevPart === "--cli-flags" || prevPart === "--session-id") return null;
116
+
117
+ if (prevPart === "--model") {
118
+ // Find the provider from earlier tokens
119
+ const knownProviders = ["cursor", "claude", "gemini"];
120
+ let provider = "";
121
+ for (const p of parts) {
122
+ const base = p.includes(":") ? p.split(":")[0] : p;
123
+ if (knownProviders.includes(base)) { provider = base; break; }
124
+ }
125
+ if (provider) {
126
+ void ctx.fetchModels(provider, ctx.lastCwd);
127
+ const cache = ctx.modelCaches.get(provider);
128
+ if (cache?.data) {
129
+ return filter(cache.data, lastPart);
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ // First token: provider or provider:model
136
+ if (parts.length <= 1) {
137
+ // Check if user typed "provider:" — show models for that provider
138
+ const colonIdx = lastPart.indexOf(":");
139
+ if (colonIdx >= 0) {
140
+ const provider = lastPart.substring(0, colonIdx);
141
+ const modelPrefix = lastPart.substring(colonIdx + 1);
142
+ const knownProviders = ["cursor", "claude", "gemini"];
143
+ if (knownProviders.includes(provider)) {
144
+ void ctx.fetchModels(provider, ctx.lastCwd);
145
+ const cache = ctx.modelCaches.get(provider);
146
+ if (cache?.data) {
147
+ const items = cache.data.map((m) => ({
148
+ ...m,
149
+ value: `${provider}:${m.value}`,
150
+ }));
151
+ return filter(items, modelPrefix ? `${provider}:${modelPrefix}` : "");
152
+ }
153
+ return null;
154
+ }
155
+ }
156
+ return filter(AI_CLI_PROVIDERS, lastPart);
157
+ }
158
+
159
+ // Subsequent tokens: flags
160
+ if (lastPart.startsWith("-") || lastPart === "") {
161
+ const usedFlags = new Set(parts.filter((p) => p.startsWith("--")));
162
+ const available = AI_CLI_FLAGS.filter((f) => !usedFlags.has(f.value.trim()));
163
+ return filter(available, lastPart);
164
+ }
165
+ return null;
166
+ },
167
+
168
+ "pr-review": (prefix: string) => {
169
+ void ctx.fetchOpenPRs(ctx.lastCwd);
170
+ if (!ctx.prCache.data) return null;
171
+ const filtered = filter(ctx.prCache.data, prefix.replace(/^#/, ""));
172
+ return filtered ? filtered.map((item) => ({ ...item, value: ctx.prUrlMap.get(item.value) || item.value })) : null;
173
+ },
174
+
175
+ "issue-review": (prefix: string) => {
176
+ void ctx.fetchOpenIssues(ctx.lastCwd);
177
+ return ctx.issueCache.data ? filter(ctx.issueCache.data, prefix.replace(/^#/, "")) : null;
178
+ },
179
+
180
+ "coderabbit-rate-limit": (prefix: string) => {
181
+ void ctx.fetchOpenPRs(ctx.lastCwd);
182
+ return ctx.prCache.data ? filter(ctx.prCache.data, prefix.replace(/^#/, "")) : null;
183
+ },
184
+
185
+ "review-local": (prefix: string) => {
186
+ void ctx.fetchBranches(ctx.lastCwd);
187
+ return ctx.branchCache.data ? filter(ctx.branchCache.data, prefix) : null;
188
+ },
189
+
190
+ "release": (prefix: string) => {
191
+ const parts = prefix.split(/\s+/);
192
+ const lastPart = parts[parts.length - 1] || "";
193
+ const prevPart = parts.length >= 2 ? parts[parts.length - 2] : "";
194
+
195
+ const RELEASE_FLAGS: AutocompleteItem[] = [
196
+ { value: "--dry-run", label: "--dry-run", description: "Preview without creating" },
197
+ { value: "--prerelease", label: "--prerelease", description: "Create prerelease" },
198
+ { value: "--draft", label: "--draft", description: "Create draft release" },
199
+ { value: "--target ", label: "--target", description: "Target specific branch" },
200
+ { value: "--tag-match ", label: "--tag-match", description: "Filter tags by pattern" },
201
+ ];
202
+
203
+ // After --target: show branch completions
204
+ if (prevPart === "--target") {
205
+ void ctx.fetchBranches(ctx.lastCwd);
206
+ return ctx.branchCache.data ? filter(ctx.branchCache.data, lastPart) : null;
207
+ }
208
+
209
+ // After --tag-match: free-text pattern, no completions
210
+ if (prevPart === "--tag-match") return null;
211
+
212
+ // Collect used flags
213
+ const usedFlags = new Set(parts.filter((p) => p.startsWith("--")));
214
+
215
+ // Available flags (exclude already used)
216
+ const availableFlags = RELEASE_FLAGS.filter((f) => !usedFlags.has(f.value.trim()));
217
+
218
+ // Fetch tags
219
+ void ctx.fetchTags(ctx.lastCwd);
220
+ const tags = ctx.tagCache.data || [];
221
+
222
+ // Combine flags and tags
223
+ const combined = [...availableFlags, ...tags];
224
+ return filter(combined, lastPart);
225
+ },
226
+
227
+
228
+ "review-handler": (prefix: string) => {
229
+ const tokens = prefix.trim().split(/\s+/).filter(Boolean);
230
+ const selected = new Set(tokens);
231
+ const lastPart = prefix.endsWith(" ") ? "" : (tokens[tokens.length - 1] || "");
232
+ const all = [
233
+ { value: "--autorabbit", label: "--autorabbit", description: "Auto-fix CodeRabbit comments in a loop" },
234
+ { value: "--autoqodo", label: "--autoqodo", description: "Auto-fix Qodo comments in a loop" },
235
+ ];
236
+ const available = all.filter(item => !selected.has(item.value));
237
+ return filter(available, lastPart);
238
+ },
239
+
240
+ "review-status": (prefix: string) => {
241
+ // List active worktrees (excluding main repo) as completion options
242
+ try {
243
+ // Use --git-common-dir to find the shared repo root (not worktree root).
244
+ // This ensures we correctly identify the main repo even if ctx.lastCwd
245
+ // happens to be inside a worktree.
246
+ const gitCommonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
247
+ cwd: ctx.lastCwd, encoding: "utf-8", timeout: 3000,
248
+ stdio: ["ignore", "pipe", "ignore"],
249
+ }).trim();
250
+ const mainRoot = path.dirname(path.resolve(ctx.lastCwd, gitCommonDir));
251
+ const porcelain = execFileSync("git", ["worktree", "list", "--porcelain"], {
252
+ cwd: ctx.lastCwd, encoding: "utf-8", timeout: 3000,
253
+ stdio: ["ignore", "pipe", "ignore"],
254
+ });
255
+ const worktrees: AutocompleteItem[] = [];
256
+ for (const line of porcelain.split("\n")) {
257
+ if (!line.startsWith("worktree ")) continue;
258
+ const wtPath = line.slice("worktree ".length).trim();
259
+ if (wtPath === mainRoot) continue; // skip main repo
260
+ const relative = path.relative(ctx.lastCwd, wtPath);
261
+ worktrees.push({ value: relative, label: relative, description: `Worktree: ${wtPath}` });
262
+ }
263
+ return filter(worktrees, prefix);
264
+ } catch {
265
+ return null;
266
+ }
267
+ },
268
+
269
+ "dream-auto": (prefix: string) => {
270
+ return filter([
271
+ { value: "on", label: "on", description: "Enable auto-dreaming (every 3h + session end)" },
272
+ { value: "off", label: "off", description: "Disable auto-dreaming" },
273
+ ], prefix);
274
+ },
275
+
276
+ "async-kill": (prefix: string) => {
277
+ return filter([
278
+ { value: "all", label: "all", description: "Kill all running/queued async agents" },
279
+ ], prefix);
280
+ },
281
+
282
+ "cron": (prefix: string) => {
283
+ const parts = prefix.split(/\s+/);
284
+ const lastPart = parts[parts.length - 1] || "";
285
+ const sub = parts[0]?.toLowerCase();
286
+
287
+ // First level: subcommands
288
+ if (parts.length <= 1 && !(["add", "list", "list-all", "remove", "rm", "delete", "kill"].includes(sub))) {
289
+ return filter([
290
+ { value: "add ", label: "add", description: "Add a scheduled task" },
291
+ { value: "list", label: "list", description: "List scheduled tasks" },
292
+ { value: "list-all", label: "list-all", description: "List crons from all sessions" },
293
+ { value: "remove ", label: "remove", description: "Remove a scheduled task" },
294
+ ], lastPart);
295
+ }
296
+ // After "add"
297
+ if (sub === "add" && parts.length <= 2) {
298
+ return filter([{ value: "every", label: "every", description: "Interval-based (e.g., every 2h)" }, { value: "at", label: "at", description: "Time-based (e.g., at 12:00)" }], lastPart);
299
+ }
300
+ // After "remove" — show task IDs (supports multi-select)
301
+ if (sub === "remove" || sub === "rm" || sub === "delete" || sub === "kill") {
302
+ try {
303
+ const cronFile = getCronFilePath();
304
+ if (!cronFile) return null;
305
+ const cronTasks = JSON.parse(fs.readFileSync(cronFile, "utf-8"));
306
+ if (Array.isArray(cronTasks)) {
307
+ const alreadySelected = new Set(parts.slice(1).filter(p => p !== lastPart));
308
+ return filter(cronTasks.filter((t: any) => !alreadySelected.has(String(t.id))).map((t: any) => ({
309
+ value: String(t.id),
310
+ label: `#${t.id}`,
311
+ description: t.description || t.task || "",
312
+ })), lastPart);
313
+ }
314
+ } catch (e: any) { console.debug("[autocomplete] cron task fetch failed:", e?.message || e); }
315
+ return null;
316
+ }
317
+ return null;
318
+ },
319
+
320
+ };
321
+
322
+ // ── Mechanism 1: registerCommand wrapping for extension commands ─
323
+
324
+ const originalRegisterCommand = pi.registerCommand.bind(pi);
325
+ pi.registerCommand = (name: string, options: any) => {
326
+ const completionFn = completions[name];
327
+ if (completionFn && !options.getArgumentCompletions) {
328
+ options.getArgumentCompletions = completionFn;
329
+ }
330
+ return originalRegisterCommand(name, options);
331
+ };
332
+
333
+ return completions;
334
+ }
335
+
336
+ // ── Prompt template interceptor ─────────────────────────────────────
337
+
338
+ function setupPromptTemplateInterceptor(
339
+ pi: ExtensionAPI,
340
+ ctx: AutocompleteContext,
341
+ completions: Record<string, CompletionFn>,
342
+ ): void {
343
+ // Prompt templates (acpx-prompt, review-local, etc.) are registered by
344
+ // pi itself — not through our registerCommand wrapper. We intercept
345
+ // them in the autocomplete provider, which runs before the built-in.
346
+
347
+ // Set of prompt template names that we handle
348
+ const promptTemplateCommands = new Set([
349
+ "external-ai", "pr-review", "issue-review", "coderabbit-rate-limit",
350
+ "review-local", "release", "review-handler", "cron", "create-skill", "create-coms-feature-manager",
351
+ ]);
352
+
353
+ // /external-ai-models-refresh command — clears cache and re-fetches
354
+ pi.registerCommand("external-ai-models-refresh", {
355
+ description: "Refresh AI CLI model cache (cursor, claude, gemini)",
356
+ async handler(_args, handlerCtx) {
357
+ ctx.modelCaches.clear();
358
+ handlerCtx.ui.notify("Refreshing AI CLI models...", "info");
359
+ await Promise.allSettled(
360
+ ["cursor", "claude", "gemini"].map((p) => ctx.fetchModels(p, handlerCtx.cwd)),
361
+ );
362
+ const counts = ["cursor", "claude", "gemini"]
363
+ .map((p) => `${p}: ${ctx.modelCaches.get(p)?.data?.length ?? 0}`)
364
+ .join(", ");
365
+ handlerCtx.ui.notify(`AI CLI models refreshed (${counts})`, "info");
366
+ },
367
+ });
368
+
369
+ let modelsPrefetched = false;
370
+
371
+ pi.on("session_start", (_event, sessionCtx) => {
372
+ ctx.lastCwd = sessionCtx.cwd;
373
+
374
+ // Pre-fetch AI CLI models once on first start (not on /new)
375
+ if (!modelsPrefetched) {
376
+ modelsPrefetched = true;
377
+ for (const provider of ["cursor", "claude", "gemini"]) {
378
+ void ctx.fetchModels(provider, sessionCtx.cwd);
379
+ }
380
+ }
381
+
382
+ if (sessionCtx.mode !== "tui") return;
383
+
384
+ sessionCtx.ui.addAutocompleteProvider((current: AutocompleteProvider) => ({
385
+ async getSuggestions(
386
+ lines: string[],
387
+ cursorLine: number,
388
+ cursorCol: number,
389
+ options: { signal: AbortSignal; force?: boolean },
390
+ ): Promise<AutocompleteSuggestions | null> {
391
+ const line = lines[cursorLine] ?? "";
392
+ const before = line.slice(0, cursorCol);
393
+ // Match /command <args> — only for our prompt template commands
394
+ const match = before.match(/^\/(\S+)\s+([\s\S]*)$/);
395
+ if (match) {
396
+ const cmdName = match[1];
397
+ const argText = match[2];
398
+
399
+ if (promptTemplateCommands.has(cmdName)) {
400
+ const completionFn = completions[cmdName];
401
+ if (completionFn) {
402
+ // Extract the last "word" for prefix matching
403
+ const lastSpaceIdx = argText.lastIndexOf(" ");
404
+ const lastWord = lastSpaceIdx >= 0 ? argText.slice(lastSpaceIdx + 1) : argText;
405
+
406
+ const items = completionFn(argText);
407
+ if (items && items.length > 0) {
408
+ return { items, prefix: lastWord };
409
+ }
410
+ }
411
+ }
412
+ }
413
+
414
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
415
+ },
416
+
417
+ applyCompletion(
418
+ lines: string[],
419
+ cursorLine: number,
420
+ cursorCol: number,
421
+ item: AutocompleteItem,
422
+ prefix: string,
423
+ ) {
424
+ return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
425
+ },
426
+
427
+ shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number) {
428
+ // Override: allow Tab completion when cursor is after a command we handle
429
+ const line = lines[cursorLine] ?? "";
430
+ const before = line.slice(0, cursorCol);
431
+ const cmdMatch = before.match(/^\/(\S+)\s/);
432
+ if (cmdMatch && promptTemplateCommands.has(cmdMatch[1])) {
433
+ return true; // Let Tab through so our getSuggestions can handle it
434
+ }
435
+ return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
436
+ },
437
+ }));
438
+ });
439
+ }
440
+
441
+ // ── Registration ────────────────────────────────────────────────────
442
+
443
+ export function registerExtendedAutocomplete(pi: ExtensionAPI): void {
444
+ if (process.env.PI_SUBAGENT_CHILD === "1") return;
445
+
446
+ // Shared mutable context — caches, fetchers, and state passed to extracted functions
447
+ const ctx = {} as AutocompleteContext;
448
+ ctx.prCache = createCache<AutocompleteItem[]>();
449
+ ctx.prUrlMap = new Map<string, string>();
450
+ ctx.issueCache = createCache<AutocompleteItem[]>();
451
+ ctx.branchCache = createCache<AutocompleteItem[]>();
452
+ ctx.tagCache = createCache<AutocompleteItem[]>();
453
+ ctx.modelCaches = new Map<string, Cache<AutocompleteItem[]>>();
454
+ ctx.lastCwd = "";
455
+
456
+ // ── Fetchers (close over pi and ctx) ────────────────────────────
457
+
458
+ ctx.fetchOpenPRs = async (cwd: string) => {
459
+ if (isFresh(ctx.prCache) || ctx.prCache.loading) return;
460
+ ctx.prCache.loading = true;
461
+ try {
462
+ const result = await pi.exec(
463
+ "gh", ["pr", "list", "--state", "open", "--limit", "50", "--json", "number,title,url"],
464
+ { cwd, timeout: 10_000 },
465
+ );
466
+ if (result.code === 0) {
467
+ const prs = JSON.parse(result.stdout) as Array<{ number: number; title: string; url: string }>;
468
+ ctx.prCache.data = prs.map((pr) => ({
469
+ value: String(pr.number),
470
+ label: `#${pr.number}`,
471
+ description: pr.title,
472
+ }));
473
+ ctx.prCache.timestamp = Date.now();
474
+ // URL-keyed version for pr-review (uses URL as completion value)
475
+ ctx.prUrlMap = new Map(prs.map((pr) => [String(pr.number), pr.url || String(pr.number)]));
476
+ }
477
+ } catch (e: any) { console.debug("[autocomplete] PR fetch failed:", e?.message || e); }
478
+ ctx.prCache.loading = false;
479
+ };
480
+
481
+ ctx.fetchOpenIssues = async (cwd: string) => {
482
+ if (isFresh(ctx.issueCache) || ctx.issueCache.loading) return;
483
+ ctx.issueCache.loading = true;
484
+ try {
485
+ const result = await pi.exec(
486
+ "gh", ["issue", "list", "--state", "open", "--limit", "50", "--json", "number,title"],
487
+ { cwd, timeout: 10_000 },
488
+ );
489
+ if (result.code === 0) {
490
+ const issues = JSON.parse(result.stdout) as Array<{ number: number; title: string }>;
491
+ ctx.issueCache.data = issues.map((issue) => ({
492
+ value: String(issue.number),
493
+ label: `#${issue.number}`,
494
+ description: issue.title,
495
+ }));
496
+ ctx.issueCache.timestamp = Date.now();
497
+ }
498
+ } catch (e: any) { console.debug("[autocomplete] issue fetch failed:", e?.message || e); }
499
+ ctx.issueCache.loading = false;
500
+ };
501
+
502
+ ctx.fetchBranches = async (cwd: string) => {
503
+ if (isFresh(ctx.branchCache) || ctx.branchCache.loading) return;
504
+ ctx.branchCache.loading = true;
505
+ try {
506
+ const result = await pi.exec(
507
+ "git", ["branch", "-a", "--format=%(HEAD)|%(refname:short)"],
508
+ { cwd, timeout: 5_000 },
509
+ );
510
+ if (result.code === 0) {
511
+ const seen = new Set<string>();
512
+ const items: AutocompleteItem[] = [];
513
+ for (const line of result.stdout.split("\n")) {
514
+ const trimmed = line.trim();
515
+ if (!trimmed) continue;
516
+ const [head, ref] = trimmed.split("|");
517
+ if (!ref) continue;
518
+ const name = ref.replace(/^origin\//, "");
519
+ if (name === "HEAD" || seen.has(name)) continue;
520
+ seen.add(name);
521
+ items.push({
522
+ value: name,
523
+ label: name,
524
+ description: head === "*" ? "← current" : undefined,
525
+ });
526
+ }
527
+ ctx.branchCache.data = items;
528
+ ctx.branchCache.timestamp = Date.now();
529
+ }
530
+ } catch (e: any) { console.debug("[autocomplete] branch fetch failed:", e?.message || e); }
531
+ ctx.branchCache.loading = false;
532
+ };
533
+
534
+ ctx.fetchModels = async (provider: string, cwd: string) => {
535
+ let cache = ctx.modelCaches.get(provider);
536
+ if (!cache) {
537
+ cache = createCache<AutocompleteItem[]>();
538
+ ctx.modelCaches.set(provider, cache);
539
+ }
540
+ if (isFresh(cache) || cache.loading) return;
541
+ cache.loading = true;
542
+ try {
543
+ const result = await pi.exec(
544
+ "myk-pi-tools", ["ai-cli", "models", provider],
545
+ { cwd, timeout: 30_000 },
546
+ );
547
+ if (result.code === 0) {
548
+ const items: AutocompleteItem[] = [];
549
+ try {
550
+ const parsed = JSON.parse(result.stdout);
551
+ if (Array.isArray(parsed)) {
552
+ for (const m of parsed) {
553
+ if (m.id) {
554
+ items.push({
555
+ value: m.id,
556
+ label: m.id,
557
+ description: m.name || m.id,
558
+ });
559
+ }
560
+ }
561
+ }
562
+ } catch (e: any) { console.debug("[autocomplete] model JSON parse failed:", e?.message || e); }
563
+ cache.data = items;
564
+ cache.timestamp = Date.now();
565
+ }
566
+ } catch (e: any) { console.debug("[autocomplete] model fetch failed:", e?.message || e); }
567
+ cache.loading = false;
568
+ };
569
+
570
+ ctx.fetchTags = async (cwd: string) => {
571
+ if (isFresh(ctx.tagCache) || ctx.tagCache.loading) return;
572
+ ctx.tagCache.loading = true;
573
+ try {
574
+ const result = await pi.exec(
575
+ "git", ["tag", "--sort=-version:refname", "-l"],
576
+ { cwd, timeout: 5_000 },
577
+ );
578
+ if (result.code === 0) {
579
+ ctx.tagCache.data = result.stdout
580
+ .split("\n")
581
+ .map((t) => t.trim())
582
+ .filter((t) => t.length > 0)
583
+ .slice(0, 20)
584
+ .map((t) => ({ value: t, label: t, description: "git tag" }));
585
+ ctx.tagCache.timestamp = Date.now();
586
+ }
587
+ } catch (e: any) { console.debug("[autocomplete] tag fetch failed:", e?.message || e); }
588
+ ctx.tagCache.loading = false;
589
+ };
590
+
591
+ const completions = registerCompletions(pi, ctx);
592
+ setupPromptTemplateInterceptor(pi, ctx, completions);
593
+ }