@gracefultools/astrid-sdk 0.6.1 → 0.7.1

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Terminal Executor Base Interface
3
+ *
4
+ * Common interface for all terminal executors (Claude, OpenAI, Gemini).
5
+ * Terminal mode executes tasks using local tools instead of remote APIs.
6
+ */
7
+ import type { Session } from '../server/session-manager.js';
8
+ /**
9
+ * Result from terminal execution
10
+ */
11
+ export interface TerminalExecutionResult {
12
+ exitCode: number | null;
13
+ stdout: string;
14
+ stderr: string;
15
+ sessionId?: string;
16
+ gitDiff?: string;
17
+ modifiedFiles?: string[];
18
+ prUrl?: string;
19
+ }
20
+ /**
21
+ * Context for terminal task execution
22
+ */
23
+ export interface TerminalTaskContext {
24
+ comments?: Array<{
25
+ authorName: string;
26
+ content: string;
27
+ createdAt: string;
28
+ }>;
29
+ prUrl?: string;
30
+ repository?: string;
31
+ }
32
+ /**
33
+ * Parsed output from terminal execution
34
+ */
35
+ export interface ParsedOutput {
36
+ summary?: string;
37
+ files?: string[];
38
+ prUrl?: string;
39
+ error?: string;
40
+ }
41
+ /**
42
+ * Progress callback function
43
+ */
44
+ export type TerminalProgressCallback = (message: string) => void;
45
+ /**
46
+ * Common interface for all terminal executors.
47
+ *
48
+ * Terminal executors process tasks using local tool execution:
49
+ * - Claude: Spawns local Claude Code CLI
50
+ * - OpenAI: Uses OpenAI API with local tool execution
51
+ * - Gemini: Uses Gemini API with local tool execution
52
+ */
53
+ export interface TerminalExecutor {
54
+ /**
55
+ * Check if the executor is available (e.g., CLI installed, API key set)
56
+ */
57
+ checkAvailable(): Promise<boolean>;
58
+ /**
59
+ * Start a new session to process a task
60
+ *
61
+ * @param session - The session containing task details
62
+ * @param prompt - Optional custom prompt (uses default if not provided)
63
+ * @param context - Optional context including comments and PR info
64
+ * @param onProgress - Optional callback for progress updates
65
+ * @returns Execution result with output, modified files, and PR URL
66
+ */
67
+ startSession(session: Session, prompt?: string, context?: TerminalTaskContext, onProgress?: TerminalProgressCallback): Promise<TerminalExecutionResult>;
68
+ /**
69
+ * Resume an existing session with new input
70
+ *
71
+ * @param session - The session to resume
72
+ * @param input - New input from user (e.g., follow-up message)
73
+ * @param context - Optional updated context
74
+ * @param onProgress - Optional callback for progress updates
75
+ * @returns Execution result
76
+ */
77
+ resumeSession(session: Session, input: string, context?: TerminalTaskContext, onProgress?: TerminalProgressCallback): Promise<TerminalExecutionResult>;
78
+ /**
79
+ * Parse output to extract key information
80
+ *
81
+ * @param output - Raw output string from execution
82
+ * @returns Parsed output with summary, files, PR URL, and error
83
+ */
84
+ parseOutput(output: string): ParsedOutput;
85
+ }
86
+ /**
87
+ * Extract PR URL from output text
88
+ */
89
+ export declare function extractPrUrl(output: string): string | undefined;
90
+ /**
91
+ * Format comment history for context
92
+ */
93
+ export declare function formatCommentHistory(comments?: TerminalTaskContext['comments']): string;
94
+ /**
95
+ * Capture git changes in a repository
96
+ */
97
+ export declare function captureGitChanges(projectPath: string): Promise<{
98
+ diff: string;
99
+ files: string[];
100
+ }>;
101
+ /**
102
+ * Build default prompt for a task
103
+ */
104
+ export declare function buildDefaultPrompt(session: Session, context?: TerminalTaskContext): string;
105
+ //# sourceMappingURL=terminal-base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-base.d.ts","sourceRoot":"","sources":["../../src/executors/terminal-base.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAM3D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,MAAM,CAAA;KAClB,CAAC,CAAA;IACF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;AAMhE;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAElC;;;;;;;;OAQG;IACH,YAAY,CACV,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,wBAAwB,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAAA;IAEnC;;;;;;;;OAQG;IACH,aAAa,CACX,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,wBAAwB,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAAA;IAEnC;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAA;CAC1C;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAe/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG,MAAM,CASvF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuCvG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAyBR"}
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Terminal Executor Base Interface
4
+ *
5
+ * Common interface for all terminal executors (Claude, OpenAI, Gemini).
6
+ * Terminal mode executes tasks using local tools instead of remote APIs.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.extractPrUrl = extractPrUrl;
10
+ exports.formatCommentHistory = formatCommentHistory;
11
+ exports.captureGitChanges = captureGitChanges;
12
+ exports.buildDefaultPrompt = buildDefaultPrompt;
13
+ // ============================================================================
14
+ // HELPER FUNCTIONS
15
+ // ============================================================================
16
+ /**
17
+ * Extract PR URL from output text
18
+ */
19
+ function extractPrUrl(output) {
20
+ const prUrlPatterns = [
21
+ /https:\/\/github\.com\/[^\/]+\/[^\/]+\/pull\/\d+/g,
22
+ /PR URL:\s*(https:\/\/[^\s]+)/i,
23
+ /Pull Request:\s*(https:\/\/[^\s]+)/i
24
+ ];
25
+ for (const pattern of prUrlPatterns) {
26
+ const match = output.match(pattern);
27
+ if (match) {
28
+ return match[0].replace(/PR URL:\s*/i, '').replace(/Pull Request:\s*/i, '');
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ /**
34
+ * Format comment history for context
35
+ */
36
+ function formatCommentHistory(comments) {
37
+ if (!comments || comments.length === 0)
38
+ return '';
39
+ const formatted = comments
40
+ .slice(-10)
41
+ .map(c => `**${c.authorName}** (${new Date(c.createdAt).toLocaleString()}):\n${c.content}`)
42
+ .join('\n\n---\n\n');
43
+ return `\n\n## Previous Discussion\n\n${formatted}`;
44
+ }
45
+ /**
46
+ * Capture git changes in a repository
47
+ */
48
+ async function captureGitChanges(projectPath) {
49
+ const { execSync } = await import('child_process');
50
+ try {
51
+ // Get modified files (staged and unstaged)
52
+ const statusOutput = execSync('git status --porcelain', {
53
+ cwd: projectPath,
54
+ encoding: 'utf-8',
55
+ timeout: 10000
56
+ });
57
+ const files = statusOutput
58
+ .split('\n')
59
+ .filter(line => line.trim())
60
+ .map(line => line.slice(3).trim()); // Remove status prefix
61
+ // Get diff (staged and unstaged, limited to 5000 chars)
62
+ let diff = '';
63
+ try {
64
+ diff = execSync('git diff HEAD --no-color', {
65
+ cwd: projectPath,
66
+ encoding: 'utf-8',
67
+ timeout: 10000,
68
+ maxBuffer: 1024 * 1024 // 1MB max
69
+ });
70
+ // Truncate if too long
71
+ if (diff.length > 5000) {
72
+ diff = diff.slice(0, 5000) + '\n\n[... diff truncated ...]';
73
+ }
74
+ }
75
+ catch {
76
+ // No diff or not a git repo
77
+ }
78
+ console.log(`📊 Git changes: ${files.length} files modified`);
79
+ return { diff, files };
80
+ }
81
+ catch {
82
+ return { diff: '', files: [] };
83
+ }
84
+ }
85
+ /**
86
+ * Build default prompt for a task
87
+ */
88
+ function buildDefaultPrompt(session, context) {
89
+ const commentHistory = formatCommentHistory(context?.comments);
90
+ return `# Task: ${session.title}
91
+
92
+ ${session.description || ''}
93
+ ${commentHistory}
94
+
95
+ ## Workflow Requirements
96
+
97
+ 1. Understand the task and verify the platform (iOS = ios-app/, Web = components/, app/)
98
+ 2. Locate relevant code and make ONLY the requested changes
99
+ 3. Run predeploy tests: \`npm run predeploy\`
100
+ 4. Create PR: \`gh pr create\` with a clear title
101
+
102
+ ## Output Requirements
103
+
104
+ Your response MUST include:
105
+ 1. Task understanding: What was requested
106
+ 2. Actual changes made: What you changed and WHY
107
+ 3. Files modified: List each file path
108
+ 4. Test results: Output from \`npm run predeploy\`
109
+ 5. PR URL: The pull request URL (REQUIRED)
110
+
111
+ Begin by analyzing the task.`;
112
+ }
113
+ //# sourceMappingURL=terminal-base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-base.js","sourceRoot":"","sources":["../../src/executors/terminal-base.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAmHH,oCAeC;AAKD,oDASC;AAKD,8CAuCC;AAKD,gDA4BC;AAjHD,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;GAEG;AACH,SAAgB,YAAY,CAAC,MAAc;IACzC,MAAM,aAAa,GAAG;QACpB,mDAAmD;QACnD,+BAA+B;QAC/B,qCAAqC;KACtC,CAAA;IAED,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACnC,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;GAEG;AACH,SAAgB,oBAAoB,CAAC,QAA0C;IAC7E,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAEjD,MAAM,SAAS,GAAG,QAAQ;SACvB,KAAK,CAAC,CAAC,EAAE,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,UAAU,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;SAC1F,IAAI,CAAC,aAAa,CAAC,CAAA;IAEtB,OAAO,iCAAiC,SAAS,EAAE,CAAA;AACrD,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IACzD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;IAElD,IAAI,CAAC;QACH,2CAA2C;QAC3C,MAAM,YAAY,GAAG,QAAQ,CAAC,wBAAwB,EAAE;YACtD,GAAG,EAAE,WAAW;YAChB,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;QAEF,MAAM,KAAK,GAAG,YAAY;aACvB,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA,CAAC,uBAAuB;QAE5D,wDAAwD;QACxD,IAAI,IAAI,GAAG,EAAE,CAAA;QACb,IAAI,CAAC;YACH,IAAI,GAAG,QAAQ,CAAC,0BAA0B,EAAE;gBAC1C,GAAG,EAAE,WAAW;gBAChB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC,UAAU;aAClC,CAAC,CAAA;YAEF,uBAAuB;YACvB,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;gBACvB,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,8BAA8B,CAAA;YAC7D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;QAC9B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,CAAC,MAAM,iBAAiB,CAAC,CAAA;QAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAChC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAChC,OAAgB,EAChB,OAA6B;IAE7B,MAAM,cAAc,GAAG,oBAAoB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAE9D,OAAO,WAAW,OAAO,CAAC,KAAK;;EAE/B,OAAO,CAAC,WAAW,IAAI,EAAE;EACzB,cAAc;;;;;;;;;;;;;;;;;;6BAkBa,CAAA;AAC7B,CAAC"}
@@ -11,29 +11,13 @@
11
11
  * - Extracts PR URLs and git changes from output
12
12
  */
13
13
  import type { Session } from '../server/session-manager.js';
14
- export interface TerminalExecutionResult {
15
- exitCode: number | null;
16
- stdout: string;
17
- stderr: string;
18
- sessionId?: string;
19
- gitDiff?: string;
20
- modifiedFiles?: string[];
21
- prUrl?: string;
22
- }
14
+ import type { TerminalExecutor, TerminalTaskContext, ParsedOutput, TerminalExecutionResult } from './terminal-base.js';
15
+ export type { TerminalExecutionResult, TerminalTaskContext } from './terminal-base.js';
23
16
  export interface TerminalClaudeOptions {
24
17
  model?: string;
25
18
  maxTurns?: number;
26
19
  timeout?: number;
27
20
  }
28
- export interface TerminalTaskContext {
29
- comments?: Array<{
30
- authorName: string;
31
- content: string;
32
- createdAt: string;
33
- }>;
34
- prUrl?: string;
35
- repository?: string;
36
- }
37
21
  /**
38
22
  * Simple file-based session store for terminal mode.
39
23
  * Stores Claude session IDs for resumption support.
@@ -50,7 +34,7 @@ declare class TerminalSessionStore {
50
34
  deleteSession(taskId: string): Promise<void>;
51
35
  }
52
36
  export declare const terminalSessionStore: TerminalSessionStore;
53
- export declare class TerminalClaudeExecutor {
37
+ export declare class TerminalClaudeExecutor implements TerminalExecutor {
54
38
  private model;
55
39
  private maxTurns;
56
40
  private timeout;
@@ -76,34 +60,38 @@ export declare class TerminalClaudeExecutor {
76
60
  formatCommentHistory(comments?: TerminalTaskContext['comments']): string;
77
61
  /**
78
62
  * Build prompt from task details
63
+ *
64
+ * IMPORTANT: Keep prompts SIMPLE! Claude Code CLI has a bug where complex
65
+ * markdown prompts with multiple sections cause it to hang indefinitely.
66
+ * Simple one-line prompts work reliably.
67
+ *
68
+ * Claude Code automatically reads CLAUDE.md from the working directory,
69
+ * so we don't need to include workflow instructions here.
79
70
  */
80
71
  buildPrompt(session: Session, userMessage?: string, context?: TerminalTaskContext): Promise<string>;
81
72
  /**
82
- * Start a new Claude Code session
73
+ * Start a new Claude Code session with retry logic
83
74
  */
84
75
  startSession(session: Session, prompt?: string, context?: TerminalTaskContext, onProgress?: (message: string) => void): Promise<TerminalExecutionResult>;
85
76
  /**
86
- * Resume an existing Claude Code session
77
+ * Resume an existing Claude Code session with retry logic
87
78
  */
88
79
  resumeSession(session: Session, input: string, context?: TerminalTaskContext, onProgress?: (message: string) => void): Promise<TerminalExecutionResult>;
89
80
  /**
90
81
  * Execute Claude Code CLI
82
+ *
83
+ * Uses stdin for prompt input instead of command-line args to avoid
84
+ * issues with long prompts, special characters, and newlines.
91
85
  */
92
86
  private runClaude;
93
87
  /**
94
88
  * Parse Claude Code output to extract key information
95
89
  */
96
- parseOutput(output: string): {
97
- summary?: string;
98
- files?: string[];
99
- prUrl?: string;
100
- error?: string;
101
- };
90
+ parseOutput(output: string): ParsedOutput;
102
91
  /**
103
92
  * Check if Claude Code CLI is available
104
93
  */
105
94
  checkAvailable(): Promise<boolean>;
106
95
  }
107
96
  export declare const terminalClaudeExecutor: TerminalClaudeExecutor;
108
- export {};
109
97
  //# sourceMappingURL=terminal-claude.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-claude.d.ts","sourceRoot":"","sources":["../../src/executors/terminal-claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terminal-claude.d.ts","sourceRoot":"","sources":["../../src/executors/terminal-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EAEnB,YAAY,EACZ,uBAAuB,EACxB,MAAM,oBAAoB,CAAA;AAG3B,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAMtF,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAMD;;;GAGG;AACH,cAAM,oBAAoB;IACxB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,MAAM,CAAQ;;IAShB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAWrB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK/D,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1E,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOnD;AAGD,eAAO,MAAM,oBAAoB,sBAA6B,CAAA;AAM9D,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAQ;gBAEX,OAAO,GAAE,qBAA0B;IAO/C;;OAEG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAyCxF;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAkBhD;;OAEG;IACG,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB9D;;OAEG;IACH,oBAAoB,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG,MAAM;IAWxE;;;;;;;;;OASG;IACG,WAAW,CACf,OAAO,EAAE,OAAO,EAChB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAclB;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACrC,OAAO,CAAC,uBAAuB,CAAC;IAqEnC;;OAEG;IACG,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACrC,OAAO,CAAC,uBAAuB,CAAC;IAsEnC;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAmKjB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IAsCzC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAezC;AAGD,eAAO,MAAM,sBAAsB,wBAA+B,CAAA"}
@@ -200,80 +200,86 @@ class TerminalClaudeExecutor {
200
200
  }
201
201
  /**
202
202
  * Build prompt from task details
203
+ *
204
+ * IMPORTANT: Keep prompts SIMPLE! Claude Code CLI has a bug where complex
205
+ * markdown prompts with multiple sections cause it to hang indefinitely.
206
+ * Simple one-line prompts work reliably.
207
+ *
208
+ * Claude Code automatically reads CLAUDE.md from the working directory,
209
+ * so we don't need to include workflow instructions here.
203
210
  */
204
211
  async buildPrompt(session, userMessage, context) {
205
212
  if (userMessage) {
206
- const history = this.formatCommentHistory(context?.comments);
207
- return history ? `${history}\n\n---\n\n## Latest Message\n\n${userMessage}` : userMessage;
213
+ // For follow-up messages, just return the message directly
214
+ return userMessage;
208
215
  }
209
- // Read project context
210
- let projectContext = '';
211
- if (session.projectPath) {
212
- projectContext = await this.readProjectContext(session.projectPath);
213
- }
214
- const commentHistory = this.formatCommentHistory(context?.comments);
215
- return `# Task: ${session.title}
216
-
217
- ${session.description || ''}
218
- ${commentHistory}
219
- ${projectContext}
220
-
221
- ## Workflow Requirements
222
-
223
- 1. Understand the task and verify the platform (iOS = ios-app/, Web = components/, app/)
224
- 2. Locate relevant code and make ONLY the requested changes
225
- 3. Run predeploy tests: \`npm run predeploy\`
226
- 4. Create PR: \`gh pr create\` with a clear title
227
-
228
- ## Output Requirements
229
-
230
- Your response MUST include:
231
- 1. Task understanding: What was requested
232
- 2. Actual changes made: What you changed and WHY
233
- 3. Files modified: List each file path
234
- 4. Test results: Output from \`npm run predeploy\`
235
- 5. PR URL: The pull request URL (REQUIRED)`;
216
+ // Build a SIMPLE prompt - complex markdown causes Claude CLI to hang!
217
+ // Claude Code reads CLAUDE.md automatically for workflow instructions.
218
+ const description = session.description?.trim() || '';
219
+ const descPart = description ? `: ${description}` : '';
220
+ return `Complete this task: ${session.title}${descPart}`;
236
221
  }
237
222
  /**
238
- * Start a new Claude Code session
223
+ * Start a new Claude Code session with retry logic
239
224
  */
240
225
  async startSession(session, prompt, context, onProgress) {
241
226
  const taskPrompt = prompt || await this.buildPrompt(session, undefined, context);
242
- const args = [
243
- '--print',
244
- '--model', this.model,
245
- '--max-turns', String(this.maxTurns),
246
- '--output-format', 'text',
247
- '--dangerously-skip-permissions',
248
- ];
249
227
  // Truncate prompt if too long (avoid ARG_MAX issues)
250
228
  const MAX_PROMPT_LENGTH = 50000;
251
229
  let finalPrompt = taskPrompt;
252
230
  if (taskPrompt.length > MAX_PROMPT_LENGTH) {
253
231
  finalPrompt = taskPrompt.slice(0, MAX_PROMPT_LENGTH) + '\n\n[... prompt truncated ...]';
254
232
  }
255
- args.push('-p', finalPrompt);
256
233
  console.log(`🚀 Starting new Claude Code session for task: ${session.title}`);
257
234
  if (session.projectPath) {
258
235
  console.log(`📁 Working directory: ${session.projectPath}`);
259
236
  }
260
- const result = await this.runClaude(args, session, onProgress);
261
- // Store session ID for resumption
262
- if (result.sessionId) {
263
- await exports.terminalSessionStore.setClaudeSessionId(session.taskId, result.sessionId);
264
- }
265
- // Capture git changes
266
- if (session.projectPath) {
267
- const changes = await this.captureGitChanges(session.projectPath);
268
- result.gitDiff = changes.diff;
269
- result.modifiedFiles = changes.files;
237
+ // Retry logic for intermittent Claude Code hangs
238
+ const MAX_RETRIES = 3;
239
+ let lastError;
240
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
241
+ if (attempt > 1) {
242
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
243
+ console.log(`🔄 Retry attempt ${attempt}/${MAX_RETRIES} after ${delay}ms delay...`);
244
+ await new Promise(resolve => setTimeout(resolve, delay));
245
+ }
246
+ // Use stdin for prompt - more reliable than command-line args for long prompts
247
+ const args = [
248
+ '--print',
249
+ '--model', this.model,
250
+ '--output-format', 'text',
251
+ '--dangerously-skip-permissions',
252
+ ];
253
+ const result = await this.runClaude(args, session, onProgress, finalPrompt);
254
+ // Check if we got actual output (not a timeout/hang)
255
+ if (result.stdout.length > 0 || result.exitCode === 0) {
256
+ // Store session ID for resumption
257
+ if (result.sessionId) {
258
+ await exports.terminalSessionStore.setClaudeSessionId(session.taskId, result.sessionId);
259
+ }
260
+ // Capture git changes
261
+ if (session.projectPath) {
262
+ const changes = await this.captureGitChanges(session.projectPath);
263
+ result.gitDiff = changes.diff;
264
+ result.modifiedFiles = changes.files;
265
+ }
266
+ // Extract PR URL
267
+ result.prUrl = this.extractPrUrl(result.stdout);
268
+ return result;
269
+ }
270
+ console.log(`⚠️ Attempt ${attempt} failed (no output received)`);
271
+ lastError = new Error(`No output received from Claude Code (attempt ${attempt})`);
270
272
  }
271
- // Extract PR URL
272
- result.prUrl = this.extractPrUrl(result.stdout);
273
- return result;
273
+ // All retries failed, return empty result
274
+ console.error(`❌ All ${MAX_RETRIES} attempts failed`);
275
+ return {
276
+ exitCode: -1,
277
+ stdout: '',
278
+ stderr: lastError?.message || 'All retry attempts failed',
279
+ };
274
280
  }
275
281
  /**
276
- * Resume an existing Claude Code session
282
+ * Resume an existing Claude Code session with retry logic
277
283
  */
278
284
  async resumeSession(session, input, context, onProgress) {
279
285
  // Get stored Claude session ID
@@ -283,36 +289,61 @@ Your response MUST include:
283
289
  return this.startSession(session, input, context, onProgress);
284
290
  }
285
291
  const promptWithContext = await this.buildPrompt(session, input, context);
286
- const args = [
287
- '--print',
288
- '--resume', claudeSessionId,
289
- '--output-format', 'text',
290
- '--dangerously-skip-permissions',
291
- ];
292
- args.push('-p', promptWithContext);
293
292
  console.log(`🔄 Resuming Claude Code session ${claudeSessionId}`);
294
293
  if (session.projectPath) {
295
294
  console.log(`📁 Working directory: ${session.projectPath}`);
296
295
  }
297
- const result = await this.runClaude(args, session, onProgress);
298
- // Update session ID if we got a new one
299
- if (result.sessionId) {
300
- await exports.terminalSessionStore.setClaudeSessionId(session.taskId, result.sessionId);
301
- }
302
- // Capture git changes
303
- if (session.projectPath) {
304
- const changes = await this.captureGitChanges(session.projectPath);
305
- result.gitDiff = changes.diff;
306
- result.modifiedFiles = changes.files;
296
+ // Retry logic for intermittent Claude Code hangs
297
+ const MAX_RETRIES = 3;
298
+ let lastError;
299
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
300
+ if (attempt > 1) {
301
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
302
+ console.log(`🔄 Retry attempt ${attempt}/${MAX_RETRIES} after ${delay}ms delay...`);
303
+ await new Promise(resolve => setTimeout(resolve, delay));
304
+ }
305
+ // Use stdin for prompt - more reliable than command-line args
306
+ const args = [
307
+ '--print',
308
+ '--resume', claudeSessionId,
309
+ '--output-format', 'text',
310
+ '--dangerously-skip-permissions',
311
+ ];
312
+ const result = await this.runClaude(args, session, onProgress, promptWithContext);
313
+ // Check if we got actual output
314
+ if (result.stdout.length > 0 || result.exitCode === 0) {
315
+ // Update session ID if we got a new one
316
+ if (result.sessionId) {
317
+ await exports.terminalSessionStore.setClaudeSessionId(session.taskId, result.sessionId);
318
+ }
319
+ // Capture git changes
320
+ if (session.projectPath) {
321
+ const changes = await this.captureGitChanges(session.projectPath);
322
+ result.gitDiff = changes.diff;
323
+ result.modifiedFiles = changes.files;
324
+ }
325
+ // Extract PR URL
326
+ result.prUrl = this.extractPrUrl(result.stdout);
327
+ return result;
328
+ }
329
+ console.log(`⚠️ Attempt ${attempt} failed (no output received)`);
330
+ lastError = new Error(`No output received from Claude Code (attempt ${attempt})`);
307
331
  }
308
- // Extract PR URL
309
- result.prUrl = this.extractPrUrl(result.stdout);
310
- return result;
332
+ // All retries failed
333
+ console.error(`❌ All ${MAX_RETRIES} attempts failed`);
334
+ return {
335
+ exitCode: -1,
336
+ stdout: '',
337
+ stderr: lastError?.message || 'All retry attempts failed',
338
+ };
311
339
  }
312
340
  /**
313
341
  * Execute Claude Code CLI
342
+ *
343
+ * Uses stdin for prompt input instead of command-line args to avoid
344
+ * issues with long prompts, special characters, and newlines.
314
345
  */
315
- runClaude(args, session, onProgress) {
346
+ runClaude(args, session, onProgress, stdinInput) {
316
347
  return new Promise((resolve, reject) => {
317
348
  let stdout = '';
318
349
  let stderr = '';
@@ -323,7 +354,11 @@ Your response MUST include:
323
354
  let initialTimeoutHandle = null;
324
355
  const INITIAL_TIMEOUT = 120000; // 2 minutes for first output
325
356
  const STALL_TIMEOUT = 300000; // 5 minutes of no output = stalled
326
- console.log(`🤖 Running: claude ${args.map(a => a.includes(' ') ? `"${a.slice(0, 50)}..."` : a).join(' ')}`);
357
+ // Log command (without prompt for readability)
358
+ console.log(`🤖 Running: claude ${args.join(' ')}`);
359
+ if (stdinInput) {
360
+ console.log(`📝 Prompt via stdin: ${stdinInput.length} chars (first 100: ${stdinInput.slice(0, 100).replace(/\n/g, '\\n')}...)`);
361
+ }
327
362
  console.log(`⏱️ Timeouts: initial=${INITIAL_TIMEOUT / 1000}s, stall=${STALL_TIMEOUT / 1000}s, max=${this.timeout / 1000}s`);
328
363
  const proc = (0, child_process_1.spawn)('claude', args, {
329
364
  cwd: session.projectPath || process.cwd(),
@@ -331,13 +366,20 @@ Your response MUST include:
331
366
  ...process.env,
332
367
  CLAUDE_CODE_ENTRYPOINT: 'cli',
333
368
  },
334
- stdio: ['ignore', 'pipe', 'pipe']
369
+ stdio: [stdinInput ? 'pipe' : 'ignore', 'pipe', 'pipe']
335
370
  });
336
371
  console.log(`🚀 Claude process spawned with PID: ${proc.pid}`);
337
372
  if (!proc.pid) {
338
373
  reject(new Error('Failed to spawn Claude process'));
339
374
  return;
340
375
  }
376
+ // Write prompt to stdin if provided
377
+ if (stdinInput && proc.stdin) {
378
+ console.log(`📤 Writing prompt to stdin...`);
379
+ proc.stdin.write(stdinInput);
380
+ proc.stdin.end();
381
+ console.log(`✅ Stdin closed`);
382
+ }
341
383
  // Heartbeat: log status every 30 seconds
342
384
  heartbeatInterval = setInterval(() => {
343
385
  const elapsed = Math.round((Date.now() - lastOutputTime) / 1000);
@@ -362,6 +404,11 @@ Your response MUST include:
362
404
  if (initialTimeoutHandle)
363
405
  clearTimeout(initialTimeoutHandle);
364
406
  };
407
+ if (!proc.stdout || !proc.stderr) {
408
+ cleanup();
409
+ reject(new Error('Failed to get stdout/stderr pipes from Claude process'));
410
+ return;
411
+ }
365
412
  proc.stdout.on('data', (data) => {
366
413
  const chunk = data.toString();
367
414
  stdout += chunk;