im-pickle-rick 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.
- package/README.md +242 -0
- package/bin.js +3 -0
- package/dist/pickle +0 -0
- package/dist/worker-executor.js +207 -0
- package/package.json +53 -0
- package/src/games/GameSidebarManager.test.ts +64 -0
- package/src/games/GameSidebarManager.ts +78 -0
- package/src/games/gameboy/GameboyView.test.ts +25 -0
- package/src/games/gameboy/GameboyView.ts +100 -0
- package/src/games/gameboy/gameboy-polyfills.ts +313 -0
- package/src/games/index.test.ts +9 -0
- package/src/games/index.ts +4 -0
- package/src/games/snake/SnakeGame.test.ts +35 -0
- package/src/games/snake/SnakeGame.ts +145 -0
- package/src/games/snake/SnakeView.test.ts +25 -0
- package/src/games/snake/SnakeView.ts +290 -0
- package/src/index.test.ts +24 -0
- package/src/index.ts +141 -0
- package/src/services/commands/worker.test.ts +14 -0
- package/src/services/commands/worker.ts +262 -0
- package/src/services/config/index.ts +2 -0
- package/src/services/config/settings.test.ts +42 -0
- package/src/services/config/settings.ts +220 -0
- package/src/services/config/state.test.ts +88 -0
- package/src/services/config/state.ts +130 -0
- package/src/services/config/types.ts +39 -0
- package/src/services/execution/index.ts +1 -0
- package/src/services/execution/pickle-source.test.ts +88 -0
- package/src/services/execution/pickle-source.ts +264 -0
- package/src/services/execution/prompt.test.ts +93 -0
- package/src/services/execution/prompt.ts +322 -0
- package/src/services/execution/sequential.test.ts +91 -0
- package/src/services/execution/sequential.ts +422 -0
- package/src/services/execution/worker-client.ts +94 -0
- package/src/services/execution/worker-executor.ts +41 -0
- package/src/services/execution/worker.test.ts +73 -0
- package/src/services/git/branch.test.ts +147 -0
- package/src/services/git/branch.ts +128 -0
- package/src/services/git/diff.test.ts +113 -0
- package/src/services/git/diff.ts +323 -0
- package/src/services/git/index.ts +4 -0
- package/src/services/git/pr.test.ts +104 -0
- package/src/services/git/pr.ts +192 -0
- package/src/services/git/worktree.test.ts +99 -0
- package/src/services/git/worktree.ts +141 -0
- package/src/services/providers/base.test.ts +86 -0
- package/src/services/providers/base.ts +438 -0
- package/src/services/providers/codex.test.ts +39 -0
- package/src/services/providers/codex.ts +208 -0
- package/src/services/providers/gemini.test.ts +40 -0
- package/src/services/providers/gemini.ts +169 -0
- package/src/services/providers/index.test.ts +28 -0
- package/src/services/providers/index.ts +41 -0
- package/src/services/providers/opencode.test.ts +64 -0
- package/src/services/providers/opencode.ts +228 -0
- package/src/services/providers/types.ts +44 -0
- package/src/skills/code-implementer.md +105 -0
- package/src/skills/code-researcher.md +78 -0
- package/src/skills/implementation-planner.md +105 -0
- package/src/skills/plan-reviewer.md +100 -0
- package/src/skills/prd-drafter.md +123 -0
- package/src/skills/research-reviewer.md +79 -0
- package/src/skills/ruthless-refactorer.md +52 -0
- package/src/skills/ticket-manager.md +135 -0
- package/src/types/index.ts +2 -0
- package/src/types/rpc.ts +14 -0
- package/src/types/tasks.ts +50 -0
- package/src/types.d.ts +9 -0
- package/src/ui/common.ts +28 -0
- package/src/ui/components/FilePickerView.test.ts +79 -0
- package/src/ui/components/FilePickerView.ts +161 -0
- package/src/ui/components/MultiLineInput.test.ts +27 -0
- package/src/ui/components/MultiLineInput.ts +233 -0
- package/src/ui/components/SessionChip.test.ts +69 -0
- package/src/ui/components/SessionChip.ts +481 -0
- package/src/ui/components/ToyboxSidebar.test.ts +36 -0
- package/src/ui/components/ToyboxSidebar.ts +329 -0
- package/src/ui/components/refactor_plan.md +35 -0
- package/src/ui/controllers/DashboardController.integration.test.ts +43 -0
- package/src/ui/controllers/DashboardController.ts +650 -0
- package/src/ui/dashboard.test.ts +43 -0
- package/src/ui/dashboard.ts +309 -0
- package/src/ui/dialogs/DashboardDialog.test.ts +146 -0
- package/src/ui/dialogs/DashboardDialog.ts +399 -0
- package/src/ui/dialogs/Dialog.test.ts +50 -0
- package/src/ui/dialogs/Dialog.ts +241 -0
- package/src/ui/dialogs/DialogSidebar.test.ts +60 -0
- package/src/ui/dialogs/DialogSidebar.ts +71 -0
- package/src/ui/dialogs/DiffViewDialog.test.ts +57 -0
- package/src/ui/dialogs/DiffViewDialog.ts +510 -0
- package/src/ui/dialogs/PRPreviewDialog.test.ts +50 -0
- package/src/ui/dialogs/PRPreviewDialog.ts +346 -0
- package/src/ui/dialogs/test-utils.ts +232 -0
- package/src/ui/file-picker-utils.test.ts +71 -0
- package/src/ui/file-picker-utils.ts +200 -0
- package/src/ui/input-chrome.test.ts +62 -0
- package/src/ui/input-chrome.ts +172 -0
- package/src/ui/logger.test.ts +68 -0
- package/src/ui/logger.ts +45 -0
- package/src/ui/mock-factory.ts +6 -0
- package/src/ui/spinner.test.ts +65 -0
- package/src/ui/spinner.ts +41 -0
- package/src/ui/test-setup.ts +300 -0
- package/src/ui/theme.test.ts +23 -0
- package/src/ui/theme.ts +16 -0
- package/src/ui/views/LandingView.integration.test.ts +21 -0
- package/src/ui/views/LandingView.test.ts +24 -0
- package/src/ui/views/LandingView.ts +221 -0
- package/src/ui/views/LogView.test.ts +24 -0
- package/src/ui/views/LogView.ts +277 -0
- package/src/ui/views/ToyboxView.test.ts +46 -0
- package/src/ui/views/ToyboxView.ts +323 -0
- package/src/utils/clipboard.test.ts +86 -0
- package/src/utils/clipboard.ts +100 -0
- package/src/utils/index.test.ts +68 -0
- package/src/utils/index.ts +95 -0
- package/src/utils/persona.test.ts +12 -0
- package/src/utils/persona.ts +8 -0
- package/src/utils/project-root.test.ts +38 -0
- package/src/utils/project-root.ts +22 -0
- package/src/utils/resources.test.ts +64 -0
- package/src/utils/resources.ts +92 -0
- package/src/utils/search.test.ts +48 -0
- package/src/utils/search.ts +103 -0
- package/src/utils/session-tracker.test.ts +46 -0
- package/src/utils/session-tracker.ts +67 -0
- package/src/utils/spinner.test.ts +54 -0
- package/src/utils/spinner.ts +87 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import type { SessionState } from "../config/types.js";
|
|
2
|
+
import { PICKLE_PERSONA } from "../../utils/persona.js";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
6
|
+
import { resolveSkillPath, getExtensionRoot, getCliCommand } from "../../utils/resources.js";
|
|
7
|
+
import type { Task } from "../../types/tasks.js";
|
|
8
|
+
|
|
9
|
+
async function loadSkill(skillName: string): Promise<string> {
|
|
10
|
+
const skillPath = resolveSkillPath(skillName);
|
|
11
|
+
if (skillPath && existsSync(skillPath)) {
|
|
12
|
+
return await readFile(skillPath, "utf-8");
|
|
13
|
+
}
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveDocPath(dir: string, baseName: string): string | undefined {
|
|
18
|
+
const fullPath = join(dir, `${baseName}.md`);
|
|
19
|
+
if (existsSync(fullPath)) return fullPath;
|
|
20
|
+
|
|
21
|
+
if (existsSync(dir)) {
|
|
22
|
+
try {
|
|
23
|
+
const files = readdirSync(dir);
|
|
24
|
+
const pattern = new RegExp(`^${baseName}_.*\\.md$`);
|
|
25
|
+
// Sort files to get the most recent by date-stamp (assuming YYYY-MM-DD format)
|
|
26
|
+
const matches = files.filter(f => pattern.test(f)).sort().reverse();
|
|
27
|
+
if (matches.length > 0) return join(dir, matches[0]);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Directory might not exist or be inaccessible
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function buildPrompt(state: SessionState, task?: Task, overrides?: { sessionDir?: string; workingDir?: string }): Promise<string> {
|
|
36
|
+
const extensionPath = getExtensionRoot();
|
|
37
|
+
const sessionDir = overrides?.sessionDir || state.session_dir;
|
|
38
|
+
const workingDir = overrides?.workingDir || state.working_dir;
|
|
39
|
+
|
|
40
|
+
let phaseInstruction = "";
|
|
41
|
+
let skillInjection = "";
|
|
42
|
+
let skillName = "";
|
|
43
|
+
let ticketPath = "";
|
|
44
|
+
let currentPhase = state.step as string;
|
|
45
|
+
|
|
46
|
+
if (task) {
|
|
47
|
+
if (task.id === "phase-prd") {
|
|
48
|
+
currentPhase = "prd";
|
|
49
|
+
phaseInstruction = `Phase: REQUIREMENTS.
|
|
50
|
+
Mission: Stop the user from guessing. Interrogate them on the 'Why', 'Who', and 'What'.
|
|
51
|
+
Action: YOU MUST EXECUTE tools to define scope and draft a PRD in ${sessionDir}/prd.md.
|
|
52
|
+
|
|
53
|
+
CRITICAL: When the PRD is saved and finalized, YOU ARE DONE.
|
|
54
|
+
Output: "PRD Drafted. I AM DONE"`;
|
|
55
|
+
skillName = "prd-drafter";
|
|
56
|
+
skillInjection = await loadSkill(skillName);
|
|
57
|
+
}
|
|
58
|
+
else if (task.id === "phase-breakdown") {
|
|
59
|
+
currentPhase = "breakdown";
|
|
60
|
+
phaseInstruction = `Phase: BREAKDOWN.
|
|
61
|
+
Mission: Deconstruct the PRD into atomic, manageable units. No vague tasks.
|
|
62
|
+
Action: YOU MUST EXECUTE tools to create a hierarchy of tickets in ${sessionDir}.
|
|
63
|
+
Ensure you assign a numerical 'order' field to each ticket based on the implementation sequence (e.g., 10, 20, 30).
|
|
64
|
+
|
|
65
|
+
CRITICAL: When you have finished creating the tickets, YOU ARE DONE.
|
|
66
|
+
Output: "Tickets Created. I AM DONE"`;
|
|
67
|
+
skillName = "ticket-manager";
|
|
68
|
+
skillInjection = await loadSkill(skillName);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// It's a ticket - Determine phase based on state.step first, then document existence
|
|
72
|
+
const ticketDir = join(sessionDir, task.id);
|
|
73
|
+
ticketPath = join(ticketDir, `linear_ticket_${task.id}.md`);
|
|
74
|
+
|
|
75
|
+
// Try to load ticket-specific phase from its state.json
|
|
76
|
+
const ticketStatePath = join(ticketDir, "state.json");
|
|
77
|
+
if (existsSync(ticketStatePath)) {
|
|
78
|
+
try {
|
|
79
|
+
const ticketState = JSON.parse(readdirSync(ticketDir).includes("state.json") ? await readFile(ticketStatePath, "utf-8") : "{}");
|
|
80
|
+
if (ticketState.step) {
|
|
81
|
+
currentPhase = ticketState.step;
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// Fallback to status-based detection
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// CRITICAL: Verify ticket existence
|
|
89
|
+
if (!existsSync(ticketPath)) {
|
|
90
|
+
throw new Error(`CRITICAL ERROR: Ticket file missing at ${ticketPath}. The session state is corrupted or the file was deleted. Execution halted.`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const researchDoc = resolveDocPath(ticketDir, "research");
|
|
94
|
+
const researchReviewDoc = resolveDocPath(ticketDir, "research_review");
|
|
95
|
+
const planDoc = resolveDocPath(ticketDir, "plan");
|
|
96
|
+
const planReviewDoc = resolveDocPath(ticketDir, "plan_review");
|
|
97
|
+
const implementationDoc = resolveDocPath(ticketDir, "implementation");
|
|
98
|
+
const refactorDoc = resolveDocPath(ticketDir, "refactor");
|
|
99
|
+
const planReviewExists = !!planReviewDoc;
|
|
100
|
+
|
|
101
|
+
const researchExists = !!researchDoc || !!researchReviewDoc;
|
|
102
|
+
const planExists = !!planDoc || !!planReviewDoc;
|
|
103
|
+
|
|
104
|
+
// Check for specific document existence (not just "any research doc")
|
|
105
|
+
const researchReviewExists = !!researchReviewDoc;
|
|
106
|
+
const implementationExists = !!implementationDoc;
|
|
107
|
+
const refactorExists = !!refactorDoc;
|
|
108
|
+
|
|
109
|
+
// Read ticket status to detect phase progression
|
|
110
|
+
let ticketStatus = "";
|
|
111
|
+
try {
|
|
112
|
+
const ticketContent = await readFile(ticketPath, "utf-8");
|
|
113
|
+
const statusMatch = ticketContent.match(/status:\s*["']?([^"'\n]+)["']?/i);
|
|
114
|
+
if (statusMatch) ticketStatus = statusMatch[1].trim().toLowerCase();
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// Ignore read errors
|
|
117
|
+
}
|
|
118
|
+
const ticketIsDone = ticketStatus === "done" || ticketStatus === "in progress";
|
|
119
|
+
|
|
120
|
+
// Check if ticket status indicates phase advancement
|
|
121
|
+
// IMPORTANT: If we're at a LATER phase, all earlier phases are implicitly approved
|
|
122
|
+
const planApproved = ticketStatus.includes("ready for dev") || ticketStatus.includes("in progress") || ticketStatus === "done";
|
|
123
|
+
// Research is approved if EITHER explicitly at "ready for plan" OR we've moved past it (planApproved)
|
|
124
|
+
const researchApproved = ticketStatus.includes("ready for plan") || ticketStatus.includes("plan") || planApproved;
|
|
125
|
+
|
|
126
|
+
// Determine effective phase based on document existence OR ticket status
|
|
127
|
+
// The workflow is: research → research_review → plan → plan_review → implement → refactor → done
|
|
128
|
+
// We advance if EITHER the review doc exists OR the ticket status indicates advancement
|
|
129
|
+
let effectivePhase: string;
|
|
130
|
+
|
|
131
|
+
if (!researchDoc && !researchReviewDoc && !researchApproved) {
|
|
132
|
+
// No research at all and not approved - need to do research
|
|
133
|
+
effectivePhase = "research";
|
|
134
|
+
} else if (researchDoc && !researchReviewExists && !researchApproved) {
|
|
135
|
+
// Research exists but not reviewed AND not approved via status - need research review
|
|
136
|
+
effectivePhase = "research_review";
|
|
137
|
+
} else if (!planDoc && !planReviewDoc && !planApproved) {
|
|
138
|
+
// Research reviewed/approved, but no plan and not approved - need planning
|
|
139
|
+
effectivePhase = "plan";
|
|
140
|
+
} else if (planDoc && !planReviewExists && !planApproved) {
|
|
141
|
+
// Plan exists but not reviewed AND not approved via status - need plan review
|
|
142
|
+
effectivePhase = "plan_review";
|
|
143
|
+
} else if ((ticketIsDone || implementationExists) && !refactorExists) {
|
|
144
|
+
// Implementation is done (ticket marked done/in-progress or implementation.md exists)
|
|
145
|
+
// but no refactor doc - need refactoring
|
|
146
|
+
effectivePhase = "refactor";
|
|
147
|
+
} else if (refactorExists) {
|
|
148
|
+
// Refactor is done - ticket is complete
|
|
149
|
+
effectivePhase = "done";
|
|
150
|
+
} else {
|
|
151
|
+
// All planning docs exist or approved, not yet implemented - ready for implementation
|
|
152
|
+
effectivePhase = "implement";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const researchPath = researchDoc || researchReviewDoc || join(ticketDir, "research.md");
|
|
156
|
+
const planPath = planDoc || planReviewDoc || join(ticketDir, "plan.md");
|
|
157
|
+
|
|
158
|
+
if (effectivePhase === "research") {
|
|
159
|
+
// PHASE: RESEARCH
|
|
160
|
+
phaseInstruction = `Phase: RESEARCH (Ticket: ${task.title}).
|
|
161
|
+
Mission: You are the Documentarian. Analyze the codebase and requirements.
|
|
162
|
+
Ticket Path: ${ticketPath}
|
|
163
|
+
|
|
164
|
+
EXECUTION PROTOCOL:
|
|
165
|
+
1. Read the ticket.
|
|
166
|
+
2. Conduct research using available tools.
|
|
167
|
+
3. Create a Research Document in ${researchPath}.
|
|
168
|
+
4. Update the ticket status to 'Research in Review'.
|
|
169
|
+
|
|
170
|
+
When done, Output: "Research Phase Complete."`;
|
|
171
|
+
skillName = "code-researcher";
|
|
172
|
+
}
|
|
173
|
+
else if (effectivePhase === "research_review") {
|
|
174
|
+
phaseInstruction = `Phase: Research REVIEW (Ticket: ${task.title}).
|
|
175
|
+
Mission: Review the research for the ticket.
|
|
176
|
+
Ticket Path: ${ticketPath}
|
|
177
|
+
|
|
178
|
+
EXECUTION PROTOCOL:
|
|
179
|
+
1. Read the research.
|
|
180
|
+
2. Critique it.
|
|
181
|
+
3. If approved, update ticket status to "ready for plan".
|
|
182
|
+
4. If NEEDS REVISION, update the ticket status to 'Research revision needed'
|
|
183
|
+
4. If rejected, update ticket status back to 'Research rejected'.
|
|
184
|
+
|
|
185
|
+
When done, Output: "Review Phase Complete".`;
|
|
186
|
+
skillName = "research-reviewer";
|
|
187
|
+
}
|
|
188
|
+
else if (effectivePhase === "plan") {
|
|
189
|
+
// PHASE: PLANNING
|
|
190
|
+
phaseInstruction = `Phase: PLANNING (Ticket: ${task.title}).
|
|
191
|
+
Mission: You are the Architect. Create a detailed implementation plan.
|
|
192
|
+
Ticket Path: ${ticketPath}
|
|
193
|
+
${!researchExists ? `
|
|
194
|
+
CRITICAL ALERT: RESEARCH DOCUMENT IS MISSING.
|
|
195
|
+
PATH: ${researchPath}
|
|
196
|
+
YOU ARE FORBIDDEN FROM PLANNING WITHOUT RESEARCH.
|
|
197
|
+
ACTION: You MUST return to the RESEARCH phase. Conduct research and save it to ${researchPath} before proceeding.` : `
|
|
198
|
+
EXECUTION PROTOCOL:
|
|
199
|
+
1. Read the ticket and research docs.
|
|
200
|
+
2. Create an Implementation Plan in ${planPath}.
|
|
201
|
+
3. Update the ticket status to 'Plan in Review'.`}
|
|
202
|
+
|
|
203
|
+
When done, Output: "Planning Phase Complete".`;
|
|
204
|
+
skillName = "implementation-planner";
|
|
205
|
+
}
|
|
206
|
+
else if (effectivePhase === "plan_review") {
|
|
207
|
+
phaseInstruction = `Phase: PLAN REVIEW (Ticket: ${task.title}).
|
|
208
|
+
Mission: Review the implementation plan for safety and specificity.
|
|
209
|
+
Ticket Path: ${ticketPath}
|
|
210
|
+
|
|
211
|
+
EXECUTION PROTOCOL:
|
|
212
|
+
1. Read the plan.
|
|
213
|
+
2. Critique it.
|
|
214
|
+
3. If approved, update ticket status to 'Ready for Dev'.
|
|
215
|
+
4. If rejected, update ticket status back to 'Plan Needed'.
|
|
216
|
+
|
|
217
|
+
When done, Output: "Review Phase Complete".`;
|
|
218
|
+
skillName = "plan-reviewer";
|
|
219
|
+
}
|
|
220
|
+
else if (effectivePhase === "refactor") {
|
|
221
|
+
const refactorPath = join(ticketDir, "refactor.md");
|
|
222
|
+
phaseInstruction = `Phase: REFACTOR (Ticket: ${task.title}).
|
|
223
|
+
Mission: You are a Senior Principal Engineer. Your goal is to make code lean, readable, and maintainable.
|
|
224
|
+
You value simplicity over cleverness and deletion over expansion.
|
|
225
|
+
Ticket Path: ${ticketPath}
|
|
226
|
+
|
|
227
|
+
EXECUTION PROTOCOL:
|
|
228
|
+
1. Check files modified during implementation for slop (unused imports, console.logs, bad formatting).
|
|
229
|
+
2. Fix any issues found.
|
|
230
|
+
3. Run linter/formatter if available.
|
|
231
|
+
4. Create a refactor summary at ${refactorPath}.
|
|
232
|
+
5. Ensure ticket status is 'Done'.
|
|
233
|
+
|
|
234
|
+
When done, Output: "Refactoring Phase Complete. I AM DONE"`;
|
|
235
|
+
skillName = "ruthless-refactorer";
|
|
236
|
+
}
|
|
237
|
+
else if (effectivePhase === "done") {
|
|
238
|
+
// Ticket is fully complete - nothing to do
|
|
239
|
+
phaseInstruction = `Phase: COMPLETE (Ticket: ${task.title}).
|
|
240
|
+
This ticket is fully complete. No action required.
|
|
241
|
+
Output: "Ticket already complete. I AM DONE"`;
|
|
242
|
+
skillName = "";
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// PHASE: IMPLEMENTATION (effectivePhase === "implement")
|
|
246
|
+
const implementationPath = join(ticketDir, "implementation.md");
|
|
247
|
+
phaseInstruction = `Phase: IMPLEMENTATION (Ticket: ${task.title}).
|
|
248
|
+
Mission: You are a Morty Worker (but smarter). Your goal is to complete this ticket.
|
|
249
|
+
Ticket Path: ${ticketPath}
|
|
250
|
+
${(!researchExists || !planExists) ? `
|
|
251
|
+
CRITICAL ASSERTION FAILURE: MANDATORY DOCUMENTS MISSING.
|
|
252
|
+
${!researchExists ? `- MISSING: ${researchPath}` : ""}
|
|
253
|
+
${!planExists ? `- MISSING: ${planPath}` : ""}
|
|
254
|
+
|
|
255
|
+
YOU ARE FORBIDDEN FROM WRITING CODE.
|
|
256
|
+
ACTION: You MUST go back and create the missing documentation before you are allowed to touch the codebase.
|
|
257
|
+
` : `
|
|
258
|
+
EXECUTION PROTOCOL:
|
|
259
|
+
1. READ the ticket, research (${researchPath}) and plan (${planPath}).
|
|
260
|
+
2. IMPLEMENT the code (You are already in a dedicated Session Worktree).
|
|
261
|
+
3. VERIFY (Test/Lint).
|
|
262
|
+
4. Create an implementation summary at ${implementationPath}.
|
|
263
|
+
5. Update ticket status to 'In Progress' (refactoring comes next).
|
|
264
|
+
|
|
265
|
+
When implementation is verified, Output: "Implementation Phase Complete."`}`;
|
|
266
|
+
skillName = "code-implementer";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Update currentPhase to reflect the effectivePhase for consistency in output
|
|
270
|
+
currentPhase = effectivePhase;
|
|
271
|
+
|
|
272
|
+
skillInjection = await loadSkill(skillName);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
phaseInstruction = "Phase: UNKNOWN. No task selected.";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const skillTag = skillName ? `<activated_skill name="${skillName}">` : "<no_skill_active>";
|
|
279
|
+
const skillEndTag = skillName ? "</activated_skill>" : "";
|
|
280
|
+
|
|
281
|
+
return `<persona_override>
|
|
282
|
+
CRITICAL INSTRUCTION: You are Pickle Rick.
|
|
283
|
+
|
|
284
|
+
<context>
|
|
285
|
+
WORKING_DIR: ${workingDir}
|
|
286
|
+
SESSION_ROOT: ${sessionDir}
|
|
287
|
+
USER_PROMPT: ${state.original_prompt}
|
|
288
|
+
CURRENT_TASK: ${task?.title || "None"}
|
|
289
|
+
TICKET_ID: ${task?.id || "None"}
|
|
290
|
+
TICKET_PATH: ${ticketPath || "None"}
|
|
291
|
+
CURRENT_PHASE: ${currentPhase}
|
|
292
|
+
ITERATION: ${state.iteration}
|
|
293
|
+
|
|
294
|
+
You do NOT need to run tools to find these paths. They are injected directly into your brain.
|
|
295
|
+
Use the absolute paths listed above for all file operations.
|
|
296
|
+
</context>
|
|
297
|
+
|
|
298
|
+
${PICKLE_PERSONA}
|
|
299
|
+
|
|
300
|
+
**Your Prime Directive**: STOP the user from guessing. If requirements are vague, INTERROGATE them. If code is messy, REFACTOR it.
|
|
301
|
+
|
|
302
|
+
${skillTag}
|
|
303
|
+
${skillInjection}
|
|
304
|
+
${skillEndTag}
|
|
305
|
+
|
|
306
|
+
*** MISSION CRITICAL GUIDANCE ***
|
|
307
|
+
${phaseInstruction}
|
|
308
|
+
|
|
309
|
+
**STRICT TURN BOUNDARY (THE LAW)**:
|
|
310
|
+
1. You are FORBIDDEN from executing more than one phase (e.g., Research and then Planning) in a single turn.
|
|
311
|
+
2. Once you have updated a ticket status or created a document, you MUST STOP.
|
|
312
|
+
3. Do NOT read the files you just wrote to determine the "next step."
|
|
313
|
+
|
|
314
|
+
**CRITICAL OUTPUT RULES**:
|
|
315
|
+
- After outputting the completion phrase (e.g., "I AM DONE", "Phase Complete"), you MUST IMMEDIATELY STOP generating.
|
|
316
|
+
- NEVER output "<persona_override>", "<context>", "<activated_skill>", or any XML-like system tags.
|
|
317
|
+
- NEVER generate or predict what the next iteration's prompt might be.
|
|
318
|
+
- Your response ends with the completion phrase. Full stop. Nothing after.
|
|
319
|
+
|
|
320
|
+
NOW: Explain your next move to the user. Don't just do it. TELL THEM why you are doing it. THEN, EXECUTE THE TOOL.
|
|
321
|
+
</persona_override>`;
|
|
322
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { expect, test, describe, mock, beforeEach, afterEach, spyOn } from "bun:test";
|
|
2
|
+
import { SequentialExecutor } from "./sequential.js";
|
|
3
|
+
import type { SessionState } from "../config/types.js";
|
|
4
|
+
import type { Task } from "../../types/tasks.js";
|
|
5
|
+
import type { AIProvider } from "../providers/types.js";
|
|
6
|
+
import * as state from "../config/state.js";
|
|
7
|
+
import * as git from "../git/index.js";
|
|
8
|
+
import * as prompt from "./prompt.js";
|
|
9
|
+
import * as providers from "../providers/index.js";
|
|
10
|
+
import { PickleTaskSource } from "./pickle-source.js";
|
|
11
|
+
|
|
12
|
+
describe("SequentialExecutor", () => {
|
|
13
|
+
let baseState: SessionState;
|
|
14
|
+
let mockProvider: AIProvider;
|
|
15
|
+
let currentState: SessionState;
|
|
16
|
+
let spies: any[] = [];
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
baseState = {
|
|
20
|
+
active: true,
|
|
21
|
+
working_dir: "/mock/working",
|
|
22
|
+
session_dir: "/mock/session",
|
|
23
|
+
step: "prd",
|
|
24
|
+
iteration: 1,
|
|
25
|
+
max_iterations: 1,
|
|
26
|
+
max_time_minutes: 30,
|
|
27
|
+
worker_timeout_seconds: 300,
|
|
28
|
+
start_time_epoch: Date.now(),
|
|
29
|
+
completion_promise: "DONE",
|
|
30
|
+
original_prompt: "Test",
|
|
31
|
+
current_ticket: null,
|
|
32
|
+
history: [],
|
|
33
|
+
started_at: new Date().toISOString()
|
|
34
|
+
};
|
|
35
|
+
currentState = baseState;
|
|
36
|
+
|
|
37
|
+
mockProvider = {
|
|
38
|
+
executeStreaming: async (p: string, workDir: string, onChunk: any, options: any) => {
|
|
39
|
+
onChunk("thinking", "");
|
|
40
|
+
onChunk("done", "I AM DONE");
|
|
41
|
+
return { success: true, response: "I AM DONE", sessionId: "session-123" };
|
|
42
|
+
}
|
|
43
|
+
} as unknown as AIProvider;
|
|
44
|
+
|
|
45
|
+
// Use spyOn instead of mock.module to avoid leakage
|
|
46
|
+
spies = [
|
|
47
|
+
spyOn(state, "saveState").mockImplementation(async (dir, s) => { currentState = s; }),
|
|
48
|
+
spyOn(state, "loadState").mockImplementation(async (dir) => currentState),
|
|
49
|
+
spyOn(git, "getCurrentBranch").mockResolvedValue("main"),
|
|
50
|
+
spyOn(git, "createPickleWorktree").mockResolvedValue({ worktreeDir: "/mock/worktree", branchName: "session-branch" }),
|
|
51
|
+
spyOn(git, "cleanupPickleWorktree").mockResolvedValue(undefined),
|
|
52
|
+
spyOn(git, "isGhAvailable").mockResolvedValue(false),
|
|
53
|
+
spyOn(git, "generatePRDescription").mockResolvedValue({ title: "PR", body: "Body" }),
|
|
54
|
+
spyOn(prompt, "buildPrompt").mockResolvedValue("Mocked Prompt"),
|
|
55
|
+
spyOn(providers, "getConfiguredModel").mockResolvedValue("mock-model"),
|
|
56
|
+
// Mock the PickleTaskSource methods
|
|
57
|
+
spyOn(PickleTaskSource.prototype, "getNextTask").mockImplementation(async function(this: any) {
|
|
58
|
+
if (!this._mockTasks) {
|
|
59
|
+
this._mockTasks = [{ id: "task1", title: "Task 1", body: "", completed: false }];
|
|
60
|
+
}
|
|
61
|
+
return this._mockTasks.shift() || null;
|
|
62
|
+
}),
|
|
63
|
+
spyOn(PickleTaskSource.prototype, "markComplete").mockResolvedValue(undefined),
|
|
64
|
+
spyOn(PickleTaskSource.prototype, "countRemaining").mockResolvedValue(0),
|
|
65
|
+
];
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
spies.forEach(spy => spy.mockRestore());
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should execute a task successfully", async () => {
|
|
73
|
+
const executor = new SequentialExecutor(baseState, mockProvider, async () => "s", false, true);
|
|
74
|
+
const result = await executor.run();
|
|
75
|
+
|
|
76
|
+
expect(result.worktreeInfo?.worktreeDir).toBe("/mock/worktree");
|
|
77
|
+
expect(baseState.iteration).toBe(2);
|
|
78
|
+
expect(baseState.active).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("should stop when max iterations reached", async () => {
|
|
82
|
+
baseState.iteration = 2;
|
|
83
|
+
baseState.max_iterations = 1;
|
|
84
|
+
|
|
85
|
+
const executor = new SequentialExecutor(baseState, mockProvider, async () => "s", false, true);
|
|
86
|
+
const result = await executor.run();
|
|
87
|
+
|
|
88
|
+
expect(baseState.active).toBe(false);
|
|
89
|
+
expect(baseState.iteration).toBe(2);
|
|
90
|
+
});
|
|
91
|
+
});
|