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.
- package/README.md +808 -0
- package/agents/api-documenter.md +32 -0
- package/agents/bash-expert.md +53 -0
- package/agents/code-reviewer-docs.md +145 -0
- package/agents/code-reviewer-guidelines.md +97 -0
- package/agents/code-reviewer-quality.md +142 -0
- package/agents/code-reviewer-security.md +107 -0
- package/agents/code-reviewer-spec.md +164 -0
- package/agents/debugger.md +63 -0
- package/agents/docker-expert.md +40 -0
- package/agents/docs-fetcher.md +48 -0
- package/agents/git-expert.md +63 -0
- package/agents/github-expert.md +83 -0
- package/agents/go-expert.md +62 -0
- package/agents/issue-reviewer-feasibility.md +60 -0
- package/agents/issue-reviewer-scope.md +63 -0
- package/agents/issue-reviewer-spec.md +77 -0
- package/agents/java-expert.md +31 -0
- package/agents/jenkins-expert.md +38 -0
- package/agents/kubernetes-expert.md +40 -0
- package/agents/planner.md +42 -0
- package/agents/python-expert.md +61 -0
- package/agents/reviewer.md +26 -0
- package/agents/scout.md +40 -0
- package/agents/security-auditor.md +254 -0
- package/agents/technical-documentation-writer.md +37 -0
- package/agents/test-automator.md +55 -0
- package/agents/test-runner.md +47 -0
- package/agents/ts-expert.md +23 -0
- package/agents/worker.md +30 -0
- package/extensions/acpx-provider/index.ts +104 -0
- package/extensions/acpx-provider/load-runtime.ts +99 -0
- package/extensions/acpx-provider/runtime-models.ts +40 -0
- package/extensions/cli-provider/agents/claude.ts +128 -0
- package/extensions/cli-provider/agents/cursor.ts +84 -0
- package/extensions/cli-provider/agents/gemini.ts +100 -0
- package/extensions/cli-provider/discover.ts +77 -0
- package/extensions/cli-provider/index.ts +57 -0
- package/extensions/cli-provider/parsers.ts +304 -0
- package/extensions/cli-provider/providers.ts +58 -0
- package/extensions/cli-provider/runner.ts +225 -0
- package/extensions/cli-provider/runtime-models.ts +32 -0
- package/extensions/cli-provider/session-reaper.ts +122 -0
- package/extensions/cli-provider/sessions.ts +533 -0
- package/extensions/cli-provider/shared/discover-cache.ts +235 -0
- package/extensions/cli-provider/types.ts +48 -0
- package/extensions/coms/coms-net-server.ts +1642 -0
- package/extensions/coms/coms-net-wrapper.ts +521 -0
- package/extensions/coms/coms-net.ts +1589 -0
- package/extensions/coms/coms-p2p.ts +1879 -0
- package/extensions/coms/coms-shared.ts +510 -0
- package/extensions/coms/coms-wrapper.ts +138 -0
- package/extensions/coms/index.ts +20 -0
- package/extensions/coms/themeMap.ts +147 -0
- package/extensions/image-gen/image-gen.ts +332 -0
- package/extensions/image-gen/index.ts +14 -0
- package/extensions/orchestrator/agents.ts +134 -0
- package/extensions/orchestrator/ask-user.ts +293 -0
- package/extensions/orchestrator/async-agents.ts +1037 -0
- package/extensions/orchestrator/async-capability.ts +129 -0
- package/extensions/orchestrator/async-runner.ts +324 -0
- package/extensions/orchestrator/async-status-parse.ts +31 -0
- package/extensions/orchestrator/async-status-ui.ts +201 -0
- package/extensions/orchestrator/async-wait.ts +36 -0
- package/extensions/orchestrator/btw.ts +210 -0
- package/extensions/orchestrator/cron-status-format.ts +126 -0
- package/extensions/orchestrator/cron-status-ui.ts +132 -0
- package/extensions/orchestrator/cron.ts +730 -0
- package/extensions/orchestrator/dreaming.ts +355 -0
- package/extensions/orchestrator/enforcement-helpers.ts +530 -0
- package/extensions/orchestrator/enforcement-rules.ts +329 -0
- package/extensions/orchestrator/enforcement.ts +828 -0
- package/extensions/orchestrator/extended-autocomplete.ts +593 -0
- package/extensions/orchestrator/git-helpers.ts +420 -0
- package/extensions/orchestrator/github-autocomplete.ts +250 -0
- package/extensions/orchestrator/icons.ts +17 -0
- package/extensions/orchestrator/index.ts +100 -0
- package/extensions/orchestrator/memory-embeddings.ts +281 -0
- package/extensions/orchestrator/memory-promotion.ts +449 -0
- package/extensions/orchestrator/memory-provenance.ts +80 -0
- package/extensions/orchestrator/memory-query-class.ts +90 -0
- package/extensions/orchestrator/memory-scoring.ts +434 -0
- package/extensions/orchestrator/memory-tools.ts +883 -0
- package/extensions/orchestrator/memory-tree.ts +212 -0
- package/extensions/orchestrator/nvim.ts +169 -0
- package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
- package/extensions/orchestrator/overlay-dashboard.ts +512 -0
- package/extensions/orchestrator/pi-config-review-state.ts +303 -0
- package/extensions/orchestrator/preference-extractor.ts +106 -0
- package/extensions/orchestrator/project-settings.ts +468 -0
- package/extensions/orchestrator/promotion-queue.ts +311 -0
- package/extensions/orchestrator/resolve-agent-model.ts +32 -0
- package/extensions/orchestrator/review-ui.ts +204 -0
- package/extensions/orchestrator/rule-placeholders.ts +12 -0
- package/extensions/orchestrator/rules.ts +481 -0
- package/extensions/orchestrator/session-search.ts +243 -0
- package/extensions/orchestrator/session-validation.ts +374 -0
- package/extensions/orchestrator/situation-report.ts +370 -0
- package/extensions/orchestrator/status-bar.ts +31 -0
- package/extensions/orchestrator/status-line.ts +192 -0
- package/extensions/orchestrator/status.ts +110 -0
- package/extensions/orchestrator/subagent-tool.ts +1748 -0
- package/extensions/orchestrator/task-lifecycle.ts +88 -0
- package/extensions/orchestrator/utils.ts +227 -0
- package/extensions/pidash/index.ts +15 -0
- package/extensions/pidash/pidash.ts +941 -0
- package/extensions/pidiff/index.ts +13 -0
- package/extensions/pidiff/pidiff.ts +369 -0
- package/extensions/providers/acpx-driver.ts +479 -0
- package/extensions/providers/built-in-drivers.ts +52 -0
- package/extensions/providers/claude-driver.ts +319 -0
- package/extensions/providers/cursor-acpx-driver.ts +398 -0
- package/extensions/providers/cursor-cli-driver.ts +484 -0
- package/extensions/providers/gemini-driver.ts +302 -0
- package/extensions/providers/index.ts +513 -0
- package/extensions/shared/build-system-prompt.ts +57 -0
- package/extensions/shared/create-runtime-provider.ts +190 -0
- package/extensions/shared/daemon-manager.ts +248 -0
- package/extensions/shared/file-logger.ts +115 -0
- package/extensions/shared/managed-refresh.ts +211 -0
- package/extensions/shared/provider-driver.ts +295 -0
- package/extensions/shared/provider-errors.ts +305 -0
- package/extensions/shared/provider-registry.ts +388 -0
- package/extensions/shared/stream-builder.ts +275 -0
- package/extensions/shared/types.ts +20 -0
- package/extensions/shared/ui/README.md +10 -0
- package/extensions/shared/ui/badge.tsx +52 -0
- package/extensions/shared/ui/button.tsx +58 -0
- package/extensions/shared/ui/collapsible.tsx +21 -0
- package/extensions/shared/ui/dropdown-menu.tsx +266 -0
- package/extensions/shared/ui/input.tsx +20 -0
- package/extensions/shared/ui/popover.tsx +114 -0
- package/extensions/shared/ui/resizable.tsx +48 -0
- package/extensions/shared/ui/scroll-area.tsx +53 -0
- package/extensions/shared/ui/select.tsx +201 -0
- package/extensions/shared/ui/separator.tsx +25 -0
- package/extensions/shared/ui/tooltip.tsx +64 -0
- package/extensions/shared/ws-client.ts +74 -0
- package/package.json +59 -0
- package/prompts/coderabbit-rate-limit.md +95 -0
- package/prompts/create-coms-feature-manager.md +186 -0
- package/prompts/create-skill.md +128 -0
- package/prompts/domain-model.md +109 -0
- package/prompts/external-ai.md +665 -0
- package/prompts/implement-and-review.md +25 -0
- package/prompts/implement.md +26 -0
- package/prompts/issue-review.md +256 -0
- package/prompts/pr-review.md +753 -0
- package/prompts/query-db.md +154 -0
- package/prompts/refine-review.md +208 -0
- package/prompts/release.md +235 -0
- package/prompts/remember.md +33 -0
- package/prompts/review-handler-status.md +27 -0
- package/prompts/review-handler.md +677 -0
- package/prompts/review-local.md +159 -0
- package/prompts/scout-and-plan.md +21 -0
- package/rules/00-orchestrator-core.md +42 -0
- package/rules/05-issue-first-workflow.md +119 -0
- package/rules/10-agent-routing.md +58 -0
- package/rules/15-mcp-launchpad.md +46 -0
- package/rules/20-code-review-loop.md +234 -0
- package/rules/25-documentation-updates.md +15 -0
- package/rules/30-prompt-templates.md +19 -0
- package/rules/35-memory.md +240 -0
- package/rules/40-critical-rules.md +124 -0
- package/rules/45-file-preview.md +29 -0
- package/rules/50-agent-bug-reporting.md +90 -0
- package/rules/55-coms-protocol.md +63 -0
- package/rules/60-task-tracking.md +66 -0
- package/skills/conflict-resolver/SKILL.md +213 -0
- package/skills/skill-creator/SKILL.md +85 -0
- package/skills/systematic-debugging/SKILL.md +353 -0
- package/skills/test-driven-development/SKILL.md +335 -0
- package/skills/update-agents-md/SKILL.md +282 -0
- 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
|
+
}
|