@posthog/agent 1.30.0 → 2.0.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/dist/index.d.ts +51 -95
- package/dist/index.js +887 -2187
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/acp-extensions.ts +1 -51
- package/src/adapters/claude/claude.ts +508 -104
- package/src/adapters/claude/tools.ts +178 -101
- package/src/adapters/connection.ts +95 -0
- package/src/agent.ts +30 -176
- package/src/file-manager.ts +1 -34
- package/src/tools/registry.ts +5 -0
- package/src/tools/types.ts +6 -0
- package/src/types.ts +3 -23
- package/src/worktree-manager.ts +92 -46
- package/dist/templates/plan-template.md +0 -41
- package/src/agents/execution.ts +0 -37
- package/src/agents/planning.ts +0 -60
- package/src/agents/research.ts +0 -160
- package/src/prompt-builder.ts +0 -499
- package/src/template-manager.ts +0 -236
- package/src/templates/plan-template.md +0 -41
- package/src/workflow/config.ts +0 -53
- package/src/workflow/steps/build.ts +0 -135
- package/src/workflow/steps/finalize.ts +0 -241
- package/src/workflow/steps/plan.ts +0 -167
- package/src/workflow/steps/research.ts +0 -223
- package/src/workflow/types.ts +0 -62
- package/src/workflow/utils.ts +0 -53
package/src/agent.ts
CHANGED
|
@@ -9,14 +9,12 @@ import { POSTHOG_NOTIFICATIONS } from "./acp-extensions.js";
|
|
|
9
9
|
import {
|
|
10
10
|
createAcpConnection,
|
|
11
11
|
type InProcessAcpConnection,
|
|
12
|
-
} from "./adapters/
|
|
12
|
+
} from "./adapters/connection.js";
|
|
13
13
|
import { PostHogFileManager } from "./file-manager.js";
|
|
14
14
|
import { GitManager } from "./git-manager.js";
|
|
15
15
|
import { PostHogAPIClient } from "./posthog-api.js";
|
|
16
|
-
import { PromptBuilder } from "./prompt-builder.js";
|
|
17
16
|
import { SessionStore } from "./session-store.js";
|
|
18
17
|
import { TaskManager } from "./task-manager.js";
|
|
19
|
-
import { TemplateManager } from "./template-manager.js";
|
|
20
18
|
import type {
|
|
21
19
|
AgentConfig,
|
|
22
20
|
CanUseTool,
|
|
@@ -25,8 +23,14 @@ import type {
|
|
|
25
23
|
TaskExecutionOptions,
|
|
26
24
|
} from "./types.js";
|
|
27
25
|
import { Logger } from "./utils/logger.js";
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Type for sending ACP notifications
|
|
29
|
+
*/
|
|
30
|
+
type SendNotification = (
|
|
31
|
+
method: string,
|
|
32
|
+
params: Record<string, unknown>,
|
|
33
|
+
) => Promise<void>;
|
|
30
34
|
|
|
31
35
|
export class Agent {
|
|
32
36
|
private workingDirectory: string;
|
|
@@ -34,10 +38,8 @@ export class Agent {
|
|
|
34
38
|
private posthogAPI?: PostHogAPIClient;
|
|
35
39
|
private fileManager: PostHogFileManager;
|
|
36
40
|
private gitManager: GitManager;
|
|
37
|
-
private templateManager: TemplateManager;
|
|
38
41
|
private logger: Logger;
|
|
39
42
|
private acpConnection?: InProcessAcpConnection;
|
|
40
|
-
private promptBuilder: PromptBuilder;
|
|
41
43
|
private mcpServers?: Record<string, any>;
|
|
42
44
|
private canUseTool?: CanUseTool;
|
|
43
45
|
private currentRunId?: string;
|
|
@@ -89,7 +91,6 @@ export class Agent {
|
|
|
89
91
|
repositoryPath: this.workingDirectory,
|
|
90
92
|
logger: this.logger.child("GitManager"),
|
|
91
93
|
});
|
|
92
|
-
this.templateManager = new TemplateManager();
|
|
93
94
|
|
|
94
95
|
if (
|
|
95
96
|
config.posthogApiUrl &&
|
|
@@ -108,13 +109,6 @@ export class Agent {
|
|
|
108
109
|
this.logger.child("SessionStore"),
|
|
109
110
|
);
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
-
this.promptBuilder = new PromptBuilder({
|
|
113
|
-
getTaskFiles: (taskId: string) => this.getTaskFiles(taskId),
|
|
114
|
-
generatePlanTemplate: (vars) => this.templateManager.generatePlan(vars),
|
|
115
|
-
posthogClient: this.posthogAPI,
|
|
116
|
-
logger: this.logger.child("PromptBuilder"),
|
|
117
|
-
});
|
|
118
112
|
}
|
|
119
113
|
|
|
120
114
|
/**
|
|
@@ -146,107 +140,18 @@ export class Agent {
|
|
|
146
140
|
}
|
|
147
141
|
}
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return this.acpConnection;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Adaptive task execution orchestrated via workflow steps
|
|
143
|
+
/**
|
|
144
|
+
* @deprecated Use runTaskV2() for local execution or runTaskCloud() for cloud execution.
|
|
145
|
+
* This method used the old workflow system which has been removed.
|
|
146
|
+
*/
|
|
159
147
|
async runTask(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
148
|
+
_taskId: string,
|
|
149
|
+
_taskRunId: string,
|
|
150
|
+
_options: import("./types.js").TaskExecutionOptions = {},
|
|
163
151
|
): Promise<void> {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const cwd = options.repositoryPath || this.workingDirectory;
|
|
168
|
-
const isCloudMode = options.isCloudMode ?? false;
|
|
169
|
-
const taskSlug = (task as any).slug || task.id;
|
|
170
|
-
|
|
171
|
-
// Use taskRunId as sessionId - they are the same identifier
|
|
172
|
-
this.currentRunId = taskRunId;
|
|
173
|
-
|
|
174
|
-
this.logger.info("Starting adaptive task execution", {
|
|
175
|
-
taskId: task.id,
|
|
176
|
-
taskSlug,
|
|
177
|
-
taskRunId,
|
|
178
|
-
isCloudMode,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const connection = this.getOrCreateConnection();
|
|
182
|
-
|
|
183
|
-
// Create sendNotification using ACP connection's extNotification
|
|
184
|
-
const sendNotification: SendNotification = async (method, params) => {
|
|
185
|
-
this.logger.debug(`Notification: ${method}`, params);
|
|
186
|
-
await connection.agentConnection.extNotification?.(method, params);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
|
|
190
|
-
sessionId: taskRunId,
|
|
191
|
-
runId: taskRunId,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
|
|
195
|
-
|
|
196
|
-
let taskError: Error | undefined;
|
|
197
|
-
try {
|
|
198
|
-
const workflowContext: WorkflowRuntime = {
|
|
199
|
-
task,
|
|
200
|
-
taskSlug,
|
|
201
|
-
runId: taskRunId,
|
|
202
|
-
cwd,
|
|
203
|
-
isCloudMode,
|
|
204
|
-
options,
|
|
205
|
-
logger: this.logger,
|
|
206
|
-
fileManager: this.fileManager,
|
|
207
|
-
gitManager: this.gitManager,
|
|
208
|
-
promptBuilder: this.promptBuilder,
|
|
209
|
-
connection: connection.agentConnection,
|
|
210
|
-
sessionId: taskRunId,
|
|
211
|
-
sendNotification,
|
|
212
|
-
mcpServers: this.mcpServers,
|
|
213
|
-
posthogAPI: this.posthogAPI,
|
|
214
|
-
stepResults: {},
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
for (const step of TASK_WORKFLOW) {
|
|
218
|
-
const result = await step.run({ step, context: workflowContext });
|
|
219
|
-
if (result.halt) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const shouldCreatePR = options.createPR ?? isCloudMode;
|
|
225
|
-
if (shouldCreatePR) {
|
|
226
|
-
await this.ensurePullRequest(
|
|
227
|
-
task,
|
|
228
|
-
workflowContext.stepResults,
|
|
229
|
-
sendNotification,
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
this.logger.info("Task execution complete", { taskId: task.id });
|
|
234
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.TASK_COMPLETE, {
|
|
235
|
-
sessionId: taskRunId,
|
|
236
|
-
taskId: task.id,
|
|
237
|
-
});
|
|
238
|
-
} catch (error) {
|
|
239
|
-
taskError = error instanceof Error ? error : new Error(String(error));
|
|
240
|
-
this.logger.error("Task execution failed", {
|
|
241
|
-
taskId: task.id,
|
|
242
|
-
error: taskError.message,
|
|
243
|
-
});
|
|
244
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.ERROR, {
|
|
245
|
-
sessionId: taskRunId,
|
|
246
|
-
message: taskError.message,
|
|
247
|
-
});
|
|
248
|
-
throw taskError;
|
|
249
|
-
}
|
|
152
|
+
throw new Error(
|
|
153
|
+
"runTask() is deprecated. Use runTaskV2() for local execution or runTaskCloud() for cloud execution.",
|
|
154
|
+
);
|
|
250
155
|
}
|
|
251
156
|
|
|
252
157
|
/**
|
|
@@ -263,8 +168,6 @@ export class Agent {
|
|
|
263
168
|
): Promise<InProcessAcpConnection> {
|
|
264
169
|
await this._configureLlmGateway();
|
|
265
170
|
|
|
266
|
-
const task = await this.fetchTask(taskId);
|
|
267
|
-
const taskSlug = (task as any).slug || task.id;
|
|
268
171
|
const isCloudMode = options.isCloudMode ?? false;
|
|
269
172
|
const _cwd = options.repositoryPath || this.workingDirectory;
|
|
270
173
|
|
|
@@ -272,9 +175,10 @@ export class Agent {
|
|
|
272
175
|
this.currentRunId = taskRunId;
|
|
273
176
|
|
|
274
177
|
this.acpConnection = createAcpConnection({
|
|
178
|
+
framework: options.framework,
|
|
275
179
|
sessionStore: this.sessionStore,
|
|
276
180
|
sessionId: taskRunId,
|
|
277
|
-
taskId
|
|
181
|
+
taskId,
|
|
278
182
|
});
|
|
279
183
|
|
|
280
184
|
const sendNotification: SendNotification = async (method, params) => {
|
|
@@ -285,12 +189,17 @@ export class Agent {
|
|
|
285
189
|
);
|
|
286
190
|
};
|
|
287
191
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
192
|
+
if (!options.isReconnect) {
|
|
193
|
+
await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
|
|
194
|
+
sessionId: taskRunId,
|
|
195
|
+
runId: taskRunId,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
292
198
|
|
|
199
|
+
// Only fetch task when we need the slug for git branch creation
|
|
293
200
|
if (!options.skipGitBranch) {
|
|
201
|
+
const task = options.task ?? (await this.fetchTask(taskId));
|
|
202
|
+
const taskSlug = (task as any).slug || task.id;
|
|
294
203
|
try {
|
|
295
204
|
await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
|
|
296
205
|
} catch (error) {
|
|
@@ -753,61 +662,6 @@ This PR implements the changes described in the task.`;
|
|
|
753
662
|
throw error;
|
|
754
663
|
}
|
|
755
664
|
}
|
|
756
|
-
|
|
757
|
-
private async ensurePullRequest(
|
|
758
|
-
task: Task,
|
|
759
|
-
stepResults: Record<string, any>,
|
|
760
|
-
sendNotification: SendNotification,
|
|
761
|
-
): Promise<void> {
|
|
762
|
-
const latestRun = task.latest_run;
|
|
763
|
-
const existingPr =
|
|
764
|
-
latestRun?.output && typeof latestRun.output === "object"
|
|
765
|
-
? (latestRun.output as any).pr_url
|
|
766
|
-
: null;
|
|
767
|
-
|
|
768
|
-
if (existingPr) {
|
|
769
|
-
this.logger.info("PR already exists, skipping creation", {
|
|
770
|
-
taskId: task.id,
|
|
771
|
-
prUrl: existingPr,
|
|
772
|
-
});
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const buildResult = stepResults.build;
|
|
777
|
-
if (!buildResult?.commitCreated) {
|
|
778
|
-
this.logger.warn(
|
|
779
|
-
"Build step did not produce a commit; skipping PR creation",
|
|
780
|
-
{ taskId: task.id },
|
|
781
|
-
);
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
const branchName = await this.gitManager.getCurrentBranch();
|
|
786
|
-
const finalizeResult = stepResults.finalize;
|
|
787
|
-
const prBody = finalizeResult?.prBody;
|
|
788
|
-
|
|
789
|
-
const prUrl = await this.createPullRequest(
|
|
790
|
-
task.id,
|
|
791
|
-
branchName,
|
|
792
|
-
task.title,
|
|
793
|
-
task.description ?? "",
|
|
794
|
-
prBody,
|
|
795
|
-
);
|
|
796
|
-
|
|
797
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.PR_CREATED, { prUrl });
|
|
798
|
-
|
|
799
|
-
try {
|
|
800
|
-
await this.attachPullRequestToTask(task.id, prUrl, branchName);
|
|
801
|
-
this.logger.info("PR attached to task successfully", {
|
|
802
|
-
taskId: task.id,
|
|
803
|
-
prUrl,
|
|
804
|
-
});
|
|
805
|
-
} catch (error) {
|
|
806
|
-
this.logger.warn("Could not attach PR to task", {
|
|
807
|
-
error: error instanceof Error ? error.message : String(error),
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
665
|
}
|
|
812
666
|
|
|
813
667
|
export type {
|
package/src/file-manager.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import { extname, join } from "node:path";
|
|
3
3
|
import z from "zod";
|
|
4
|
-
import type {
|
|
4
|
+
import type { SupportingFile } from "./types.js";
|
|
5
5
|
import { Logger } from "./utils/logger.js";
|
|
6
6
|
|
|
7
7
|
export interface TaskFile {
|
|
@@ -162,39 +162,6 @@ export class PostHogFileManager {
|
|
|
162
162
|
return await this.readTaskFile(taskId, "requirements.md");
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
async writeResearch(taskId: string, data: ResearchEvaluation): Promise<void> {
|
|
166
|
-
this.logger.debug("Writing research", {
|
|
167
|
-
taskId,
|
|
168
|
-
score: data.actionabilityScore,
|
|
169
|
-
hasQuestions: !!data.questions,
|
|
170
|
-
questionCount: data.questions?.length ?? 0,
|
|
171
|
-
answered: data.answered ?? false,
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
await this.writeTaskFile(taskId, {
|
|
175
|
-
name: "research.json",
|
|
176
|
-
content: JSON.stringify(data, null, 2),
|
|
177
|
-
type: "artifact",
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
this.logger.info("Research file written", {
|
|
181
|
-
taskId,
|
|
182
|
-
score: data.actionabilityScore,
|
|
183
|
-
hasQuestions: !!data.questions,
|
|
184
|
-
answered: data.answered ?? false,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async readResearch(taskId: string): Promise<ResearchEvaluation | null> {
|
|
189
|
-
try {
|
|
190
|
-
const content = await this.readTaskFile(taskId, "research.json");
|
|
191
|
-
return content ? (JSON.parse(content) as ResearchEvaluation) : null;
|
|
192
|
-
} catch (error) {
|
|
193
|
-
this.logger.debug("Failed to parse research.json", { error });
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
165
|
async writeTodos(taskId: string, data: unknown): Promise<void> {
|
|
199
166
|
const todos = z.object({
|
|
200
167
|
metadata: z.object({
|
package/src/tools/registry.ts
CHANGED
|
@@ -84,6 +84,11 @@ const TOOL_DEFINITIONS: Record<string, Tool> = {
|
|
|
84
84
|
category: "assistant",
|
|
85
85
|
description: "Exit plan mode and present plan to user",
|
|
86
86
|
},
|
|
87
|
+
AskUserQuestion: {
|
|
88
|
+
name: "AskUserQuestion",
|
|
89
|
+
category: "assistant",
|
|
90
|
+
description: "Ask the user a clarifying question with options",
|
|
91
|
+
},
|
|
87
92
|
SlashCommand: {
|
|
88
93
|
name: "SlashCommand",
|
|
89
94
|
category: "assistant",
|
package/src/tools/types.ts
CHANGED
|
@@ -100,6 +100,11 @@ export interface ExitPlanModeTool extends Tool {
|
|
|
100
100
|
category: "assistant";
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
export interface AskUserQuestionTool extends Tool {
|
|
104
|
+
name: "AskUserQuestion";
|
|
105
|
+
category: "assistant";
|
|
106
|
+
}
|
|
107
|
+
|
|
103
108
|
export interface SlashCommandTool extends Tool {
|
|
104
109
|
name: "SlashCommand";
|
|
105
110
|
category: "assistant";
|
|
@@ -124,4 +129,5 @@ export type KnownTool =
|
|
|
124
129
|
| TaskTool
|
|
125
130
|
| TodoWriteTool
|
|
126
131
|
| ExitPlanModeTool
|
|
132
|
+
| AskUserQuestionTool
|
|
127
133
|
| SlashCommandTool;
|
package/src/types.ts
CHANGED
|
@@ -141,6 +141,9 @@ export interface TaskExecutionOptions {
|
|
|
141
141
|
// See: https://docs.claude.com/en/api/agent-sdk/permissions
|
|
142
142
|
canUseTool?: CanUseTool;
|
|
143
143
|
skipGitBranch?: boolean; // Skip creating a task-specific git branch
|
|
144
|
+
framework?: "claude"; // Agent framework to use (defaults to "claude")
|
|
145
|
+
task?: Task; // Pre-fetched task to avoid redundant API call
|
|
146
|
+
isReconnect?: boolean; // Session recreation - skip RUN_STARTED notification
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
export interface ExecutionResult {
|
|
@@ -248,29 +251,6 @@ export interface UrlMention {
|
|
|
248
251
|
label?: string;
|
|
249
252
|
}
|
|
250
253
|
|
|
251
|
-
// Research evaluation types
|
|
252
|
-
export interface ResearchQuestion {
|
|
253
|
-
id: string;
|
|
254
|
-
question: string;
|
|
255
|
-
options: string[];
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export interface ResearchAnswer {
|
|
259
|
-
questionId: string;
|
|
260
|
-
selectedOption: string;
|
|
261
|
-
customInput?: string;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export interface ResearchEvaluation {
|
|
265
|
-
actionabilityScore: number; // 0-1 confidence score
|
|
266
|
-
context: string; // brief summary for planning
|
|
267
|
-
keyFiles: string[]; // files needing modification
|
|
268
|
-
blockers?: string[]; // what's preventing full confidence
|
|
269
|
-
questions?: ResearchQuestion[]; // only if score < 0.7
|
|
270
|
-
answered?: boolean; // whether questions have been answered
|
|
271
|
-
answers?: ResearchAnswer[]; // user's answers to questions
|
|
272
|
-
}
|
|
273
|
-
|
|
274
254
|
// Worktree types for parallel task development
|
|
275
255
|
export interface WorktreeInfo {
|
|
276
256
|
worktreePath: string;
|
package/src/worktree-manager.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
2
|
import * as crypto from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
@@ -6,7 +6,6 @@ import { promisify } from "node:util";
|
|
|
6
6
|
import type { WorktreeInfo } from "./types.js";
|
|
7
7
|
import { Logger } from "./utils/logger.js";
|
|
8
8
|
|
|
9
|
-
const execAsync = promisify(exec);
|
|
10
9
|
const execFileAsync = promisify(execFile);
|
|
11
10
|
|
|
12
11
|
export interface WorktreeConfig {
|
|
@@ -512,14 +511,14 @@ export class WorktreeManager {
|
|
|
512
511
|
return this.worktreeBasePath !== null;
|
|
513
512
|
}
|
|
514
513
|
|
|
515
|
-
private async runGitCommand(
|
|
514
|
+
private async runGitCommand(args: string[]): Promise<string> {
|
|
516
515
|
try {
|
|
517
|
-
const { stdout } = await
|
|
516
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
518
517
|
cwd: this.mainRepoPath,
|
|
519
518
|
});
|
|
520
519
|
return stdout.trim();
|
|
521
520
|
} catch (error) {
|
|
522
|
-
throw new Error(`Git command failed: ${
|
|
521
|
+
throw new Error(`Git command failed: git ${args.join(" ")}\n${error}`);
|
|
523
522
|
}
|
|
524
523
|
}
|
|
525
524
|
|
|
@@ -605,48 +604,68 @@ export class WorktreeManager {
|
|
|
605
604
|
}
|
|
606
605
|
|
|
607
606
|
private async getDefaultBranch(): Promise<string> {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
} catch {
|
|
619
|
-
try {
|
|
620
|
-
await this.runGitCommand("rev-parse --verify master");
|
|
621
|
-
return "master";
|
|
622
|
-
} catch {
|
|
623
|
-
throw new Error(
|
|
624
|
-
"Cannot determine default branch. No main or master branch found.",
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
607
|
+
// Try all methods in parallel for speed
|
|
608
|
+
const [symbolicRef, mainExists, masterExists] = await Promise.allSettled([
|
|
609
|
+
this.runGitCommand(["symbolic-ref", "refs/remotes/origin/HEAD"]),
|
|
610
|
+
this.runGitCommand(["rev-parse", "--verify", "main"]),
|
|
611
|
+
this.runGitCommand(["rev-parse", "--verify", "master"]),
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
// Prefer symbolic ref (most accurate)
|
|
615
|
+
if (symbolicRef.status === "fulfilled") {
|
|
616
|
+
return symbolicRef.value.replace("refs/remotes/origin/", "");
|
|
628
617
|
}
|
|
618
|
+
|
|
619
|
+
// Fallback to main if it exists
|
|
620
|
+
if (mainExists.status === "fulfilled") {
|
|
621
|
+
return "main";
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Fallback to master if it exists
|
|
625
|
+
if (masterExists.status === "fulfilled") {
|
|
626
|
+
return "master";
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
throw new Error(
|
|
630
|
+
"Cannot determine default branch. No main or master branch found.",
|
|
631
|
+
);
|
|
629
632
|
}
|
|
630
633
|
|
|
631
634
|
async createWorktree(options?: {
|
|
632
635
|
baseBranch?: string;
|
|
633
636
|
}): Promise<WorktreeInfo> {
|
|
637
|
+
const totalStart = Date.now();
|
|
638
|
+
|
|
639
|
+
// Run setup tasks in parallel for speed
|
|
640
|
+
const setupPromises: Promise<unknown>[] = [];
|
|
641
|
+
|
|
634
642
|
// Only modify .git/info/exclude when using in-repo storage
|
|
635
643
|
if (!this.usesExternalPath()) {
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Ensure the worktree folder exists when using external path
|
|
640
|
-
if (this.usesExternalPath()) {
|
|
644
|
+
setupPromises.push(this.ensureArrayDirIgnored());
|
|
645
|
+
} else {
|
|
646
|
+
// Ensure the worktree folder exists when using external path
|
|
641
647
|
const folderPath = this.getWorktreeFolderPath();
|
|
642
|
-
|
|
648
|
+
setupPromises.push(fs.mkdir(folderPath, { recursive: true }));
|
|
643
649
|
}
|
|
644
650
|
|
|
645
|
-
// Generate unique worktree name
|
|
646
|
-
const
|
|
651
|
+
// Generate unique worktree name (in parallel with above)
|
|
652
|
+
const worktreeNamePromise = this.generateUniqueWorktreeName();
|
|
653
|
+
setupPromises.push(worktreeNamePromise);
|
|
654
|
+
|
|
655
|
+
// Get default branch in parallel if not provided
|
|
656
|
+
const baseBranchPromise = options?.baseBranch
|
|
657
|
+
? Promise.resolve(options.baseBranch)
|
|
658
|
+
: this.getDefaultBranch();
|
|
659
|
+
setupPromises.push(baseBranchPromise);
|
|
660
|
+
|
|
661
|
+
// Wait for all setup to complete
|
|
662
|
+
await Promise.all(setupPromises);
|
|
663
|
+
const setupTime = Date.now() - totalStart;
|
|
664
|
+
|
|
665
|
+
const worktreeName = await worktreeNamePromise;
|
|
666
|
+
const baseBranch = await baseBranchPromise;
|
|
647
667
|
const worktreePath = this.getWorktreePath(worktreeName);
|
|
648
668
|
const branchName = `array/${worktreeName}`;
|
|
649
|
-
const baseBranch = options?.baseBranch ?? (await this.getDefaultBranch());
|
|
650
669
|
|
|
651
670
|
this.logger.info("Creating worktree", {
|
|
652
671
|
worktreeName,
|
|
@@ -654,21 +673,36 @@ export class WorktreeManager {
|
|
|
654
673
|
branchName,
|
|
655
674
|
baseBranch,
|
|
656
675
|
external: this.usesExternalPath(),
|
|
676
|
+
setupTimeMs: setupTime,
|
|
657
677
|
});
|
|
658
678
|
|
|
659
679
|
// Create the worktree with a new branch
|
|
680
|
+
const gitStart = Date.now();
|
|
660
681
|
if (this.usesExternalPath()) {
|
|
661
682
|
// Use absolute path for external worktrees
|
|
662
|
-
await this.runGitCommand(
|
|
663
|
-
|
|
664
|
-
|
|
683
|
+
await this.runGitCommand([
|
|
684
|
+
"worktree",
|
|
685
|
+
"add",
|
|
686
|
+
"--quiet",
|
|
687
|
+
"-b",
|
|
688
|
+
branchName,
|
|
689
|
+
worktreePath,
|
|
690
|
+
baseBranch,
|
|
691
|
+
]);
|
|
665
692
|
} else {
|
|
666
693
|
// Use relative path from repo root for in-repo worktrees
|
|
667
|
-
const relativePath =
|
|
668
|
-
await this.runGitCommand(
|
|
669
|
-
|
|
670
|
-
|
|
694
|
+
const relativePath = `./${WORKTREE_FOLDER_NAME}/${worktreeName}`;
|
|
695
|
+
await this.runGitCommand([
|
|
696
|
+
"worktree",
|
|
697
|
+
"add",
|
|
698
|
+
"--quiet",
|
|
699
|
+
"-b",
|
|
700
|
+
branchName,
|
|
701
|
+
relativePath,
|
|
702
|
+
baseBranch,
|
|
703
|
+
]);
|
|
671
704
|
}
|
|
705
|
+
const gitTime = Date.now() - gitStart;
|
|
672
706
|
|
|
673
707
|
const createdAt = new Date().toISOString();
|
|
674
708
|
|
|
@@ -676,6 +710,9 @@ export class WorktreeManager {
|
|
|
676
710
|
worktreeName,
|
|
677
711
|
worktreePath,
|
|
678
712
|
branchName,
|
|
713
|
+
setupTimeMs: setupTime,
|
|
714
|
+
gitWorktreeAddMs: gitTime,
|
|
715
|
+
totalMs: Date.now() - totalStart,
|
|
679
716
|
});
|
|
680
717
|
|
|
681
718
|
return {
|
|
@@ -758,7 +795,7 @@ export class WorktreeManager {
|
|
|
758
795
|
try {
|
|
759
796
|
await fs.rm(worktreePath, { recursive: true, force: true });
|
|
760
797
|
// Also prune the worktree list
|
|
761
|
-
await this.runGitCommand("worktree prune");
|
|
798
|
+
await this.runGitCommand(["worktree", "prune"]);
|
|
762
799
|
this.logger.info("Worktree cleaned up manually", { worktreePath });
|
|
763
800
|
} catch (cleanupError) {
|
|
764
801
|
this.logger.error("Failed to cleanup worktree", {
|
|
@@ -773,7 +810,11 @@ export class WorktreeManager {
|
|
|
773
810
|
async getWorktreeInfo(worktreePath: string): Promise<WorktreeInfo | null> {
|
|
774
811
|
try {
|
|
775
812
|
// Parse the worktree list to find info about this worktree
|
|
776
|
-
const output = await this.runGitCommand(
|
|
813
|
+
const output = await this.runGitCommand([
|
|
814
|
+
"worktree",
|
|
815
|
+
"list",
|
|
816
|
+
"--porcelain",
|
|
817
|
+
]);
|
|
777
818
|
const worktrees = this.parseWorktreeList(output);
|
|
778
819
|
|
|
779
820
|
const worktree = worktrees.find((w) => w.worktreePath === worktreePath);
|
|
@@ -786,7 +827,11 @@ export class WorktreeManager {
|
|
|
786
827
|
|
|
787
828
|
async listWorktrees(): Promise<WorktreeInfo[]> {
|
|
788
829
|
try {
|
|
789
|
-
const output = await this.runGitCommand(
|
|
830
|
+
const output = await this.runGitCommand([
|
|
831
|
+
"worktree",
|
|
832
|
+
"list",
|
|
833
|
+
"--porcelain",
|
|
834
|
+
]);
|
|
790
835
|
return this.parseWorktreeList(output);
|
|
791
836
|
} catch (error) {
|
|
792
837
|
this.logger.debug("Failed to list worktrees", { error });
|
|
@@ -836,8 +881,9 @@ export class WorktreeManager {
|
|
|
836
881
|
|
|
837
882
|
async isWorktree(repoPath: string): Promise<boolean> {
|
|
838
883
|
try {
|
|
839
|
-
const { stdout } = await
|
|
840
|
-
"git
|
|
884
|
+
const { stdout } = await execFileAsync(
|
|
885
|
+
"git",
|
|
886
|
+
["rev-parse", "--is-inside-work-tree"],
|
|
841
887
|
{ cwd: repoPath },
|
|
842
888
|
);
|
|
843
889
|
if (stdout.trim() !== "true") {
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Implementation Plan: {{task_title}}
|
|
2
|
-
|
|
3
|
-
**Task ID:** {{task_id}}
|
|
4
|
-
**Generated:** {{date}}
|
|
5
|
-
|
|
6
|
-
## Summary
|
|
7
|
-
|
|
8
|
-
Brief description of what will be implemented and the overall approach.
|
|
9
|
-
|
|
10
|
-
## Implementation Steps
|
|
11
|
-
|
|
12
|
-
### 1. Analysis
|
|
13
|
-
- [ ] Identify relevant files and components
|
|
14
|
-
- [ ] Review existing patterns and constraints
|
|
15
|
-
|
|
16
|
-
### 2. Changes Required
|
|
17
|
-
- [ ] Files to create/modify
|
|
18
|
-
- [ ] Dependencies to add/update
|
|
19
|
-
|
|
20
|
-
### 3. Implementation
|
|
21
|
-
- [ ] Core functionality changes
|
|
22
|
-
- [ ] Tests and validation
|
|
23
|
-
- [ ] Documentation updates
|
|
24
|
-
|
|
25
|
-
## File Changes
|
|
26
|
-
|
|
27
|
-
### New Files
|
|
28
|
-
```
|
|
29
|
-
path/to/new/file.ts - Purpose
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Modified Files
|
|
33
|
-
```
|
|
34
|
-
path/to/existing/file.ts - Changes needed
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Considerations
|
|
38
|
-
|
|
39
|
-
- Key architectural decisions
|
|
40
|
-
- Potential risks and mitigation
|
|
41
|
-
- Testing approach
|