@posthog/agent 1.19.0 → 1.20.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 (41) hide show
  1. package/dist/claude-cli/cli.js +2544 -2336
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/src/adapters/claude/claude-adapter.d.ts +3 -2
  7. package/dist/src/adapters/claude/claude-adapter.d.ts.map +1 -1
  8. package/dist/src/adapters/claude/claude-adapter.js +8 -0
  9. package/dist/src/adapters/claude/claude-adapter.js.map +1 -1
  10. package/dist/src/adapters/types.d.ts +6 -1
  11. package/dist/src/adapters/types.d.ts.map +1 -1
  12. package/dist/src/agents/research.d.ts +1 -1
  13. package/dist/src/agents/research.d.ts.map +1 -1
  14. package/dist/src/agents/research.js +55 -5
  15. package/dist/src/agents/research.js.map +1 -1
  16. package/dist/src/file-manager.d.ts +2 -0
  17. package/dist/src/file-manager.d.ts.map +1 -1
  18. package/dist/src/file-manager.js +27 -0
  19. package/dist/src/file-manager.js.map +1 -1
  20. package/dist/src/prompt-builder.d.ts.map +1 -1
  21. package/dist/src/prompt-builder.js +25 -0
  22. package/dist/src/prompt-builder.js.map +1 -1
  23. package/dist/src/todo-manager.d.ts +29 -0
  24. package/dist/src/todo-manager.d.ts.map +1 -0
  25. package/dist/src/todo-manager.js +126 -0
  26. package/dist/src/todo-manager.js.map +1 -0
  27. package/dist/src/workflow/steps/build.d.ts.map +1 -1
  28. package/dist/src/workflow/steps/build.js +7 -0
  29. package/dist/src/workflow/steps/build.js.map +1 -1
  30. package/dist/src/workflow/steps/plan.d.ts.map +1 -1
  31. package/dist/src/workflow/steps/plan.js +10 -3
  32. package/dist/src/workflow/steps/plan.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/adapters/claude/claude-adapter.ts +11 -2
  35. package/src/adapters/types.ts +7 -1
  36. package/src/agents/research.ts +55 -5
  37. package/src/file-manager.ts +30 -0
  38. package/src/prompt-builder.ts +24 -0
  39. package/src/todo-manager.ts +169 -0
  40. package/src/workflow/steps/build.ts +9 -0
  41. package/src/workflow/steps/plan.ts +13 -3
@@ -19,7 +19,14 @@ Calculate an actionabilityScore (0-1) based on:
19
19
 
20
20
  If actionabilityScore < 0.7, generate specific clarifying questions to increase confidence.
21
21
 
22
- DO NOT ask questions like "how should I fix this" — focus on missing information that prevents confident planning.
22
+ Questions must present complete implementation choices, NOT request information from the user:
23
+ options: array of strings
24
+ - GOOD: options: ["Use Redux Toolkit (matches pattern in src/store/)", "Zustand (lighter weight)"]
25
+ - BAD: "Tell me which state management library to use"
26
+ - GOOD: options: ["Place in Button.tsx (existing component)", "create NewButton.tsx (separate concerns)?"]
27
+ - BAD: "Where should I put this code?"
28
+
29
+ DO NOT ask questions like "how should I fix this" or "tell me the pattern" — present concrete options that can be directly chosen and acted upon.
23
30
  </objective>
24
31
 
25
32
  <process>
@@ -60,6 +67,9 @@ Rules:
60
67
  - questions: ONLY include if actionabilityScore < 0.7
61
68
  - Each question must have 2-3 options (maximum 3)
62
69
  - Max 3 questions total
70
+ - Options must be complete, actionable choices that require NO additional user input
71
+ - NEVER use options like "Tell me the pattern", "Show me examples", "Specify the approach"
72
+ - Each option must be a full implementation decision that can be directly acted upon
63
73
  </output_format>
64
74
 
65
75
  <scoring_examples>
@@ -92,11 +102,25 @@ Questions needed: What feature? Which product area? What should it do?
92
102
  "id": "q1",
93
103
  "question": "Which caching layer should we use for API responses?",
94
104
  "options": [
95
- "Redis (existing infrastructure, requires setup)",
96
- "In-memory cache (simpler, but not distributed)",
97
- "Browser-side caching only (minimal backend changes)"
105
+ "Redis with 1-hour TTL (existing infrastructure, requires Redis client setup)",
106
+ "In-memory LRU cache with 100MB limit (simpler, single-server only)",
107
+ "HTTP Cache-Control headers only (minimal backend changes, relies on browser/CDN)"
98
108
  ]
99
109
  }
110
+ Reason: Each option is a complete, actionable decision with concrete details
111
+ </good_example>
112
+
113
+ <good_example>
114
+ {
115
+ "id": "q2",
116
+ "question": "Where should the new analytics tracking code be placed?",
117
+ "options": [
118
+ "In the existing UserAnalytics.ts module alongside page view tracking",
119
+ "Create a new EventTracking.ts module in src/analytics/ for all event tracking",
120
+ "Add directly to each component that needs tracking (no centralized module)"
121
+ ]
122
+ }
123
+ Reason: Specific file paths and architectural patterns, no user input needed
100
124
  </good_example>
101
125
 
102
126
  <bad_example>
@@ -105,7 +129,33 @@ Questions needed: What feature? Which product area? What should it do?
105
129
  "question": "How should I implement this?",
106
130
  "options": ["One way", "Another way"]
107
131
  }
108
- Reason: Too vague, doesn't explain the tradeoffs
132
+ Reason: Too vague, doesn't explain the tradeoffs or provide concrete details
133
+ </bad_example>
134
+
135
+ <bad_example>
136
+ {
137
+ "id": "q2",
138
+ "question": "Which pattern should we follow for state management?",
139
+ "options": [
140
+ "Tell me which pattern the codebase currently uses",
141
+ "Show me examples of state management",
142
+ "Whatever you think is best"
143
+ ]
144
+ }
145
+ Reason: Options request user input instead of being actionable choices. Should be concrete patterns like "Zustand stores (matching existing patterns in src/stores/)" or "React Context (simpler, no new dependencies)"
146
+ </bad_example>
147
+
148
+ <bad_example>
149
+ {
150
+ "id": "q3",
151
+ "question": "What color scheme should the button use?",
152
+ "options": [
153
+ "Use the existing theme colors",
154
+ "Let me specify custom colors",
155
+ "Match the design system"
156
+ ]
157
+ }
158
+ Reason: "Let me specify" requires user input. Should be "Primary blue (#0066FF, existing theme)" or "Secondary gray (#6B7280, existing theme)"
109
159
  </bad_example>
110
160
  </question_examples>`;
111
161
 
@@ -185,6 +185,36 @@ export class PostHogFileManager {
185
185
  }
186
186
  }
187
187
 
188
+ async writeTodos(taskId: string, data: any): Promise<void> {
189
+ this.logger.debug('Writing todos', {
190
+ taskId,
191
+ total: data.metadata?.total ?? 0,
192
+ completed: data.metadata?.completed ?? 0,
193
+ });
194
+
195
+ await this.writeTaskFile(taskId, {
196
+ name: 'todos.json',
197
+ content: JSON.stringify(data, null, 2),
198
+ type: 'artifact'
199
+ });
200
+
201
+ this.logger.info('Todos file written', {
202
+ taskId,
203
+ total: data.metadata?.total ?? 0,
204
+ completed: data.metadata?.completed ?? 0,
205
+ });
206
+ }
207
+
208
+ async readTodos(taskId: string): Promise<any | null> {
209
+ try {
210
+ const content = await this.readTaskFile(taskId, 'todos.json');
211
+ return content ? JSON.parse(content) : null;
212
+ } catch (error) {
213
+ this.logger.debug('Failed to parse todos.json', { error });
214
+ return null;
215
+ }
216
+ }
217
+
188
218
  async getTaskFiles(taskId: string): Promise<SupportingFile[]> {
189
219
  const fileNames = await this.listTaskFiles(taskId);
190
220
  const files: SupportingFile[] = [];
@@ -386,12 +386,16 @@ export class PromptBuilder {
386
386
  try {
387
387
  const taskFiles = await this.getTaskFiles(task.id);
388
388
  const hasPlan = taskFiles.some((f: any) => f.type === 'plan');
389
+ const todosFile = taskFiles.find((f: any) => f.name === 'todos.json');
389
390
 
390
391
  if (taskFiles.length > 0) {
391
392
  prompt += '\n<context>\n';
392
393
  for (const file of taskFiles) {
393
394
  if (file.type === 'plan') {
394
395
  prompt += `<plan>\n${file.content}\n</plan>\n`;
396
+ } else if (file.name === 'todos.json') {
397
+ // skip - we do this below
398
+ continue;
395
399
  } else {
396
400
  prompt += `<file name="${file.name}" type="${file.type}">\n${file.content}\n</file>\n`;
397
401
  }
@@ -399,6 +403,26 @@ export class PromptBuilder {
399
403
  prompt += '</context>\n';
400
404
  }
401
405
 
406
+ // Add todos context if resuming work
407
+ if (todosFile) {
408
+ try {
409
+ const todos = JSON.parse(todosFile.content);
410
+ if (todos.items && todos.items.length > 0) {
411
+ prompt += '\n<previous_todos>\n';
412
+ prompt += 'You previously created the following todo list for this task:\n\n';
413
+ for (const item of todos.items) {
414
+ const statusIcon = item.status === 'completed' ? '✓' : item.status === 'in_progress' ? '▶' : '○';
415
+ prompt += `${statusIcon} [${item.status}] ${item.content}\n`;
416
+ }
417
+ prompt += `\nProgress: ${todos.metadata.completed}/${todos.metadata.total} completed\n`;
418
+ prompt += '\nYou can reference this list when resuming work or create an updated list as needed.\n';
419
+ prompt += '</previous_todos>\n';
420
+ }
421
+ } catch (error) {
422
+ this.logger.debug('Failed to parse todos.json for context', { error });
423
+ }
424
+ }
425
+
402
426
  prompt += '\n<instructions>\n';
403
427
  if (hasPlan) {
404
428
  prompt += 'Implement the changes described in the execution plan. Follow the plan step-by-step and make the necessary file modifications.\n';
@@ -0,0 +1,169 @@
1
+ import type { PostHogFileManager } from './file-manager.js';
2
+ import { Logger } from './utils/logger.js';
3
+
4
+ export interface TodoItem {
5
+ content: string;
6
+ status: 'pending' | 'in_progress' | 'completed';
7
+ activeForm: string;
8
+ }
9
+
10
+ export interface TodoList {
11
+ items: TodoItem[];
12
+ metadata: {
13
+ total: number;
14
+ pending: number;
15
+ in_progress: number;
16
+ completed: number;
17
+ last_updated: string;
18
+ };
19
+ }
20
+
21
+ export class TodoManager {
22
+ private fileManager: PostHogFileManager;
23
+ private logger: Logger;
24
+
25
+ constructor(fileManager: PostHogFileManager, logger?: Logger) {
26
+ this.fileManager = fileManager;
27
+ this.logger = logger || new Logger({ debug: false, prefix: '[TodoManager]' });
28
+ }
29
+
30
+ async readTodos(taskId: string): Promise<TodoList | null> {
31
+ try {
32
+ const content = await this.fileManager.readTaskFile(taskId, 'todos.json');
33
+ if (!content) {
34
+ return null;
35
+ }
36
+
37
+ const parsed = JSON.parse(content) as TodoList;
38
+ this.logger.debug('Loaded todos', {
39
+ taskId,
40
+ total: parsed.metadata.total,
41
+ pending: parsed.metadata.pending,
42
+ in_progress: parsed.metadata.in_progress,
43
+ completed: parsed.metadata.completed,
44
+ });
45
+
46
+ return parsed;
47
+ } catch (error) {
48
+ this.logger.debug('Failed to read todos.json', {
49
+ taskId,
50
+ error: error instanceof Error ? error.message : String(error),
51
+ });
52
+ return null;
53
+ }
54
+ }
55
+
56
+ async writeTodos(taskId: string, todos: TodoList): Promise<void> {
57
+ this.logger.debug('Writing todos', {
58
+ taskId,
59
+ total: todos.metadata.total,
60
+ pending: todos.metadata.pending,
61
+ in_progress: todos.metadata.in_progress,
62
+ completed: todos.metadata.completed,
63
+ });
64
+
65
+ await this.fileManager.writeTaskFile(taskId, {
66
+ name: 'todos.json',
67
+ content: JSON.stringify(todos, null, 2),
68
+ type: 'artifact',
69
+ });
70
+
71
+ this.logger.info('Todos saved', {
72
+ taskId,
73
+ total: todos.metadata.total,
74
+ completed: todos.metadata.completed,
75
+ });
76
+ }
77
+
78
+ parseTodoWriteInput(toolInput: any): TodoList {
79
+ const items: TodoItem[] = [];
80
+
81
+ if (toolInput.todos && Array.isArray(toolInput.todos)) {
82
+ for (const todo of toolInput.todos) {
83
+ items.push({
84
+ content: todo.content || '',
85
+ status: todo.status || 'pending',
86
+ activeForm: todo.activeForm || todo.content || '',
87
+ });
88
+ }
89
+ }
90
+
91
+ const metadata = this.calculateMetadata(items);
92
+
93
+ return { items, metadata };
94
+ }
95
+
96
+ private calculateMetadata(items: TodoItem[]): TodoList['metadata'] {
97
+ const total = items.length;
98
+ const pending = items.filter((t) => t.status === 'pending').length;
99
+ const in_progress = items.filter((t) => t.status === 'in_progress').length;
100
+ const completed = items.filter((t) => t.status === 'completed').length;
101
+
102
+ return {
103
+ total,
104
+ pending,
105
+ in_progress,
106
+ completed,
107
+ last_updated: new Date().toISOString(),
108
+ };
109
+ }
110
+
111
+ async getTodoContext(taskId: string): Promise<string> {
112
+ const todos = await this.readTodos(taskId);
113
+ if (!todos || todos.items.length === 0) {
114
+ return '';
115
+ }
116
+
117
+ const lines: string[] = ['## Previous Todo List\n'];
118
+ lines.push('You previously created the following todo list:\n');
119
+
120
+ for (const item of todos.items) {
121
+ const statusIcon =
122
+ item.status === 'completed' ? '✓' : item.status === 'in_progress' ? '▶' : '○';
123
+ lines.push(`${statusIcon} [${item.status}] ${item.content}`);
124
+ }
125
+
126
+ lines.push(
127
+ `\nProgress: ${todos.metadata.completed}/${todos.metadata.total} completed\n`
128
+ );
129
+
130
+ return lines.join('\n');
131
+ }
132
+
133
+ // check for TodoWrite tool call and persist if found
134
+ async checkAndPersistFromMessage(
135
+ message: any,
136
+ taskId: string
137
+ ): Promise<TodoList | null> {
138
+ if (message.type !== 'assistant' || !message.message?.content) {
139
+ return null;
140
+ }
141
+
142
+ for (const block of message.message.content) {
143
+ if (block.type === 'tool_use' && block.name === 'TodoWrite') {
144
+ try {
145
+ this.logger.info('TodoWrite detected, persisting todos', { taskId });
146
+
147
+ const todoList = this.parseTodoWriteInput(block.input);
148
+ await this.writeTodos(taskId, todoList);
149
+
150
+ this.logger.info('Persisted todos successfully', {
151
+ taskId,
152
+ total: todoList.metadata.total,
153
+ completed: todoList.metadata.completed,
154
+ });
155
+
156
+ return todoList;
157
+ } catch (error) {
158
+ this.logger.error('Failed to persist todos', {
159
+ taskId,
160
+ error: error instanceof Error ? error.message : String(error),
161
+ });
162
+ return null;
163
+ }
164
+ }
165
+ }
166
+
167
+ return null;
168
+ }
169
+ }
@@ -3,6 +3,7 @@ import { EXECUTION_SYSTEM_PROMPT } from '../../agents/execution.js';
3
3
  import { PermissionMode } from '../../types.js';
4
4
  import type { WorkflowStepRunner } from '../types.js';
5
5
  import { finalizeStepGitActions } from '../utils.js';
6
+ import { TodoManager } from '../../todo-manager.js';
6
7
 
7
8
  export const buildStep: WorkflowStepRunner = async ({ step, context }) => {
8
9
  const {
@@ -82,12 +83,20 @@ export const buildStep: WorkflowStepRunner = async ({ step, context }) => {
82
83
  // Track commits made during Claude Code execution
83
84
  const commitTracker = await gitManager.trackCommitsDuring();
84
85
 
86
+ // Track todos from TodoWrite tool calls
87
+ const todoManager = new TodoManager(context.fileManager, stepLogger);
88
+
85
89
  for await (const message of response) {
86
90
  emitEvent(adapter.createRawSDKEvent(message));
87
91
  const transformed = adapter.transform(message);
88
92
  if (transformed) {
89
93
  emitEvent(transformed);
90
94
  }
95
+
96
+ const todoList = await todoManager.checkAndPersistFromMessage(message, task.id);
97
+ if (todoList) {
98
+ emitEvent(adapter.createArtifactEvent('todos', todoList));
99
+ }
91
100
  }
92
101
 
93
102
  // Finalize: commit any remaining changes and optionally push
@@ -2,6 +2,7 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
2
2
  import { PLANNING_SYSTEM_PROMPT } from '../../agents/planning.js';
3
3
  import type { WorkflowStepRunner } from '../types.js';
4
4
  import { finalizeStepGitActions } from '../utils.js';
5
+ import { TodoManager } from '../../todo-manager.js';
5
6
 
6
7
  export const planStep: WorkflowStepRunner = async ({ step, context }) => {
7
8
  const {
@@ -96,6 +97,8 @@ export const planStep: WorkflowStepRunner = async ({ step, context }) => {
96
97
  options: { ...baseOptions, ...(options.queryOverrides || {}) },
97
98
  });
98
99
 
100
+ const todoManager = new TodoManager(fileManager, stepLogger);
101
+
99
102
  let planContent = '';
100
103
  for await (const message of response) {
101
104
  emitEvent(adapter.createRawSDKEvent(message));
@@ -103,10 +106,17 @@ export const planStep: WorkflowStepRunner = async ({ step, context }) => {
103
106
  if (transformed) {
104
107
  emitEvent(transformed);
105
108
  }
109
+
110
+ const todoList = await todoManager.checkAndPersistFromMessage(message, task.id);
111
+ if (todoList) {
112
+ emitEvent(adapter.createArtifactEvent('todos', todoList));
113
+ }
114
+
115
+ // Extract text content for plan
106
116
  if (message.type === 'assistant' && message.message?.content) {
107
- for (const c of message.message.content) {
108
- if (c.type === 'text' && c.text) {
109
- planContent += `${c.text}\n`;
117
+ for (const block of message.message.content) {
118
+ if (block.type === 'text' && block.text) {
119
+ planContent += `${block.text}\n`;
110
120
  }
111
121
  }
112
122
  }