@gracefultools/astrid-sdk 0.7.16 → 0.8.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 (115) hide show
  1. package/README.md +127 -341
  2. package/dist/channel/channel.d.ts +33 -0
  3. package/dist/channel/channel.d.ts.map +1 -0
  4. package/dist/channel/channel.js +90 -0
  5. package/dist/channel/channel.js.map +1 -0
  6. package/dist/channel/index.d.ts +13 -0
  7. package/dist/channel/index.d.ts.map +1 -0
  8. package/dist/channel/index.js +23 -0
  9. package/dist/channel/index.js.map +1 -0
  10. package/dist/channel/message-formatter.d.ts +14 -0
  11. package/dist/channel/message-formatter.d.ts.map +1 -0
  12. package/dist/channel/message-formatter.js +71 -0
  13. package/dist/channel/message-formatter.js.map +1 -0
  14. package/dist/channel/oauth-client.d.ts +15 -0
  15. package/dist/channel/oauth-client.d.ts.map +1 -0
  16. package/dist/channel/oauth-client.js +45 -0
  17. package/dist/channel/oauth-client.js.map +1 -0
  18. package/dist/channel/rest-client.d.ts +16 -0
  19. package/dist/channel/rest-client.d.ts.map +1 -0
  20. package/dist/channel/rest-client.js +66 -0
  21. package/dist/channel/rest-client.js.map +1 -0
  22. package/dist/channel/session-mapper.d.ts +14 -0
  23. package/dist/channel/session-mapper.d.ts.map +1 -0
  24. package/dist/channel/session-mapper.js +37 -0
  25. package/dist/channel/session-mapper.js.map +1 -0
  26. package/dist/channel/sse-client.d.ts +31 -0
  27. package/dist/channel/sse-client.d.ts.map +1 -0
  28. package/dist/channel/sse-client.js +171 -0
  29. package/dist/channel/sse-client.js.map +1 -0
  30. package/dist/channel/types.d.ts +65 -0
  31. package/dist/channel/types.d.ts.map +1 -0
  32. package/dist/channel/types.js +3 -0
  33. package/dist/channel/types.js.map +1 -0
  34. package/dist/config/agent-workflow.js +7 -7
  35. package/dist/index.d.ts +1 -8
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +16 -30
  38. package/dist/index.js.map +1 -1
  39. package/dist/types/index.d.ts +1 -1
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/dist/utils/agent-config.d.ts.map +1 -1
  42. package/dist/utils/agent-config.js +14 -0
  43. package/dist/utils/agent-config.js.map +1 -1
  44. package/openclaw.plugin.json +25 -0
  45. package/package.json +66 -77
  46. package/templates/.astrid.config.json +60 -60
  47. package/templates/ASTRID.template.md +74 -74
  48. package/dist/bin/cli.d.ts +0 -14
  49. package/dist/bin/cli.d.ts.map +0 -1
  50. package/dist/bin/cli.js +0 -1610
  51. package/dist/bin/cli.js.map +0 -1
  52. package/dist/executors/claude.d.ts +0 -65
  53. package/dist/executors/claude.d.ts.map +0 -1
  54. package/dist/executors/claude.js +0 -838
  55. package/dist/executors/claude.js.map +0 -1
  56. package/dist/executors/gemini.d.ts +0 -23
  57. package/dist/executors/gemini.d.ts.map +0 -1
  58. package/dist/executors/gemini.js +0 -558
  59. package/dist/executors/gemini.js.map +0 -1
  60. package/dist/executors/openai.d.ts +0 -17
  61. package/dist/executors/openai.d.ts.map +0 -1
  62. package/dist/executors/openai.js +0 -614
  63. package/dist/executors/openai.js.map +0 -1
  64. package/dist/executors/shared/index.d.ts +0 -9
  65. package/dist/executors/shared/index.d.ts.map +0 -1
  66. package/dist/executors/shared/index.js +0 -21
  67. package/dist/executors/shared/index.js.map +0 -1
  68. package/dist/executors/shared/tool-executor.d.ts +0 -52
  69. package/dist/executors/shared/tool-executor.d.ts.map +0 -1
  70. package/dist/executors/shared/tool-executor.js +0 -262
  71. package/dist/executors/shared/tool-executor.js.map +0 -1
  72. package/dist/executors/shared/tool-schemas.d.ts +0 -61
  73. package/dist/executors/shared/tool-schemas.d.ts.map +0 -1
  74. package/dist/executors/shared/tool-schemas.js +0 -135
  75. package/dist/executors/shared/tool-schemas.js.map +0 -1
  76. package/dist/executors/terminal-base.d.ts +0 -207
  77. package/dist/executors/terminal-base.d.ts.map +0 -1
  78. package/dist/executors/terminal-base.js +0 -552
  79. package/dist/executors/terminal-base.js.map +0 -1
  80. package/dist/executors/terminal-claude.d.ts +0 -116
  81. package/dist/executors/terminal-claude.d.ts.map +0 -1
  82. package/dist/executors/terminal-claude.js +0 -700
  83. package/dist/executors/terminal-claude.js.map +0 -1
  84. package/dist/executors/terminal-executors.test.d.ts +0 -8
  85. package/dist/executors/terminal-executors.test.d.ts.map +0 -1
  86. package/dist/executors/terminal-executors.test.js +0 -469
  87. package/dist/executors/terminal-executors.test.js.map +0 -1
  88. package/dist/executors/terminal-gemini.d.ts +0 -50
  89. package/dist/executors/terminal-gemini.d.ts.map +0 -1
  90. package/dist/executors/terminal-gemini.js +0 -401
  91. package/dist/executors/terminal-gemini.js.map +0 -1
  92. package/dist/executors/terminal-openai.d.ts +0 -50
  93. package/dist/executors/terminal-openai.d.ts.map +0 -1
  94. package/dist/executors/terminal-openai.js +0 -405
  95. package/dist/executors/terminal-openai.js.map +0 -1
  96. package/dist/server/astrid-client.d.ts +0 -77
  97. package/dist/server/astrid-client.d.ts.map +0 -1
  98. package/dist/server/astrid-client.js +0 -125
  99. package/dist/server/astrid-client.js.map +0 -1
  100. package/dist/server/index.d.ts +0 -38
  101. package/dist/server/index.d.ts.map +0 -1
  102. package/dist/server/index.js +0 -408
  103. package/dist/server/index.js.map +0 -1
  104. package/dist/server/repo-manager.d.ts +0 -41
  105. package/dist/server/repo-manager.d.ts.map +0 -1
  106. package/dist/server/repo-manager.js +0 -177
  107. package/dist/server/repo-manager.js.map +0 -1
  108. package/dist/server/session-manager.d.ts +0 -93
  109. package/dist/server/session-manager.d.ts.map +0 -1
  110. package/dist/server/session-manager.js +0 -217
  111. package/dist/server/session-manager.js.map +0 -1
  112. package/dist/server/webhook-signature.d.ts +0 -23
  113. package/dist/server/webhook-signature.d.ts.map +0 -1
  114. package/dist/server/webhook-signature.js +0 -74
  115. package/dist/server/webhook-signature.js.map +0 -1
@@ -1,838 +0,0 @@
1
- "use strict";
2
- /**
3
- * Claude Agent SDK Executor
4
- *
5
- * Executes code implementation using Claude Code's native tools (Read, Write, Edit, Bash)
6
- * instead of generating code via API and parsing JSON responses.
7
- *
8
- * This provides:
9
- * - Better code quality (real file editing vs generated text)
10
- * - Native error handling and recovery
11
- * - Actual test execution
12
- * - Real git operations
13
- */
14
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- var desc = Object.getOwnPropertyDescriptor(m, k);
17
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
- desc = { enumerable: true, get: function() { return m[k]; } };
19
- }
20
- Object.defineProperty(o, k2, desc);
21
- }) : (function(o, m, k, k2) {
22
- if (k2 === undefined) k2 = k;
23
- o[k2] = m[k];
24
- }));
25
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
- Object.defineProperty(o, "default", { enumerable: true, value: v });
27
- }) : function(o, v) {
28
- o["default"] = v;
29
- });
30
- var __importStar = (this && this.__importStar) || (function () {
31
- var ownKeys = function(o) {
32
- ownKeys = Object.getOwnPropertyNames || function (o) {
33
- var ar = [];
34
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
- return ar;
36
- };
37
- return ownKeys(o);
38
- };
39
- return function (mod) {
40
- if (mod && mod.__esModule) return mod;
41
- var result = {};
42
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
- __setModuleDefault(result, mod);
44
- return result;
45
- };
46
- })();
47
- Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.verifyChanges = verifyChanges;
49
- exports.planWithClaude = planWithClaude;
50
- exports.executeWithClaude = executeWithClaude;
51
- exports.getGitHubUser = getGitHubUser;
52
- exports.prepareRepository = prepareRepository;
53
- const claude_agent_sdk_1 = require("@anthropic-ai/claude-agent-sdk");
54
- const fs = __importStar(require("fs/promises"));
55
- const path = __importStar(require("path"));
56
- const index_js_1 = require("../config/index.js");
57
- // ============================================================================
58
- // REPOSITORY CONTEXT LOADING
59
- // ============================================================================
60
- /**
61
- * Load ASTRID.md from the repository if it exists
62
- * Also loads README.md as fallback context
63
- */
64
- async function loadAstridMd(repoPath, maxLength = 16000) {
65
- const astridPath = path.join(repoPath, 'ASTRID.md');
66
- try {
67
- const content = await fs.readFile(astridPath, 'utf-8');
68
- return content.length > maxLength ? content.substring(0, maxLength) + '\n\n[ASTRID.md truncated...]' : content;
69
- }
70
- catch {
71
- // Try README.md as fallback
72
- try {
73
- const readmePath = path.join(repoPath, 'README.md');
74
- const content = await fs.readFile(readmePath, 'utf-8');
75
- const truncated = content.length > maxLength / 2
76
- ? content.substring(0, maxLength / 2) + '\n\n[README.md truncated...]'
77
- : content;
78
- return `## Project Context (from README.md)\n\n${truncated}`;
79
- }
80
- catch {
81
- return null;
82
- }
83
- }
84
- }
85
- /**
86
- * Detect the test command for the repository
87
- */
88
- async function detectTestCommand(repoPath) {
89
- const packageJsonPath = path.join(repoPath, 'package.json');
90
- try {
91
- const content = await fs.readFile(packageJsonPath, 'utf-8');
92
- const pkg = JSON.parse(content);
93
- // Prefer quick checks over full test suites for faster feedback
94
- if (pkg.scripts?.['predeploy:quick']) {
95
- return 'npm run predeploy:quick';
96
- }
97
- if (pkg.scripts?.typecheck) {
98
- return 'npm run typecheck';
99
- }
100
- if (pkg.scripts?.['type-check']) {
101
- return 'npm run type-check';
102
- }
103
- if (pkg.scripts?.build) {
104
- return 'npm run build';
105
- }
106
- if (pkg.scripts?.predeploy) {
107
- return 'npm run predeploy';
108
- }
109
- if (pkg.scripts?.test) {
110
- return 'npm test';
111
- }
112
- }
113
- catch {
114
- // No package.json or invalid JSON
115
- }
116
- return null;
117
- }
118
- /**
119
- * Verify changes work by running tests/build
120
- * Returns { success, output, error }
121
- */
122
- async function verifyChanges(repoPath, logger) {
123
- const { execSync } = await import('child_process');
124
- const log = logger || (() => { });
125
- const testCommand = await detectTestCommand(repoPath);
126
- if (!testCommand) {
127
- log('info', 'No verification command found, skipping verification');
128
- return { success: true, output: 'No verification command available' };
129
- }
130
- log('info', `Running verification: ${testCommand}`);
131
- try {
132
- const output = execSync(testCommand, {
133
- cwd: repoPath,
134
- encoding: 'utf-8',
135
- timeout: 300000, // 5 minutes for verification
136
- stdio: ['pipe', 'pipe', 'pipe'],
137
- maxBuffer: 10 * 1024 * 1024, // 10MB buffer
138
- });
139
- log('info', 'Verification passed');
140
- return { success: true, output: output.slice(-2000) }; // Last 2000 chars
141
- }
142
- catch (error) {
143
- const err = error;
144
- const combinedOutput = `${err.stdout || ''}\n${err.stderr || ''}`.slice(-3000);
145
- log('error', 'Verification failed', { error: err.message });
146
- return {
147
- success: false,
148
- output: combinedOutput,
149
- error: err.message || 'Verification command failed'
150
- };
151
- }
152
- }
153
- /**
154
- * Detect iOS project and return build info
155
- */
156
- async function detectiOSProject(repoPath) {
157
- const possiblePaths = ['ios-app', 'ios', 'iOS', '.'];
158
- for (const subdir of possiblePaths) {
159
- const searchPath = path.join(repoPath, subdir);
160
- try {
161
- const entries = await fs.readdir(searchPath);
162
- const xcodeproj = entries.find(e => e.endsWith('.xcodeproj'));
163
- if (xcodeproj) {
164
- const schemeName = xcodeproj.replace('.xcodeproj', '');
165
- const projectPath = subdir === '.' ? xcodeproj : `${subdir}/${xcodeproj}`;
166
- return {
167
- hasIOSProject: true,
168
- projectPath,
169
- schemeName,
170
- buildCommand: `cd ${subdir === '.' ? '.' : subdir} && xcodebuild -scheme "${schemeName}" -destination "platform=iOS Simulator,name=iPhone 16" -configuration Debug build 2>&1 | tail -50`
171
- };
172
- }
173
- }
174
- catch {
175
- // Directory doesn't exist
176
- }
177
- }
178
- return null;
179
- }
180
- // ============================================================================
181
- // PLANNING
182
- // ============================================================================
183
- /**
184
- * Generate an implementation plan using Claude Agent SDK
185
- */
186
- async function planWithClaude(taskTitle, taskDescription, config) {
187
- const log = config.logger || (() => { });
188
- const onProgress = config.onProgress || (() => { });
189
- log('info', 'Starting Claude Agent SDK planning', {
190
- repoPath: config.repoPath,
191
- taskTitle
192
- });
193
- onProgress('Initializing Claude Code for planning...');
194
- // Load project-specific configuration
195
- const astridConfig = await (0, index_js_1.loadAstridConfig)(config.repoPath);
196
- const astridMd = await loadAstridMd(config.repoPath);
197
- if (astridMd) {
198
- log('info', 'Loaded ASTRID.md from repository');
199
- onProgress('Loaded project context from ASTRID.md');
200
- }
201
- // Detect platform from config
202
- const platform = (0, index_js_1.detectPlatform)(astridConfig, taskTitle, taskDescription || '');
203
- // Generate prompts from config
204
- const structurePrompt = (0, index_js_1.generateStructurePrompt)(astridConfig);
205
- const platformHints = (0, index_js_1.generatePlatformHints)(platform);
206
- const initialGlobPattern = (0, index_js_1.getInitialGlobPattern)(astridConfig, platform);
207
- if (config.previousContext?.hasBeenProcessedBefore) {
208
- log('info', 'Task has previous context', {
209
- attempts: config.previousContext.previousAttempts.length,
210
- feedbackItems: config.previousContext.userFeedback.length
211
- });
212
- onProgress('Considering previous attempts and user feedback...');
213
- }
214
- const prompt = buildPlanningPrompt(taskTitle, taskDescription, astridMd, config.previousContext);
215
- const options = {
216
- cwd: config.repoPath,
217
- permissionMode: 'default',
218
- maxTurns: config.maxTurns || 30,
219
- maxBudgetUsd: config.maxBudgetUsd || 2.0,
220
- allowedTools: ['Read', 'Glob', 'Grep', 'Bash'],
221
- settingSources: [],
222
- systemPrompt: {
223
- type: 'preset',
224
- preset: 'claude_code',
225
- append: `
226
- You are an expert software engineer analyzing a codebase to create an implementation plan.
227
-
228
- ${structurePrompt}
229
- ${platformHints}
230
- ${astridConfig.customInstructions ? `\n## Custom Instructions\n${astridConfig.customInstructions}\n` : ''}
231
-
232
- ## EXPLORATION WORKFLOW (MANDATORY)
233
-
234
- You MUST follow this workflow before creating a plan:
235
-
236
- ### Step 1: Understand Project Structure
237
- - Use Glob with patterns like "**/*.{ts,tsx,js,jsx}" to find source files
238
- - Use Glob with patterns like "**/package.json" to find project roots
239
- - Read key config files: package.json, tsconfig.json, etc.
240
-
241
- ### Step 2: Find Relevant Code
242
- - Use Grep to search for related terms, functions, or patterns
243
- - Read files that are likely to need changes
244
- - Read adjacent files to understand context and patterns
245
-
246
- ### Step 3: Analyze Patterns
247
- - Note coding conventions (naming, structure, imports)
248
- - Note testing patterns if tests exist
249
- - Note type patterns and interfaces
250
-
251
- ### Step 4: Create Precise Plan
252
- After thorough exploration, create a surgical implementation plan.
253
-
254
- CRITICAL RULES:
255
- 1. DO NOT modify any files - this is READ-ONLY exploration
256
- 2. DO NOT use Write or Edit tools
257
- 3. MUST read at least 3-5 relevant files before creating a plan
258
- 4. MUST use Grep to find related code patterns
259
- ${platform ? `5. For ${platform.name} tasks, START with pattern: "${initialGlobPattern}"` : ''}
260
-
261
- After exploring, output ONLY a JSON block with this exact structure:
262
- \`\`\`json
263
- {
264
- "summary": "Brief summary of what needs to be done",
265
- "approach": "High-level approach with specific technical details",
266
- "files": [
267
- {
268
- "path": "path/to/file.ts",
269
- "purpose": "Why this file needs changes",
270
- "changes": "Specific, detailed changes to make"
271
- }
272
- ],
273
- "estimatedComplexity": "simple|medium|complex",
274
- "considerations": ["Edge case 1", "Testing requirement", "Pattern to follow"]
275
- }
276
- \`\`\`
277
-
278
- PLANNING RULES:
279
- - Maximum 8 files in the plan
280
- - Be SURGICAL: only list files that MUST change
281
- - Include SPECIFIC file paths you discovered (no guessing)
282
- - Follow existing patterns in the codebase
283
- - Consider edge cases and error handling
284
- - Note any tests that need updating
285
- ${astridConfig.protectedPaths?.length ? `- DO NOT modify these paths: ${astridConfig.protectedPaths.join(', ')}` : ''}
286
- `
287
- }
288
- };
289
- const env = { ...process.env };
290
- if (config.apiKey) {
291
- env.ANTHROPIC_API_KEY = config.apiKey;
292
- }
293
- options.env = env;
294
- try {
295
- const messages = [];
296
- let result = null;
297
- const exploredFiles = [];
298
- onProgress('Claude Code is exploring the codebase...');
299
- for await (const message of (0, claude_agent_sdk_1.query)({ prompt, options })) {
300
- messages.push(message);
301
- if (message.type === 'assistant') {
302
- const content = message.message.content;
303
- if (Array.isArray(content)) {
304
- for (const block of content) {
305
- if (block.type === 'text' && block.text) {
306
- onProgress(`Analyzing: ${block.text.substring(0, 100)}...`);
307
- }
308
- if (block.type === 'tool_use') {
309
- onProgress(`Exploring: ${block.name}`);
310
- log('info', 'Tool use (planning)', { tool: block.name, input: block.input });
311
- if (block.name === 'Read' && typeof block.input === 'object' && block.input !== null) {
312
- const input = block.input;
313
- if (input.file_path) {
314
- exploredFiles.push({
315
- path: input.file_path,
316
- content: '',
317
- relevance: 'Explored during planning'
318
- });
319
- }
320
- }
321
- }
322
- }
323
- }
324
- }
325
- if (message.type === 'result') {
326
- result = message;
327
- }
328
- }
329
- if (!result) {
330
- throw new Error('No result received from Claude Agent SDK');
331
- }
332
- if (result.subtype !== 'success') {
333
- const errorResult = result;
334
- throw new Error(`Planning failed: ${result.subtype} - ${errorResult.errors?.join(', ') || 'Unknown error'}`);
335
- }
336
- log('info', 'Claude Agent SDK planning completed', {
337
- turns: result.num_turns,
338
- costUsd: result.total_cost_usd,
339
- filesExplored: exploredFiles.length
340
- });
341
- const plan = parsePlanFromResult(result, exploredFiles, log);
342
- if (!plan) {
343
- throw new Error('Could not parse implementation plan from Claude output');
344
- }
345
- return {
346
- success: true,
347
- plan,
348
- usage: {
349
- inputTokens: result.usage.input_tokens,
350
- outputTokens: result.usage.output_tokens,
351
- costUSD: result.total_cost_usd
352
- }
353
- };
354
- }
355
- catch (error) {
356
- const errorMessage = error instanceof Error ? error.message : String(error);
357
- log('error', 'Claude Agent SDK planning failed', { error: errorMessage });
358
- return {
359
- success: false,
360
- error: errorMessage
361
- };
362
- }
363
- }
364
- // ============================================================================
365
- // EXECUTION
366
- // ============================================================================
367
- /**
368
- * Execute an implementation plan using Claude Agent SDK
369
- */
370
- async function executeWithClaude(plan, taskTitle, taskDescription, config) {
371
- const log = config.logger || (() => { });
372
- const onProgress = config.onProgress || (() => { });
373
- log('info', 'Starting Claude Agent SDK execution', {
374
- repoPath: config.repoPath,
375
- filesInPlan: plan.files.length,
376
- complexity: plan.estimatedComplexity
377
- });
378
- onProgress('Initializing Claude Code agent...');
379
- const astridMd = await loadAstridMd(config.repoPath);
380
- const testCommand = await detectTestCommand(config.repoPath);
381
- const iosProject = await detectiOSProject(config.repoPath);
382
- if (astridMd)
383
- log('info', 'Loaded ASTRID.md from repository');
384
- if (testCommand)
385
- log('info', `Detected test command: ${testCommand}`);
386
- if (iosProject?.hasIOSProject) {
387
- log('info', `Detected iOS project: ${iosProject.projectPath}`);
388
- onProgress(`Found iOS project: ${iosProject.schemeName}`);
389
- }
390
- const prompt = buildImplementationPrompt(plan, taskTitle, taskDescription, astridMd, testCommand, iosProject);
391
- let testingInstructions = '';
392
- if (testCommand) {
393
- testingInstructions += `
394
- WEB/NODE TESTING:
395
- 1. After making code changes, run: ${testCommand}
396
- 2. If tests fail, fix the issues before completing`;
397
- }
398
- if (iosProject?.hasIOSProject) {
399
- testingInstructions += `
400
-
401
- iOS BUILD REQUIREMENTS (CRITICAL):
402
- 1. If you modified ANY Swift files, you MUST run the iOS build
403
- 2. Build command: ${iosProject.buildCommand}
404
- 3. If the build fails, FIX THE ERRORS before completing`;
405
- }
406
- const options = {
407
- cwd: config.repoPath,
408
- permissionMode: 'acceptEdits',
409
- maxTurns: config.maxTurns || 50,
410
- maxBudgetUsd: config.maxBudgetUsd || 5.0,
411
- allowedTools: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash'],
412
- settingSources: [],
413
- systemPrompt: {
414
- type: 'preset',
415
- preset: 'claude_code',
416
- append: `
417
- You are an expert software engineer implementing a coding task from an approved plan.
418
-
419
- ## IMPLEMENTATION WORKFLOW (MANDATORY)
420
-
421
- ### Step 1: Verify Understanding
422
- - Re-read the files in the plan to confirm your approach
423
- - Check import paths and dependencies
424
- - Understand the existing code patterns
425
-
426
- ### Step 2: Implement Changes
427
- - Make changes one file at a time
428
- - Follow existing code style and patterns
429
- - Write complete, production-ready code (no TODOs or placeholders)
430
- - Handle edge cases and errors properly
431
-
432
- ### Step 3: Verify Changes
433
- - After making changes, run the test/build command
434
- - If tests fail, FIX the issues before continuing
435
- - Do not proceed to the next file until current changes pass
436
-
437
- ### Step 4: Final Verification
438
- - Run the full test suite one more time
439
- - Confirm all changes are complete
440
-
441
- CRITICAL RULES:
442
- 1. Follow the implementation plan exactly
443
- 2. Create/modify ONLY the files specified in the plan
444
- 3. Write complete, working code - no placeholders or TODOs
445
- 4. Run tests/builds after making changes - they MUST pass
446
- 5. If verification fails, FIX THE ISSUES before completing
447
- 6. Do NOT commit changes - just make the file edits
448
- ${testingInstructions}
449
-
450
- After completing all changes AND verification passes, output a JSON block:
451
- \`\`\`json
452
- {
453
- "completed": true,
454
- "filesModified": ["path/to/file1.ts"],
455
- "testsRun": true,
456
- "testsPassed": true,
457
- "commitMessage": "brief commit message following conventional commits",
458
- "prTitle": "feat/fix/chore: Short descriptive title",
459
- "prDescription": "## Summary\\n\\nWhat this PR does and why.\\n\\n## Changes\\n\\n- Change 1\\n- Change 2"
460
- }
461
- \`\`\`
462
- `
463
- }
464
- };
465
- const env = { ...process.env };
466
- if (config.apiKey) {
467
- env.ANTHROPIC_API_KEY = config.apiKey;
468
- }
469
- options.env = env;
470
- try {
471
- const messages = [];
472
- let result = null;
473
- onProgress('Claude Code is analyzing the codebase...');
474
- for await (const message of (0, claude_agent_sdk_1.query)({ prompt, options })) {
475
- messages.push(message);
476
- if (message.type === 'assistant') {
477
- const content = message.message.content;
478
- if (Array.isArray(content)) {
479
- for (const block of content) {
480
- if (block.type === 'text' && block.text) {
481
- onProgress(`Working: ${block.text.substring(0, 100)}...`);
482
- }
483
- if (block.type === 'tool_use') {
484
- onProgress(`Using tool: ${block.name}`);
485
- log('info', 'Tool use', { tool: block.name, input: block.input });
486
- }
487
- }
488
- }
489
- }
490
- if (message.type === 'result') {
491
- result = message;
492
- }
493
- }
494
- if (!result) {
495
- throw new Error('No result received from Claude Agent SDK');
496
- }
497
- if (result.subtype !== 'success') {
498
- const errorResult = result;
499
- throw new Error(`Execution failed: ${result.subtype} - ${errorResult.errors?.join(', ') || 'Unknown error'}`);
500
- }
501
- log('info', 'Claude Agent SDK execution completed', {
502
- turns: result.num_turns,
503
- costUsd: result.total_cost_usd,
504
- inputTokens: result.usage.input_tokens,
505
- outputTokens: result.usage.output_tokens
506
- });
507
- const executionResult = await parseExecutionResult(config.repoPath, result, plan, log);
508
- return {
509
- ...executionResult,
510
- usage: {
511
- inputTokens: result.usage.input_tokens,
512
- outputTokens: result.usage.output_tokens,
513
- costUSD: result.total_cost_usd
514
- }
515
- };
516
- }
517
- catch (error) {
518
- const errorMessage = error instanceof Error ? error.message : String(error);
519
- log('error', 'Claude Agent SDK execution failed', { error: errorMessage });
520
- return {
521
- success: false,
522
- files: [],
523
- commitMessage: '',
524
- prTitle: '',
525
- prDescription: '',
526
- error: errorMessage
527
- };
528
- }
529
- }
530
- // ============================================================================
531
- // REPOSITORY PREPARATION
532
- // ============================================================================
533
- /**
534
- * Fetch the authenticated GitHub user's info from the token
535
- */
536
- async function getGitHubUser(githubToken) {
537
- try {
538
- const response = await fetch('https://api.github.com/user', {
539
- headers: {
540
- 'Authorization': `token ${githubToken}`,
541
- 'Accept': 'application/vnd.github.v3+json',
542
- }
543
- });
544
- if (!response.ok) {
545
- throw new Error(`GitHub API error: ${response.status}`);
546
- }
547
- const data = await response.json();
548
- return data;
549
- }
550
- catch {
551
- return { login: 'unknown', email: null, name: null };
552
- }
553
- }
554
- /**
555
- * Prepare a repository for execution
556
- */
557
- async function prepareRepository(repoOwner, repoName, branch, githubToken, workDir, gitAuthor) {
558
- const { execSync } = await import('child_process');
559
- const os = await import('os');
560
- const tempDir = workDir || await fs.mkdtemp(path.join(os.tmpdir(), 'claude-agent-'));
561
- const repoPath = path.join(tempDir, repoName);
562
- // Clone with more history for context (depth 50 instead of 1)
563
- const cloneUrl = `https://x-access-token:${githubToken}@github.com/${repoOwner}/${repoName}.git`;
564
- execSync(`git clone --depth 50 --branch ${branch} ${cloneUrl} ${repoPath}`, {
565
- stdio: 'pipe',
566
- timeout: 180000
567
- });
568
- // Install dependencies if package.json exists
569
- const packageJsonPath = path.join(repoPath, 'package.json');
570
- try {
571
- await fs.access(packageJsonPath);
572
- console.log('📦 Installing dependencies...');
573
- execSync('npm install --legacy-peer-deps', {
574
- cwd: repoPath,
575
- stdio: 'pipe',
576
- timeout: 300000 // 5 minutes for npm install
577
- });
578
- console.log('✅ Dependencies installed');
579
- }
580
- catch {
581
- // No package.json or npm install failed - continue anyway
582
- }
583
- // Use configurable git author for commits
584
- // Priority: 1) env vars, 2) passed gitAuthor, 3) fetch from GitHub API
585
- let gitEmail = process.env.VERCEL_GIT_EMAIL || process.env.AI_AGENT_GIT_EMAIL;
586
- let gitName = process.env.VERCEL_GIT_NAME || process.env.AI_AGENT_GIT_NAME;
587
- if (!gitEmail && gitAuthor?.email) {
588
- gitEmail = gitAuthor.email;
589
- gitName = gitAuthor.name || gitName;
590
- }
591
- // If still no email, fetch from GitHub API (for Vercel preview compatibility)
592
- if (!gitEmail) {
593
- const user = await getGitHubUser(githubToken);
594
- if (user.email) {
595
- gitEmail = user.email;
596
- gitName = gitName || user.name || user.login;
597
- }
598
- else {
599
- // Use noreply email for Vercel compatibility
600
- gitEmail = `${user.login}@users.noreply.github.com`;
601
- gitName = gitName || user.name || user.login;
602
- }
603
- }
604
- gitName = gitName || 'Astrid AI Agent';
605
- execSync(`git config user.email "${gitEmail}"`, { cwd: repoPath });
606
- execSync(`git config user.name "${gitName}"`, { cwd: repoPath });
607
- return {
608
- repoPath,
609
- cleanup: async () => {
610
- try {
611
- await fs.rm(tempDir, { recursive: true, force: true });
612
- }
613
- catch {
614
- // Ignore cleanup errors
615
- }
616
- }
617
- };
618
- }
619
- // ============================================================================
620
- // HELPER FUNCTIONS
621
- // ============================================================================
622
- function buildPlanningPrompt(taskTitle, taskDescription, astridMd, previousContext) {
623
- const contextSection = astridMd
624
- ? `## Project Context (from ASTRID.md)\n\n${astridMd}\n\n---\n\n`
625
- : '';
626
- let previousAttemptsSection = '';
627
- if (previousContext?.hasBeenProcessedBefore) {
628
- previousAttemptsSection = `## Previous Attempts & User Feedback\n\n`;
629
- if (previousContext.previousAttempts.length > 0) {
630
- previousAttemptsSection += `### Previous Attempts\n`;
631
- previousContext.previousAttempts.forEach((attempt, i) => {
632
- previousAttemptsSection += `\n**Attempt ${i + 1}:**\n`;
633
- if (attempt.planSummary)
634
- previousAttemptsSection += `- Previous plan: ${attempt.planSummary}\n`;
635
- if (attempt.filesModified?.length)
636
- previousAttemptsSection += `- Files modified: ${attempt.filesModified.slice(0, 5).join(', ')}\n`;
637
- if (attempt.prUrl)
638
- previousAttemptsSection += `- PR created: ${attempt.prUrl}\n`;
639
- if (attempt.outcome)
640
- previousAttemptsSection += `- Outcome: ${attempt.outcome}\n`;
641
- });
642
- }
643
- if (previousContext.userFeedback.length > 0) {
644
- previousAttemptsSection += `\n### User Feedback (Address These)\n`;
645
- previousContext.userFeedback.forEach(feedback => {
646
- previousAttemptsSection += `\n> "${feedback.content}"\n`;
647
- });
648
- }
649
- previousAttemptsSection += `\n---\n\n`;
650
- }
651
- return `# Planning Task
652
-
653
- ${contextSection}${previousAttemptsSection}## Task to Implement
654
- **${taskTitle}**
655
-
656
- ${taskDescription || 'No additional description provided.'}
657
-
658
- ## Your Mission
659
-
660
- 1. **Check for ASTRID.md** - Read project context
661
- 2. **Explore the codebase** - Understand structure and patterns
662
- 3. **Read key files** - Understand implementations you'll change
663
- 4. **Create an implementation plan** - List specific files to modify
664
-
665
- Begin exploration now.`;
666
- }
667
- function buildImplementationPrompt(plan, taskTitle, taskDescription, astridMd, testCommand, iosProject) {
668
- const filesSection = plan.files
669
- .map(f => `- **${f.path}**: ${f.purpose}\n Changes: ${f.changes}`)
670
- .join('\n');
671
- const contextSection = astridMd
672
- ? `## Project Context\n\n${astridMd.substring(0, 4000)}${astridMd.length > 4000 ? '\n\n[truncated...]' : ''}\n\n---\n\n`
673
- : '';
674
- let testingSection = '## Testing Requirements\n\n';
675
- if (testCommand) {
676
- testingSection += `**Test Command:** \`${testCommand}\`\n\n`;
677
- }
678
- if (iosProject?.hasIOSProject) {
679
- testingSection += `**iOS Build:** \`${iosProject.buildCommand}\`\n\n`;
680
- }
681
- return `# Implementation Task
682
-
683
- ${contextSection}## Task
684
- **${taskTitle}**
685
- ${taskDescription || ''}
686
-
687
- ## Approved Implementation Plan
688
-
689
- ### Summary
690
- ${plan.summary}
691
-
692
- ### Approach
693
- ${plan.approach}
694
-
695
- ### Files to Modify
696
- ${filesSection}
697
-
698
- ### Complexity
699
- ${plan.estimatedComplexity}
700
-
701
- ${testingSection}
702
-
703
- Begin implementation now.`;
704
- }
705
- function parsePlanFromResult(result, exploredFiles, log) {
706
- const resultText = result.result || '';
707
- const jsonMatch = resultText.match(/```json\s*([\s\S]*?)\s*```/);
708
- if (!jsonMatch) {
709
- log('warn', 'No JSON block found in planning output', {});
710
- return null;
711
- }
712
- try {
713
- const parsed = JSON.parse(jsonMatch[1]);
714
- if (!parsed.summary || !parsed.files || !Array.isArray(parsed.files)) {
715
- log('warn', 'Invalid plan structure', { parsed });
716
- return null;
717
- }
718
- // Validate plan has at least 1 file
719
- if (parsed.files.length === 0) {
720
- log('warn', 'Plan has no files to modify', { parsed });
721
- return null;
722
- }
723
- const plan = {
724
- summary: parsed.summary,
725
- approach: parsed.approach || parsed.summary,
726
- files: parsed.files.map((f) => ({
727
- path: f.path,
728
- purpose: f.purpose || 'Implementation',
729
- changes: f.changes || 'See plan'
730
- })),
731
- estimatedComplexity: parsed.estimatedComplexity || 'medium',
732
- considerations: parsed.considerations || [],
733
- exploredFiles
734
- };
735
- log('info', 'Successfully parsed implementation plan', {
736
- filesInPlan: plan.files.length,
737
- filesExplored: plan.exploredFiles?.length || 0
738
- });
739
- return plan;
740
- }
741
- catch (error) {
742
- log('error', 'Failed to parse plan JSON', { error: String(error), jsonText: jsonMatch[1] });
743
- return null;
744
- }
745
- }
746
- async function parseExecutionResult(repoPath, result, plan, log) {
747
- const { execSync } = await import('child_process');
748
- try {
749
- const gitStatusRaw = execSync('git status --porcelain', {
750
- cwd: repoPath,
751
- encoding: 'utf-8'
752
- });
753
- const lines = gitStatusRaw.split('\n').filter(line => line.trim().length > 0);
754
- if (lines.length === 0) {
755
- log('warn', 'No file changes detected by git', {});
756
- return {
757
- success: false,
758
- files: [],
759
- commitMessage: '',
760
- prTitle: '',
761
- prDescription: '',
762
- error: 'No file changes were made'
763
- };
764
- }
765
- const files = [];
766
- for (const line of lines) {
767
- const status = line.substring(0, 2).trim();
768
- const filePath = line.substring(3).trim();
769
- let action;
770
- if (status === 'D' || status === ' D') {
771
- action = 'delete';
772
- }
773
- else if (status === '?' || status === 'A' || status === '??') {
774
- action = 'create';
775
- }
776
- else {
777
- action = 'modify';
778
- }
779
- let content = '';
780
- if (action !== 'delete') {
781
- const fullPath = path.join(repoPath, filePath);
782
- try {
783
- content = await fs.readFile(fullPath, 'utf-8');
784
- }
785
- catch {
786
- log('warn', `Could not read file: ${filePath}`, {});
787
- }
788
- }
789
- files.push({ path: filePath, content, action });
790
- }
791
- log('info', 'Parsed file changes from git', {
792
- totalFiles: files.length,
793
- created: files.filter(f => f.action === 'create').length,
794
- modified: files.filter(f => f.action === 'modify').length,
795
- deleted: files.filter(f => f.action === 'delete').length
796
- });
797
- const resultText = result.result || '';
798
- const jsonMatch = resultText.match(/```json\s*([\s\S]*?)\s*```/);
799
- let commitMessage = `feat: ${plan.summary.substring(0, 50)}`;
800
- let prTitle = plan.summary;
801
- let prDescription = plan.approach;
802
- if (jsonMatch) {
803
- try {
804
- const parsed = JSON.parse(jsonMatch[1]);
805
- if (parsed.commitMessage)
806
- commitMessage = parsed.commitMessage;
807
- if (parsed.prTitle)
808
- prTitle = parsed.prTitle;
809
- if (parsed.prDescription)
810
- prDescription = parsed.prDescription;
811
- }
812
- catch {
813
- log('warn', 'Could not parse JSON output from Claude', {});
814
- }
815
- }
816
- return {
817
- success: true,
818
- files,
819
- commitMessage,
820
- prTitle,
821
- prDescription
822
- };
823
- }
824
- catch (error) {
825
- log('error', 'Failed to parse execution result', {
826
- error: error instanceof Error ? error.message : String(error)
827
- });
828
- return {
829
- success: false,
830
- files: [],
831
- commitMessage: '',
832
- prTitle: '',
833
- prDescription: '',
834
- error: `Failed to parse file changes: ${error instanceof Error ? error.message : String(error)}`
835
- };
836
- }
837
- }
838
- //# sourceMappingURL=claude.js.map