@posthog/agent 1.21.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +3 -3
  3. package/dist/claude-cli/cli.js +1396 -1347
  4. package/dist/index.d.ts +11 -11
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/src/adapters/claude/claude-adapter.d.ts +3 -3
  8. package/dist/src/adapters/claude/claude-adapter.d.ts.map +1 -1
  9. package/dist/src/adapters/claude/claude-adapter.js +156 -111
  10. package/dist/src/adapters/claude/claude-adapter.js.map +1 -1
  11. package/dist/src/adapters/claude/tool-mapper.d.ts +1 -1
  12. package/dist/src/adapters/claude/tool-mapper.d.ts.map +1 -1
  13. package/dist/src/adapters/claude/tool-mapper.js.map +1 -1
  14. package/dist/src/adapters/types.d.ts +1 -1
  15. package/dist/src/adapters/types.d.ts.map +1 -1
  16. package/dist/src/agent.d.ts +7 -7
  17. package/dist/src/agent.d.ts.map +1 -1
  18. package/dist/src/agent.js +143 -85
  19. package/dist/src/agent.js.map +1 -1
  20. package/dist/src/agents/execution.js.map +1 -1
  21. package/dist/src/agents/planning.js.map +1 -1
  22. package/dist/src/agents/research.js.map +1 -1
  23. package/dist/src/file-manager.d.ts +4 -4
  24. package/dist/src/file-manager.d.ts.map +1 -1
  25. package/dist/src/file-manager.js +59 -58
  26. package/dist/src/file-manager.js.map +1 -1
  27. package/dist/src/git-manager.d.ts +2 -1
  28. package/dist/src/git-manager.d.ts.map +1 -1
  29. package/dist/src/git-manager.js +99 -68
  30. package/dist/src/git-manager.js.map +1 -1
  31. package/dist/src/posthog-api.d.ts +2 -3
  32. package/dist/src/posthog-api.d.ts.map +1 -1
  33. package/dist/src/posthog-api.js +22 -22
  34. package/dist/src/posthog-api.js.map +1 -1
  35. package/dist/src/prompt-builder.d.ts +3 -3
  36. package/dist/src/prompt-builder.d.ts.map +1 -1
  37. package/dist/src/prompt-builder.js +123 -93
  38. package/dist/src/prompt-builder.js.map +1 -1
  39. package/dist/src/task-manager.d.ts +4 -4
  40. package/dist/src/task-manager.d.ts.map +1 -1
  41. package/dist/src/task-manager.js +19 -18
  42. package/dist/src/task-manager.js.map +1 -1
  43. package/dist/src/task-progress-reporter.d.ts +3 -4
  44. package/dist/src/task-progress-reporter.d.ts.map +1 -1
  45. package/dist/src/task-progress-reporter.js +69 -53
  46. package/dist/src/task-progress-reporter.js.map +1 -1
  47. package/dist/src/template-manager.d.ts +1 -1
  48. package/dist/src/template-manager.d.ts.map +1 -1
  49. package/dist/src/template-manager.js +30 -28
  50. package/dist/src/template-manager.js.map +1 -1
  51. package/dist/src/todo-manager.d.ts +3 -3
  52. package/dist/src/todo-manager.d.ts.map +1 -1
  53. package/dist/src/todo-manager.js +29 -24
  54. package/dist/src/todo-manager.js.map +1 -1
  55. package/dist/src/tools/registry.d.ts +1 -1
  56. package/dist/src/tools/registry.js +60 -60
  57. package/dist/src/tools/registry.js.map +1 -1
  58. package/dist/src/tools/types.d.ts +31 -31
  59. package/dist/src/types.d.ts +33 -33
  60. package/dist/src/types.d.ts.map +1 -1
  61. package/dist/src/types.js.map +1 -1
  62. package/dist/src/utils/logger.d.ts +4 -4
  63. package/dist/src/utils/logger.d.ts.map +1 -1
  64. package/dist/src/utils/logger.js +8 -8
  65. package/dist/src/utils/logger.js.map +1 -1
  66. package/dist/src/workflow/config.d.ts +1 -1
  67. package/dist/src/workflow/config.d.ts.map +1 -1
  68. package/dist/src/workflow/config.js +18 -18
  69. package/dist/src/workflow/config.js.map +1 -1
  70. package/dist/src/workflow/steps/build.d.ts +1 -1
  71. package/dist/src/workflow/steps/build.d.ts.map +1 -1
  72. package/dist/src/workflow/steps/build.js +46 -38
  73. package/dist/src/workflow/steps/build.js.map +1 -1
  74. package/dist/src/workflow/steps/finalize.d.ts +1 -1
  75. package/dist/src/workflow/steps/finalize.d.ts.map +1 -1
  76. package/dist/src/workflow/steps/finalize.js +62 -47
  77. package/dist/src/workflow/steps/finalize.js.map +1 -1
  78. package/dist/src/workflow/steps/plan.d.ts +1 -1
  79. package/dist/src/workflow/steps/plan.d.ts.map +1 -1
  80. package/dist/src/workflow/steps/plan.js +58 -46
  81. package/dist/src/workflow/steps/plan.js.map +1 -1
  82. package/dist/src/workflow/steps/research.d.ts +1 -1
  83. package/dist/src/workflow/steps/research.d.ts.map +1 -1
  84. package/dist/src/workflow/steps/research.js +68 -56
  85. package/dist/src/workflow/steps/research.js.map +1 -1
  86. package/dist/src/workflow/types.d.ts +12 -12
  87. package/dist/src/workflow/types.d.ts.map +1 -1
  88. package/dist/src/workflow/utils.d.ts +1 -1
  89. package/dist/src/workflow/utils.d.ts.map +1 -1
  90. package/dist/src/workflow/utils.js +7 -4
  91. package/dist/src/workflow/utils.js.map +1 -1
  92. package/package.json +9 -9
  93. package/src/adapters/claude/claude-adapter.ts +220 -168
  94. package/src/adapters/claude/tool-mapper.ts +2 -2
  95. package/src/adapters/types.ts +1 -1
  96. package/src/agent.ts +579 -444
  97. package/src/agents/execution.ts +1 -1
  98. package/src/agents/planning.ts +1 -1
  99. package/src/agents/research.ts +0 -1
  100. package/src/file-manager.ts +64 -63
  101. package/src/git-manager.ts +159 -86
  102. package/src/posthog-api.ts +122 -82
  103. package/src/prompt-builder.ts +180 -135
  104. package/src/task-manager.ts +38 -30
  105. package/src/task-progress-reporter.ts +80 -58
  106. package/src/template-manager.ts +98 -45
  107. package/src/todo-manager.ts +35 -30
  108. package/src/tools/registry.ts +62 -62
  109. package/src/tools/types.ts +36 -36
  110. package/src/types.ts +93 -71
  111. package/src/utils/logger.ts +62 -56
  112. package/src/workflow/config.ts +48 -48
  113. package/src/workflow/steps/build.ts +122 -113
  114. package/src/workflow/steps/finalize.ts +218 -177
  115. package/src/workflow/steps/plan.ts +151 -131
  116. package/src/workflow/steps/research.ts +205 -186
  117. package/src/workflow/types.ts +38 -36
  118. package/src/workflow/utils.ts +37 -34
  119. package/LICENSE +0 -33
@@ -1,6 +1,6 @@
1
- import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
- import { Logger } from './utils/logger.js';
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { Logger } from "./utils/logger.js";
4
4
 
5
5
  const execAsync = promisify(exec);
6
6
 
@@ -27,12 +27,23 @@ export class GitManager {
27
27
  this.repositoryPath = config.repositoryPath;
28
28
  this.authorName = config.authorName;
29
29
  this.authorEmail = config.authorEmail;
30
- this.logger = config.logger || new Logger({ debug: false, prefix: '[GitManager]' });
30
+ this.logger =
31
+ config.logger || new Logger({ debug: false, prefix: "[GitManager]" });
32
+ }
33
+
34
+ private escapeShellArg(str: string): string {
35
+ return str
36
+ .replace(/\\/g, "\\\\")
37
+ .replace(/"/g, '\\"')
38
+ .replace(/`/g, "\\`")
39
+ .replace(/\$/g, "\\$");
31
40
  }
32
41
 
33
42
  private async runGitCommand(command: string): Promise<string> {
34
43
  try {
35
- const { stdout } = await execAsync(`cd "${this.repositoryPath}" && git ${command}`);
44
+ const { stdout } = await execAsync(
45
+ `cd "${this.repositoryPath}" && git ${command}`,
46
+ );
36
47
  return stdout.trim();
37
48
  } catch (error) {
38
49
  throw new Error(`Git command failed: ${command}\n${error}`);
@@ -41,7 +52,9 @@ export class GitManager {
41
52
 
42
53
  private async runCommand(command: string): Promise<string> {
43
54
  try {
44
- const { stdout } = await execAsync(`cd "${this.repositoryPath}" && ${command}`);
55
+ const { stdout } = await execAsync(
56
+ `cd "${this.repositoryPath}" && ${command}`,
57
+ );
45
58
  return stdout.trim();
46
59
  } catch (error) {
47
60
  throw new Error(`Command failed: ${command}\n${error}`);
@@ -50,7 +63,7 @@ export class GitManager {
50
63
 
51
64
  async isGitRepository(): Promise<boolean> {
52
65
  try {
53
- await this.runGitCommand('rev-parse --git-dir');
66
+ await this.runGitCommand("rev-parse --git-dir");
54
67
  return true;
55
68
  } catch {
56
69
  return false;
@@ -58,22 +71,26 @@ export class GitManager {
58
71
  }
59
72
 
60
73
  async getCurrentBranch(): Promise<string> {
61
- return await this.runGitCommand('branch --show-current');
74
+ return await this.runGitCommand("branch --show-current");
62
75
  }
63
76
 
64
77
  async getDefaultBranch(): Promise<string> {
65
78
  try {
66
79
  // Try to get the default branch from remote
67
- const remoteBranch = await this.runGitCommand('symbolic-ref refs/remotes/origin/HEAD');
68
- return remoteBranch.replace('refs/remotes/origin/', '');
80
+ const remoteBranch = await this.runGitCommand(
81
+ "symbolic-ref refs/remotes/origin/HEAD",
82
+ );
83
+ return remoteBranch.replace("refs/remotes/origin/", "");
69
84
  } catch {
70
85
  // Fallback: check if main exists, otherwise use master
71
- if (await this.branchExists('main')) {
72
- return 'main';
73
- } else if (await this.branchExists('master')) {
74
- return 'master';
86
+ if (await this.branchExists("main")) {
87
+ return "main";
88
+ } else if (await this.branchExists("master")) {
89
+ return "master";
75
90
  } else {
76
- throw new Error('Cannot determine default branch. No main or master branch found.');
91
+ throw new Error(
92
+ "Cannot determine default branch. No main or master branch found.",
93
+ );
77
94
  }
78
95
  }
79
96
  }
@@ -88,7 +105,7 @@ export class GitManager {
88
105
  }
89
106
 
90
107
  async createBranch(branchName: string, baseBranch?: string): Promise<void> {
91
- const base = baseBranch || await this.getCurrentBranch();
108
+ const base = baseBranch || (await this.getCurrentBranch());
92
109
  await this.runGitCommand(`checkout -b ${branchName} ${base}`);
93
110
  }
94
111
 
@@ -101,25 +118,31 @@ export class GitManager {
101
118
  const defaultBranch = await this.getDefaultBranch();
102
119
 
103
120
  if (currentBranch === defaultBranch) {
104
- this.logger.debug('Already on default branch', { branch: defaultBranch });
121
+ this.logger.debug("Already on default branch", { branch: defaultBranch });
105
122
  return true;
106
123
  }
107
124
 
108
125
  if (await this.hasChanges()) {
109
- this.logger.warn('Skipping branch reset - uncommitted changes present', {
126
+ this.logger.warn("Skipping branch reset - uncommitted changes present", {
110
127
  currentBranch,
111
- defaultBranch
128
+ defaultBranch,
112
129
  });
113
130
  return false;
114
131
  }
115
132
 
116
133
  await this.switchToBranch(defaultBranch);
117
- this.logger.info('Reset to default branch', { from: currentBranch, to: defaultBranch });
134
+ this.logger.info("Reset to default branch", {
135
+ from: currentBranch,
136
+ to: defaultBranch,
137
+ });
118
138
  return true;
119
139
  }
120
140
 
121
- async createOrSwitchToBranch(branchName: string, baseBranch?: string): Promise<void> {
122
- await this.ensureCleanWorkingDirectory('switching branches');
141
+ async createOrSwitchToBranch(
142
+ branchName: string,
143
+ baseBranch?: string,
144
+ ): Promise<void> {
145
+ await this.ensureCleanWorkingDirectory("switching branches");
123
146
 
124
147
  const exists = await this.branchExists(branchName);
125
148
  if (exists) {
@@ -130,32 +153,44 @@ export class GitManager {
130
153
  }
131
154
 
132
155
  async addFiles(paths: string[]): Promise<void> {
133
- const pathList = paths.map(p => `"${p}"`).join(' ');
156
+ const pathList = paths.map((p) => `"${this.escapeShellArg(p)}"`).join(" ");
134
157
  await this.runGitCommand(`add ${pathList}`);
135
158
  }
136
159
 
137
160
  async addAllPostHogFiles(): Promise<void> {
138
161
  try {
139
162
  // Use -A flag to add all changes (including new files) and ignore errors if directory is empty
140
- await this.runGitCommand('add -A .posthog/');
163
+ await this.runGitCommand("add -A .posthog/");
141
164
  } catch (error) {
142
165
  // If the directory doesn't exist or has no files, that's fine - just log and continue
143
- this.logger.debug('No PostHog files to add', { error });
166
+ this.logger.debug("No PostHog files to add", { error });
144
167
  }
145
168
  }
146
169
 
147
- async commitChanges(message: string, options?: {
148
- authorName?: string;
149
- authorEmail?: string;
150
- }): Promise<string> {
170
+ async commitChanges(
171
+ message: string,
172
+ options?: {
173
+ authorName?: string;
174
+ authorEmail?: string;
175
+ },
176
+ ): Promise<string> {
151
177
  const command = this.buildCommitCommand(message, options);
152
178
  return await this.runGitCommand(command);
153
179
  }
154
180
 
155
181
  async hasChanges(): Promise<boolean> {
156
182
  try {
157
- const status = await this.runGitCommand('status --porcelain');
158
- return status.length > 0;
183
+ const status = await this.runGitCommand("status --porcelain");
184
+ if (!status || status.trim().length === 0) {
185
+ return false;
186
+ }
187
+
188
+ const lines = status.split("\n").filter((line) => {
189
+ const trimmed = line.trim();
190
+ return trimmed.length > 0 && !trimmed.includes(".posthog/");
191
+ });
192
+
193
+ return lines.length > 0;
159
194
  } catch {
160
195
  return false;
161
196
  }
@@ -163,7 +198,7 @@ export class GitManager {
163
198
 
164
199
  async hasStagedChanges(): Promise<boolean> {
165
200
  try {
166
- const status = await this.runGitCommand('diff --cached --name-only');
201
+ const status = await this.runGitCommand("diff --cached --name-only");
167
202
  return status.length > 0;
168
203
  } catch {
169
204
  return false;
@@ -173,12 +208,14 @@ export class GitManager {
173
208
  // Helper: Centralized safety check for uncommitted changes
174
209
  private async ensureCleanWorkingDirectory(operation: string): Promise<void> {
175
210
  if (await this.hasChanges()) {
176
- throw new Error(`Uncommitted changes detected. Please commit or stash changes before ${operation}.`);
211
+ throw new Error(
212
+ `Uncommitted changes detected. Please commit or stash changes before ${operation}.`,
213
+ );
177
214
  }
178
215
  }
179
216
 
180
217
  private async generateUniqueBranchName(baseName: string): Promise<string> {
181
- if (!await this.branchExists(baseName)) {
218
+ if (!(await this.branchExists(baseName))) {
182
219
  return baseName;
183
220
  }
184
221
 
@@ -196,18 +233,25 @@ export class GitManager {
196
233
  const currentBranch = await this.getCurrentBranch();
197
234
 
198
235
  if (currentBranch !== defaultBranch) {
199
- await this.ensureCleanWorkingDirectory('switching to default branch');
236
+ await this.ensureCleanWorkingDirectory("switching to default branch");
200
237
  await this.switchToBranch(defaultBranch);
201
238
  }
202
239
 
203
240
  return defaultBranch;
204
241
  }
205
242
 
206
- private buildCommitCommand(message: string, options?: { allowEmpty?: boolean; authorName?: string; authorEmail?: string }): string {
207
- let command = `commit -m "${message.replace(/"/g, '\\"')}"`;
243
+ private buildCommitCommand(
244
+ message: string,
245
+ options?: {
246
+ allowEmpty?: boolean;
247
+ authorName?: string;
248
+ authorEmail?: string;
249
+ },
250
+ ): string {
251
+ let command = `commit -m "${this.escapeShellArg(message)}"`;
208
252
 
209
253
  if (options?.allowEmpty) {
210
- command += ' --allow-empty';
254
+ command += " --allow-empty";
211
255
  }
212
256
 
213
257
  const authorName = options?.authorName || this.authorName;
@@ -222,14 +266,14 @@ export class GitManager {
222
266
 
223
267
  async getRemoteUrl(): Promise<string | null> {
224
268
  try {
225
- return await this.runGitCommand('remote get-url origin');
269
+ return await this.runGitCommand("remote get-url origin");
226
270
  } catch {
227
271
  return null;
228
272
  }
229
273
  }
230
274
 
231
275
  async pushBranch(branchName: string, force: boolean = false): Promise<void> {
232
- const forceFlag = force ? '--force' : '';
276
+ const forceFlag = force ? "--force" : "";
233
277
  await this.runGitCommand(`push ${forceFlag} -u origin ${branchName}`);
234
278
  }
235
279
 
@@ -248,11 +292,11 @@ export class GitManager {
248
292
  push?: boolean;
249
293
  }) => Promise<{ commitCreated: boolean; pushedBranch: boolean }>;
250
294
  }> {
251
- const initialSha = await this.getCommitSha('HEAD');
295
+ const initialSha = await this.getCommitSha("HEAD");
252
296
 
253
297
  return {
254
298
  finalize: async (options) => {
255
- const currentSha = await this.getCommitSha('HEAD');
299
+ const currentSha = await this.getCommitSha("HEAD");
256
300
  const externalCommitsCreated = initialSha !== currentSha;
257
301
  const hasUncommittedChanges = await this.hasChanges();
258
302
 
@@ -265,7 +309,7 @@ export class GitManager {
265
309
 
266
310
  // Commit any remaining uncommitted changes
267
311
  if (hasUncommittedChanges) {
268
- await this.runGitCommand('add .');
312
+ await this.runGitCommand("add .");
269
313
  const hasStagedChanges = await this.hasStagedChanges();
270
314
 
271
315
  if (hasStagedChanges) {
@@ -280,11 +324,13 @@ export class GitManager {
280
324
  const currentBranch = await this.getCurrentBranch();
281
325
  await this.pushBranch(currentBranch);
282
326
  pushedBranch = true;
283
- this.logger.info('Pushed branch after operation', { branch: currentBranch });
327
+ this.logger.info("Pushed branch after operation", {
328
+ branch: currentBranch,
329
+ });
284
330
  }
285
331
 
286
332
  return { commitCreated, pushedBranch };
287
- }
333
+ },
288
334
  };
289
335
  }
290
336
 
@@ -294,10 +340,10 @@ export class GitManager {
294
340
  // Ensure we're on default branch before creating task branch
295
341
  const defaultBranch = await this.ensureOnDefaultBranch();
296
342
 
297
- this.logger.info('Creating task branch from default branch', {
343
+ this.logger.info("Creating task branch from default branch", {
298
344
  branchName,
299
345
  taskSlug,
300
- baseBranch: defaultBranch
346
+ baseBranch: defaultBranch,
301
347
  });
302
348
 
303
349
  await this.createOrSwitchToBranch(branchName, defaultBranch);
@@ -305,26 +351,35 @@ export class GitManager {
305
351
  return branchName;
306
352
  }
307
353
 
308
- async createTaskPlanningBranch(taskId: string, baseBranch?: string): Promise<string> {
354
+ async createTaskPlanningBranch(
355
+ taskId: string,
356
+ baseBranch?: string,
357
+ ): Promise<string> {
309
358
  const baseName = `posthog/task-${taskId}-planning`;
310
359
  const branchName = await this.generateUniqueBranchName(baseName);
311
360
 
312
- this.logger.debug('Creating unique planning branch', { branchName, taskId });
361
+ this.logger.debug("Creating unique planning branch", {
362
+ branchName,
363
+ taskId,
364
+ });
313
365
 
314
- const base = baseBranch || await this.ensureOnDefaultBranch();
366
+ const base = baseBranch || (await this.ensureOnDefaultBranch());
315
367
  await this.createBranch(branchName, base);
316
368
 
317
369
  return branchName;
318
370
  }
319
371
 
320
- async createTaskImplementationBranch(taskId: string, planningBranchName?: string): Promise<string> {
372
+ async createTaskImplementationBranch(
373
+ taskId: string,
374
+ planningBranchName?: string,
375
+ ): Promise<string> {
321
376
  const baseName = `posthog/task-${taskId}-implementation`;
322
377
  const branchName = await this.generateUniqueBranchName(baseName);
323
378
 
324
- this.logger.debug('Creating unique implementation branch', {
379
+ this.logger.debug("Creating unique implementation branch", {
325
380
  branchName,
326
381
  taskId,
327
- currentBranch: await this.getCurrentBranch()
382
+ currentBranch: await this.getCurrentBranch(),
328
383
  });
329
384
 
330
385
  // Determine base branch: explicit param > current planning branch > default
@@ -332,21 +387,24 @@ export class GitManager {
332
387
 
333
388
  if (!baseBranch) {
334
389
  const currentBranch = await this.getCurrentBranch();
335
- if (currentBranch.includes('-planning')) {
390
+ if (currentBranch.includes("-planning")) {
336
391
  baseBranch = currentBranch;
337
- this.logger.debug('Using current planning branch', { baseBranch });
392
+ this.logger.debug("Using current planning branch", { baseBranch });
338
393
  } else {
339
394
  baseBranch = await this.ensureOnDefaultBranch();
340
- this.logger.debug('Using default branch', { baseBranch });
395
+ this.logger.debug("Using default branch", { baseBranch });
341
396
  }
342
397
  }
343
398
 
344
- this.logger.debug('Creating implementation branch from base', { baseBranch, branchName });
399
+ this.logger.debug("Creating implementation branch from base", {
400
+ baseBranch,
401
+ branchName,
402
+ });
345
403
  await this.createBranch(branchName, baseBranch);
346
404
 
347
- this.logger.info('Implementation branch created', {
405
+ this.logger.info("Implementation branch created", {
348
406
  branchName,
349
- currentBranch: await this.getCurrentBranch()
407
+ currentBranch: await this.getCurrentBranch(),
350
408
  });
351
409
 
352
410
  return branchName;
@@ -354,16 +412,16 @@ export class GitManager {
354
412
 
355
413
  async commitPlan(taskId: string, taskTitle: string): Promise<string> {
356
414
  const currentBranch = await this.getCurrentBranch();
357
- this.logger.debug('Committing plan', { taskId, currentBranch });
415
+ this.logger.debug("Committing plan", { taskId, currentBranch });
358
416
 
359
417
  await this.addAllPostHogFiles();
360
418
 
361
419
  const hasChanges = await this.hasStagedChanges();
362
- this.logger.debug('Checking for staged changes', { hasChanges });
420
+ this.logger.debug("Checking for staged changes", { hasChanges });
363
421
 
364
422
  if (!hasChanges) {
365
- this.logger.info('No plan changes to commit', { taskId });
366
- return 'No changes to commit';
423
+ this.logger.info("No plan changes to commit", { taskId });
424
+ return "No changes to commit";
367
425
  }
368
426
 
369
427
  const message = `📋 Add plan for task: ${taskTitle}
@@ -375,17 +433,21 @@ This commit contains the implementation plan and supporting documentation
375
433
  for the task. Review the plan before proceeding with implementation.`;
376
434
 
377
435
  const result = await this.commitChanges(message);
378
- this.logger.info('Plan committed', { taskId, taskTitle });
436
+ this.logger.info("Plan committed", { taskId, taskTitle });
379
437
  return result;
380
438
  }
381
439
 
382
- async commitImplementation(taskId: string, taskTitle: string, planSummary?: string): Promise<string> {
383
- await this.runGitCommand('add .');
440
+ async commitImplementation(
441
+ taskId: string,
442
+ taskTitle: string,
443
+ planSummary?: string,
444
+ ): Promise<string> {
445
+ await this.runGitCommand("add .");
384
446
 
385
447
  const hasChanges = await this.hasStagedChanges();
386
448
  if (!hasChanges) {
387
- this.logger.warn('No implementation changes to commit', { taskId });
388
- return 'No changes to commit';
449
+ this.logger.warn("No implementation changes to commit", { taskId });
450
+ return "No changes to commit";
389
451
  }
390
452
 
391
453
  let message = `✨ Implement task: ${taskTitle}
@@ -400,12 +462,15 @@ Generated by PostHog Agent`;
400
462
  message += `\n\nThis commit implements the changes described in the task plan.`;
401
463
 
402
464
  const result = await this.commitChanges(message);
403
- this.logger.info('Implementation committed', { taskId, taskTitle });
465
+ this.logger.info("Implementation committed", { taskId, taskTitle });
404
466
  return result;
405
467
  }
406
468
 
407
- async deleteBranch(branchName: string, force: boolean = false): Promise<void> {
408
- const forceFlag = force ? '-D' : '-d';
469
+ async deleteBranch(
470
+ branchName: string,
471
+ force: boolean = false,
472
+ ): Promise<void> {
473
+ const forceFlag = force ? "-D" : "-d";
409
474
  await this.runGitCommand(`branch ${forceFlag} ${branchName}`);
410
475
  }
411
476
 
@@ -420,15 +485,15 @@ Generated by PostHog Agent`;
420
485
  return {
421
486
  name: branchName,
422
487
  exists,
423
- isCurrentBranch: branchName === currentBranch
488
+ isCurrentBranch: branchName === currentBranch,
424
489
  };
425
490
  }
426
491
 
427
- async getCommitSha(ref: string = 'HEAD'): Promise<string> {
492
+ async getCommitSha(ref: string = "HEAD"): Promise<string> {
428
493
  return await this.runGitCommand(`rev-parse ${ref}`);
429
494
  }
430
495
 
431
- async getCommitMessage(ref: string = 'HEAD'): Promise<string> {
496
+ async getCommitMessage(ref: string = "HEAD"): Promise<string> {
432
497
  return await this.runGitCommand(`log -1 --pretty=%B ${ref}`);
433
498
  }
434
499
 
@@ -436,17 +501,17 @@ Generated by PostHog Agent`;
436
501
  branchName: string,
437
502
  title: string,
438
503
  body: string,
439
- baseBranch?: string
504
+ baseBranch?: string,
440
505
  ): Promise<string> {
441
506
  const currentBranch = await this.getCurrentBranch();
442
507
  if (currentBranch !== branchName) {
443
- await this.ensureCleanWorkingDirectory('creating PR');
508
+ await this.ensureCleanWorkingDirectory("creating PR");
444
509
  await this.switchToBranch(branchName);
445
510
  }
446
511
 
447
512
  await this.pushBranch(branchName);
448
513
 
449
- let command = `gh pr create --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}"`;
514
+ let command = `gh pr create --title "${this.escapeShellArg(title)}" --body "${this.escapeShellArg(body)}"`;
450
515
 
451
516
  if (baseBranch) {
452
517
  command += ` --base ${baseBranch}`;
@@ -463,30 +528,35 @@ Generated by PostHog Agent`;
463
528
  async getTaskBranch(taskSlug: string): Promise<string | null> {
464
529
  try {
465
530
  // Get all branches matching the task slug pattern
466
- const branches = await this.runGitCommand('branch --list --all');
531
+ const branches = await this.runGitCommand("branch --list --all");
467
532
  const branchPattern = `posthog/task-${taskSlug}`;
468
-
533
+
469
534
  // Look for exact match or with counter suffix
470
- const lines = branches.split('\n').map(l => l.trim().replace(/^\*\s+/, ''));
535
+ const lines = branches
536
+ .split("\n")
537
+ .map((l) => l.trim().replace(/^\*\s+/, ""));
471
538
  for (const line of lines) {
472
- const cleanBranch = line.replace('remotes/origin/', '');
539
+ const cleanBranch = line.replace("remotes/origin/", "");
473
540
  if (cleanBranch.startsWith(branchPattern)) {
474
541
  return cleanBranch;
475
542
  }
476
543
  }
477
-
544
+
478
545
  return null;
479
546
  } catch (error) {
480
- this.logger.debug('Failed to get task branch', { taskSlug, error });
547
+ this.logger.debug("Failed to get task branch", { taskSlug, error });
481
548
  return null;
482
549
  }
483
550
  }
484
551
 
485
- async commitAndPush(message: string, options?: { allowEmpty?: boolean }): Promise<void> {
552
+ async commitAndPush(
553
+ message: string,
554
+ options?: { allowEmpty?: boolean },
555
+ ): Promise<void> {
486
556
  const hasChanges = await this.hasStagedChanges();
487
557
 
488
558
  if (!hasChanges && !options?.allowEmpty) {
489
- this.logger.debug('No changes to commit, skipping');
559
+ this.logger.debug("No changes to commit, skipping");
490
560
  return;
491
561
  }
492
562
 
@@ -497,6 +567,9 @@ Generated by PostHog Agent`;
497
567
  const currentBranch = await this.getCurrentBranch();
498
568
  await this.pushBranch(currentBranch);
499
569
 
500
- this.logger.info('Committed and pushed changes', { branch: currentBranch, message });
570
+ this.logger.info("Committed and pushed changes", {
571
+ branch: currentBranch,
572
+ message,
573
+ });
501
574
  }
502
575
  }