@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.
- package/dist/claude-cli/cli.js +2544 -2336
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/adapters/claude/claude-adapter.d.ts +3 -2
- package/dist/src/adapters/claude/claude-adapter.d.ts.map +1 -1
- package/dist/src/adapters/claude/claude-adapter.js +8 -0
- package/dist/src/adapters/claude/claude-adapter.js.map +1 -1
- package/dist/src/adapters/types.d.ts +6 -1
- package/dist/src/adapters/types.d.ts.map +1 -1
- package/dist/src/agents/research.d.ts +1 -1
- package/dist/src/agents/research.d.ts.map +1 -1
- package/dist/src/agents/research.js +55 -5
- package/dist/src/agents/research.js.map +1 -1
- package/dist/src/file-manager.d.ts +2 -0
- package/dist/src/file-manager.d.ts.map +1 -1
- package/dist/src/file-manager.js +27 -0
- package/dist/src/file-manager.js.map +1 -1
- package/dist/src/prompt-builder.d.ts.map +1 -1
- package/dist/src/prompt-builder.js +25 -0
- package/dist/src/prompt-builder.js.map +1 -1
- package/dist/src/todo-manager.d.ts +29 -0
- package/dist/src/todo-manager.d.ts.map +1 -0
- package/dist/src/todo-manager.js +126 -0
- package/dist/src/todo-manager.js.map +1 -0
- package/dist/src/workflow/steps/build.d.ts.map +1 -1
- package/dist/src/workflow/steps/build.js +7 -0
- package/dist/src/workflow/steps/build.js.map +1 -1
- package/dist/src/workflow/steps/plan.d.ts.map +1 -1
- package/dist/src/workflow/steps/plan.js +10 -3
- package/dist/src/workflow/steps/plan.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/claude-adapter.ts +11 -2
- package/src/adapters/types.ts +7 -1
- package/src/agents/research.ts +55 -5
- package/src/file-manager.ts +30 -0
- package/src/prompt-builder.ts +24 -0
- package/src/todo-manager.ts +169 -0
- package/src/workflow/steps/build.ts +9 -0
- package/src/workflow/steps/plan.ts +13 -3
package/src/agents/research.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
97
|
-
"
|
|
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
|
|
package/src/file-manager.ts
CHANGED
|
@@ -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[] = [];
|
package/src/prompt-builder.ts
CHANGED
|
@@ -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
|
|
108
|
-
if (
|
|
109
|
-
planContent += `${
|
|
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
|
}
|