agentic-loop 3.12.3 → 3.14.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.
Files changed (52) hide show
  1. package/.claude/skills/loopgram/SKILL.md +19 -0
  2. package/README.md +1 -0
  3. package/bin/ralph.sh +17 -11
  4. package/dist/loopgram/claude.d.ts +18 -0
  5. package/dist/loopgram/claude.d.ts.map +1 -0
  6. package/dist/loopgram/claude.js +89 -0
  7. package/dist/loopgram/claude.js.map +1 -0
  8. package/dist/loopgram/context-search.d.ts +26 -0
  9. package/dist/loopgram/context-search.d.ts.map +1 -0
  10. package/dist/loopgram/context-search.js +175 -0
  11. package/dist/loopgram/context-search.js.map +1 -0
  12. package/dist/loopgram/conversation.d.ts +39 -0
  13. package/dist/loopgram/conversation.d.ts.map +1 -0
  14. package/dist/loopgram/conversation.js +158 -0
  15. package/dist/loopgram/conversation.js.map +1 -0
  16. package/dist/loopgram/index.d.ts +3 -0
  17. package/dist/loopgram/index.d.ts.map +1 -0
  18. package/dist/loopgram/index.js +246 -0
  19. package/dist/loopgram/index.js.map +1 -0
  20. package/dist/loopgram/loop-monitor.d.ts +16 -0
  21. package/dist/loopgram/loop-monitor.d.ts.map +1 -0
  22. package/dist/loopgram/loop-monitor.js +149 -0
  23. package/dist/loopgram/loop-monitor.js.map +1 -0
  24. package/dist/loopgram/loop-runner.d.ts +28 -0
  25. package/dist/loopgram/loop-runner.d.ts.map +1 -0
  26. package/dist/loopgram/loop-runner.js +157 -0
  27. package/dist/loopgram/loop-runner.js.map +1 -0
  28. package/dist/loopgram/prd-generator.d.ts +37 -0
  29. package/dist/loopgram/prd-generator.d.ts.map +1 -0
  30. package/dist/loopgram/prd-generator.js +134 -0
  31. package/dist/loopgram/prd-generator.js.map +1 -0
  32. package/dist/loopgram/saver.d.ts +9 -0
  33. package/dist/loopgram/saver.d.ts.map +1 -0
  34. package/dist/loopgram/saver.js +35 -0
  35. package/dist/loopgram/saver.js.map +1 -0
  36. package/dist/loopgram/types.d.ts +37 -0
  37. package/dist/loopgram/types.d.ts.map +1 -0
  38. package/dist/loopgram/types.js +5 -0
  39. package/dist/loopgram/types.js.map +1 -0
  40. package/package.json +6 -2
  41. package/ralph/hooks/common.sh +89 -0
  42. package/ralph/hooks/warn-debug.sh +14 -32
  43. package/ralph/hooks/warn-empty-catch.sh +13 -29
  44. package/ralph/hooks/warn-secrets.sh +19 -37
  45. package/ralph/hooks/warn-urls.sh +17 -33
  46. package/ralph/init.sh +30 -0
  47. package/ralph/loop.sh +5 -2
  48. package/ralph/setup/ui.sh +0 -42
  49. package/ralph/setup.sh +69 -45
  50. package/ralph/utils.sh +167 -31
  51. package/templates/config/fastmcp.json +5 -5
  52. package/templates/hooks/warn-uv-python.sh +38 -0
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env tsx
2
+ import { Telegraf } from 'telegraf';
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { handleMessage, handleSaveCommand, handleClearCommand, handleProjectsCommand, handleStatusCommand, setConversationContext, getConversationContext, getHistory, clearConversation, } from './conversation.js';
6
+ import { parseProgressFile, formatLoopStatus } from './loop-monitor.js';
7
+ import { getTopicContext } from './context-search.js';
8
+ import { generateStories, appendToPRD } from './prd-generator.js';
9
+ import { startLoop, stopLoop } from './loop-runner.js';
10
+ // Load configuration
11
+ function loadConfig() {
12
+ const configPath = join(process.env.HOME || '', '.config/ralph/loopgram.json');
13
+ if (!existsSync(configPath)) {
14
+ console.error(`Config file not found: ${configPath}`);
15
+ console.error('Create it with your Telegram user ID and project mappings.');
16
+ console.error('See setup instructions in the README.');
17
+ process.exit(1);
18
+ }
19
+ try {
20
+ const content = readFileSync(configPath, 'utf-8');
21
+ return JSON.parse(content);
22
+ }
23
+ catch (error) {
24
+ console.error(`Error reading config: ${error}`);
25
+ process.exit(1);
26
+ }
27
+ }
28
+ // Get project config for a chat ID
29
+ function getProject(chatId, config) {
30
+ return config.projects[chatId] || null;
31
+ }
32
+ // Get project path for a chat ID
33
+ function getProjectPath(chatId, config) {
34
+ const project = config.projects[chatId];
35
+ return project?.path || null;
36
+ }
37
+ // Main entry point
38
+ async function main() {
39
+ // Check for bot token
40
+ const token = process.env.TELEGRAM_BOT_TOKEN;
41
+ if (!token) {
42
+ console.error('TELEGRAM_BOT_TOKEN environment variable is required');
43
+ console.error('Set it in ~/.config/ralph/secrets and source the file');
44
+ process.exit(1);
45
+ }
46
+ // Check for Anthropic API key
47
+ if (!process.env.ANTHROPIC_API_KEY) {
48
+ console.error('ANTHROPIC_API_KEY environment variable is required');
49
+ console.error('Set it in ~/.config/ralph/secrets and source the file');
50
+ process.exit(1);
51
+ }
52
+ const config = loadConfig();
53
+ const bot = new Telegraf(token);
54
+ // Security middleware: only respond to allowed users
55
+ bot.use((ctx, next) => {
56
+ const userId = ctx.from?.id.toString();
57
+ if (!userId || !config.telegram.allowedUserIds.includes(userId)) {
58
+ // Silently ignore messages from unauthorized users
59
+ return;
60
+ }
61
+ return next();
62
+ });
63
+ // Logging middleware: log unknown group IDs for easy config setup
64
+ bot.use((ctx, next) => {
65
+ const chatId = ctx.chat?.id.toString();
66
+ if (chatId && !config.projects[chatId]) {
67
+ console.log(`Unknown chat ID: ${chatId} - add to config to enable`);
68
+ }
69
+ return next();
70
+ });
71
+ // Command handlers
72
+ bot.command('save', (ctx) => {
73
+ const chatId = ctx.chat?.id.toString();
74
+ const projectPath = chatId ? getProjectPath(chatId, config) : null;
75
+ return handleSaveCommand(ctx, projectPath, config);
76
+ });
77
+ bot.command('clear', handleClearCommand);
78
+ bot.command('projects', (ctx) => handleProjectsCommand(ctx, config));
79
+ bot.command('status', (ctx) => {
80
+ const chatId = ctx.chat?.id.toString();
81
+ const projectPath = chatId ? getProjectPath(chatId, config) : null;
82
+ return handleStatusCommand(ctx, projectPath, config);
83
+ });
84
+ bot.command('help', (ctx) => {
85
+ ctx.reply(`📱 **Loopgram**\n\n` +
86
+ `Your mobile connection to agentic-loop.\n\n` +
87
+ `**Workflow:**\n` +
88
+ `/context <topic> - Load codebase context\n` +
89
+ `(chat about your idea)\n` +
90
+ `/prd - Generate stories from conversation\n` +
91
+ `/start-loop - Start Ralph to execute stories\n` +
92
+ `/loop - Check Ralph progress\n` +
93
+ `/stop-loop - Stop Ralph loop\n\n` +
94
+ `**Other:**\n` +
95
+ `/save - Save conversation as idea file\n` +
96
+ `/clear - Clear conversation history\n` +
97
+ `/status - Show current session info\n` +
98
+ `/projects - List configured projects\n` +
99
+ `/help - Show this message`);
100
+ });
101
+ // /loop command - check Ralph loop status
102
+ bot.command('loop', (ctx) => {
103
+ const chatId = ctx.chat?.id.toString();
104
+ const project = chatId ? getProject(chatId, config) : null;
105
+ if (!project) {
106
+ ctx.reply('This chat is not configured for a project.');
107
+ return;
108
+ }
109
+ const status = parseProgressFile(project.path);
110
+ if (!status) {
111
+ ctx.reply(`No Ralph loop running in ${project.name}. Start one with /start-loop`);
112
+ return;
113
+ }
114
+ ctx.reply(formatLoopStatus(status, project.name));
115
+ });
116
+ // /prd command - generate stories from conversation
117
+ bot.command('prd', async (ctx) => {
118
+ const chatId = ctx.chat?.id;
119
+ const chatIdStr = chatId?.toString();
120
+ const project = chatIdStr ? getProject(chatIdStr, config) : null;
121
+ if (!project || !chatId) {
122
+ ctx.reply('This chat is not configured for a project.');
123
+ return;
124
+ }
125
+ const history = getHistory(chatId);
126
+ if (history.length < 2) {
127
+ ctx.reply('Nothing to generate. Start a conversation first!');
128
+ return;
129
+ }
130
+ await ctx.reply('🔄 Generating stories from conversation...');
131
+ try {
132
+ const context = getConversationContext(chatId);
133
+ const { featureName, featureDescription, stories } = await generateStories(history, context, config.anthropic.model);
134
+ const { added, total } = appendToPRD(project.path, featureName, featureDescription, stories);
135
+ // Format story list
136
+ const storyList = stories.map((s) => `• ${s.title}`).join('\n');
137
+ await ctx.reply(`✅ Added ${added} stories to PRD (${total} total)\n\n` +
138
+ `**${featureName}**\n${storyList}\n\n` +
139
+ `Use /start-loop to execute.`);
140
+ // Clear conversation after generating PRD
141
+ clearConversation(chatId);
142
+ }
143
+ catch (error) {
144
+ console.error('PRD generation error:', error);
145
+ await ctx.reply('Error generating stories. Check the logs.');
146
+ }
147
+ });
148
+ // /start-loop command - start Ralph loop
149
+ bot.command('start_loop', (ctx) => {
150
+ const chatId = ctx.chat?.id.toString();
151
+ const project = chatId ? getProject(chatId, config) : null;
152
+ if (!project) {
153
+ ctx.reply('This chat is not configured for a project.');
154
+ return;
155
+ }
156
+ const result = startLoop(project.path);
157
+ if (result.success) {
158
+ ctx.reply(`🚀 ${result.message}`);
159
+ }
160
+ else {
161
+ ctx.reply(`❌ ${result.message}`);
162
+ }
163
+ });
164
+ // /stop-loop command - stop Ralph loop
165
+ bot.command('stop_loop', (ctx) => {
166
+ const chatId = ctx.chat?.id.toString();
167
+ const project = chatId ? getProject(chatId, config) : null;
168
+ if (!project) {
169
+ ctx.reply('This chat is not configured for a project.');
170
+ return;
171
+ }
172
+ const result = stopLoop(project.path);
173
+ if (result.success) {
174
+ ctx.reply(`✅ ${result.message}`);
175
+ }
176
+ else {
177
+ ctx.reply(`❌ ${result.message}`);
178
+ }
179
+ });
180
+ // /context command - search codebase for a topic
181
+ bot.command('context', async (ctx) => {
182
+ const chatId = ctx.chat?.id.toString();
183
+ const project = chatId ? getProject(chatId, config) : null;
184
+ if (!project) {
185
+ ctx.reply('This chat is not configured for a project.');
186
+ return;
187
+ }
188
+ // Extract topic from command
189
+ const text = ctx.message?.text || '';
190
+ const topic = text.replace(/^\/context\s*/i, '').trim();
191
+ if (!topic) {
192
+ ctx.reply('Usage: /context <topic>\nExample: /context authentication');
193
+ return;
194
+ }
195
+ await ctx.reply(`🔍 Searching ${project.name} for "${topic}"...`);
196
+ try {
197
+ const { summary, filesFound, searchTerms } = await getTopicContext(project.path, project.name, topic, config.anthropic.model);
198
+ // Store this context for the conversation
199
+ if (chatId) {
200
+ setConversationContext(parseInt(chatId), summary);
201
+ }
202
+ let response = `🔍 Searched for: ${searchTerms.join(', ')}\n\n`;
203
+ response += `📁 Found ${filesFound.length} files:\n`;
204
+ response += filesFound.slice(0, 5).map(f => `• ${f}`).join('\n');
205
+ if (filesFound.length > 5) {
206
+ response += `\n...and ${filesFound.length - 5} more`;
207
+ }
208
+ response += `\n\n${summary}`;
209
+ response += `\n\n💡 Context loaded! Now brainstorm away.`;
210
+ await ctx.reply(response);
211
+ }
212
+ catch (error) {
213
+ console.error('Context search error:', error);
214
+ await ctx.reply('Error searching codebase. Check the logs.');
215
+ }
216
+ });
217
+ // Handle regular text messages
218
+ bot.on('text', (ctx) => {
219
+ const chatId = ctx.chat?.id.toString();
220
+ const project = chatId ? getProject(chatId, config) : null;
221
+ return handleMessage(ctx, config, project);
222
+ });
223
+ // Start the bot
224
+ console.log('📱 Loopgram starting...');
225
+ console.log(`Model: ${config.anthropic.model}`);
226
+ console.log(`Configured projects: ${Object.values(config.projects)
227
+ .map((p) => p.name)
228
+ .join(', ') || 'none'}`);
229
+ console.log(`Allowed users: ${config.telegram.allowedUserIds.length} configured`);
230
+ await bot.launch();
231
+ console.log('✅ Loopgram running! Send messages in Telegram.');
232
+ // Graceful shutdown
233
+ process.once('SIGINT', () => {
234
+ console.log('\nShutting down...');
235
+ bot.stop('SIGINT');
236
+ });
237
+ process.once('SIGTERM', () => {
238
+ console.log('\nShutting down...');
239
+ bot.stop('SIGTERM');
240
+ });
241
+ }
242
+ main().catch((error) => {
243
+ console.error('Fatal error:', error);
244
+ process.exit(1);
245
+ });
246
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/loopgram/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { LoopStatus } from './types.js';
2
+ /**
3
+ * Parse the Ralph progress file to get loop status
4
+ * Format: [ISO-timestamp] STATUS story_id message
5
+ * Statuses: COMPLETED, FAILED, CLI_CRASH, TIMEOUT, BLOCKED
6
+ */
7
+ export declare function parseProgressFile(projectPath: string): LoopStatus | null;
8
+ /**
9
+ * Format loop status for Telegram message
10
+ */
11
+ export declare function formatLoopStatus(status: LoopStatus, projectName: string): string;
12
+ /**
13
+ * Watch a project's progress file for changes
14
+ */
15
+ export declare function watchProgress(projectPath: string, _projectName: string, onUpdate: (status: LoopStatus, changeType: 'started' | 'completed' | 'error' | 'update') => void): () => void;
16
+ //# sourceMappingURL=loop-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-monitor.d.ts","sourceRoot":"","sources":["../../src/loopgram/loop-monitor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK7C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA6ExE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,KAAK,IAAI,GAC/F,MAAM,IAAI,CAiDZ"}
@@ -0,0 +1,149 @@
1
+ import { readFileSync, existsSync, watchFile, unwatchFile } from 'fs';
2
+ import { join } from 'path';
3
+ // Track last known state per project to detect changes
4
+ const lastState = new Map();
5
+ /**
6
+ * Parse the Ralph progress file to get loop status
7
+ * Format: [ISO-timestamp] STATUS story_id message
8
+ * Statuses: COMPLETED, FAILED, CLI_CRASH, TIMEOUT, BLOCKED
9
+ */
10
+ export function parseProgressFile(projectPath) {
11
+ const progressPath = join(projectPath, '.ralph/progress.txt');
12
+ if (!existsSync(progressPath)) {
13
+ return null;
14
+ }
15
+ try {
16
+ const content = readFileSync(progressPath, 'utf-8');
17
+ const lines = content.split('\n').filter(Boolean);
18
+ // Only look at recent lines (last 50)
19
+ const recentLines = lines.slice(-50);
20
+ const errors = [];
21
+ const completedStoryIds = new Set();
22
+ let currentStory = null;
23
+ let lastUpdate = '';
24
+ let isRunning = false;
25
+ let hasRalphActivity = false;
26
+ // Ralph log format: [timestamp] STATUS story_id message
27
+ const logPattern = /^\[([^\]]+)\]\s+(COMPLETED|FAILED|CLI_CRASH|TIMEOUT|BLOCKED)\s+(\S+)(.*)$/;
28
+ for (const line of recentLines) {
29
+ const match = line.match(logPattern);
30
+ if (match) {
31
+ hasRalphActivity = true;
32
+ const [, timestamp, status, storyId, message] = match;
33
+ lastUpdate = timestamp;
34
+ if (status === 'COMPLETED') {
35
+ completedStoryIds.add(storyId);
36
+ if (currentStory === storyId) {
37
+ currentStory = null;
38
+ }
39
+ }
40
+ else if (status === 'FAILED' || status === 'CLI_CRASH' || status === 'TIMEOUT') {
41
+ errors.push(`${status} ${storyId}${message}`.trim());
42
+ currentStory = storyId; // Still working on this story
43
+ isRunning = true;
44
+ }
45
+ }
46
+ // Check for loop start/end markers
47
+ if (line.includes('Loop starting') || line.includes('Starting loop')) {
48
+ isRunning = true;
49
+ hasRalphActivity = true;
50
+ }
51
+ if (line.includes('Loop complete') || line.includes('All stories done')) {
52
+ isRunning = false;
53
+ }
54
+ // Try to get total stories
55
+ const totalMatch = line.match(/(\d+)\s*(?:stories|story)/i);
56
+ if (totalMatch) {
57
+ // Don't overwrite with smaller numbers
58
+ }
59
+ }
60
+ // If no Ralph activity found, return null
61
+ if (!hasRalphActivity) {
62
+ return null;
63
+ }
64
+ return {
65
+ isRunning,
66
+ currentStory,
67
+ completedStories: completedStoryIds.size,
68
+ totalStories: 0, // Would need to read PRD to know this
69
+ lastUpdate,
70
+ errors: errors.slice(-3),
71
+ };
72
+ }
73
+ catch (error) {
74
+ console.error('Error parsing progress file:', error);
75
+ return null;
76
+ }
77
+ }
78
+ /**
79
+ * Format loop status for Telegram message
80
+ */
81
+ export function formatLoopStatus(status, projectName) {
82
+ const statusEmoji = status.isRunning ? '🔄' : '✅';
83
+ const progress = status.totalStories > 0
84
+ ? `${status.completedStories}/${status.totalStories}`
85
+ : `${status.completedStories} done`;
86
+ let message = `${statusEmoji} **${projectName}**\n`;
87
+ message += `Progress: ${progress}\n`;
88
+ if (status.currentStory) {
89
+ message += `Current: ${status.currentStory}\n`;
90
+ }
91
+ if (status.lastUpdate) {
92
+ message += `Updated: ${status.lastUpdate}\n`;
93
+ }
94
+ if (status.errors.length > 0) {
95
+ message += `\n⚠️ Recent errors:\n`;
96
+ for (const err of status.errors) {
97
+ message += `• ${err.substring(0, 100)}...\n`;
98
+ }
99
+ }
100
+ return message;
101
+ }
102
+ /**
103
+ * Watch a project's progress file for changes
104
+ */
105
+ export function watchProgress(projectPath, _projectName, onUpdate) {
106
+ const progressPath = join(projectPath, '.ralph/progress.txt');
107
+ if (!existsSync(progressPath)) {
108
+ return () => { }; // No-op cleanup
109
+ }
110
+ // Store initial state
111
+ const initialContent = readFileSync(progressPath, 'utf-8');
112
+ lastState.set(projectPath, initialContent);
113
+ const checkForChanges = () => {
114
+ try {
115
+ const currentContent = readFileSync(progressPath, 'utf-8');
116
+ const previousContent = lastState.get(projectPath) || '';
117
+ if (currentContent !== previousContent) {
118
+ lastState.set(projectPath, currentContent);
119
+ const status = parseProgressFile(projectPath);
120
+ if (!status)
121
+ return;
122
+ // Determine change type
123
+ let changeType = 'update';
124
+ const newLines = currentContent.substring(previousContent.length);
125
+ if (newLines.includes('✓') || newLines.includes('completed')) {
126
+ changeType = 'completed';
127
+ }
128
+ else if (newLines.includes('✗') || newLines.includes('ERROR')) {
129
+ changeType = 'error';
130
+ }
131
+ else if (newLines.includes('Starting') || newLines.includes('▶')) {
132
+ changeType = 'started';
133
+ }
134
+ onUpdate(status, changeType);
135
+ }
136
+ }
137
+ catch (error) {
138
+ // File might be temporarily unavailable during write
139
+ }
140
+ };
141
+ // Watch for changes
142
+ watchFile(progressPath, { interval: 5000 }, checkForChanges);
143
+ // Return cleanup function
144
+ return () => {
145
+ unwatchFile(progressPath);
146
+ lastState.delete(projectPath);
147
+ };
148
+ }
149
+ //# sourceMappingURL=loop-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-monitor.js","sourceRoot":"","sources":["../../src/loopgram/loop-monitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Check if a Ralph loop is currently running for a project
3
+ */
4
+ export declare function isLoopRunning(projectPath: string): boolean;
5
+ /**
6
+ * Start a Ralph loop for a project
7
+ */
8
+ export declare function startLoop(projectPath: string): {
9
+ success: boolean;
10
+ message: string;
11
+ pid?: number;
12
+ };
13
+ /**
14
+ * Stop a running Ralph loop
15
+ */
16
+ export declare function stopLoop(projectPath: string): {
17
+ success: boolean;
18
+ message: string;
19
+ };
20
+ /**
21
+ * Get loop process info
22
+ */
23
+ export declare function getLoopInfo(projectPath: string): {
24
+ running: boolean;
25
+ pid?: number;
26
+ logTail?: string;
27
+ };
28
+ //# sourceMappingURL=loop-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-runner.d.ts","sourceRoot":"","sources":["../../src/loopgram/loop-runner.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAsB1D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAyDA;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CA0CA;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CA2BA"}
@@ -0,0 +1,157 @@
1
+ import { spawn } from 'child_process';
2
+ import { existsSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
3
+ import { join } from 'path';
4
+ const PID_FILE = '.ralph/loop.pid';
5
+ /**
6
+ * Check if a Ralph loop is currently running for a project
7
+ */
8
+ export function isLoopRunning(projectPath) {
9
+ const pidFile = join(projectPath, PID_FILE);
10
+ if (!existsSync(pidFile)) {
11
+ return false;
12
+ }
13
+ try {
14
+ const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
15
+ // Check if process is still running
16
+ process.kill(pid, 0);
17
+ return true;
18
+ }
19
+ catch {
20
+ // Process not running, clean up stale PID file
21
+ try {
22
+ unlinkSync(pidFile);
23
+ }
24
+ catch {
25
+ // Ignore cleanup errors
26
+ }
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * Start a Ralph loop for a project
32
+ */
33
+ export function startLoop(projectPath) {
34
+ // Check if PRD exists
35
+ const prdPath = join(projectPath, '.ralph/prd.json');
36
+ if (!existsSync(prdPath)) {
37
+ return {
38
+ success: false,
39
+ message: 'No PRD found. Use /prd to create stories first.',
40
+ };
41
+ }
42
+ // Check if already running
43
+ if (isLoopRunning(projectPath)) {
44
+ return {
45
+ success: false,
46
+ message: 'Loop already running. Use /loop to check status.',
47
+ };
48
+ }
49
+ try {
50
+ // Start Ralph loop in background
51
+ const logFile = join(projectPath, '.ralph/loop.log');
52
+ // Use npx agentic-loop run
53
+ const child = spawn('npx', ['agentic-loop', 'run'], {
54
+ cwd: projectPath,
55
+ detached: true,
56
+ stdio: ['ignore', 'pipe', 'pipe'],
57
+ env: {
58
+ ...process.env,
59
+ // Ensure Claude runs non-interactively
60
+ CLAUDE_CODE_ENTRYPOINT: 'cli',
61
+ },
62
+ });
63
+ // Write output to log file
64
+ const logStream = require('fs').createWriteStream(logFile, { flags: 'a' });
65
+ child.stdout?.pipe(logStream);
66
+ child.stderr?.pipe(logStream);
67
+ // Save PID
68
+ const pidFile = join(projectPath, PID_FILE);
69
+ writeFileSync(pidFile, child.pid.toString());
70
+ // Detach child process
71
+ child.unref();
72
+ return {
73
+ success: true,
74
+ message: `Loop started (PID: ${child.pid}). Use /loop to check progress.`,
75
+ pid: child.pid,
76
+ };
77
+ }
78
+ catch (error) {
79
+ return {
80
+ success: false,
81
+ message: `Failed to start loop: ${error}`,
82
+ };
83
+ }
84
+ }
85
+ /**
86
+ * Stop a running Ralph loop
87
+ */
88
+ export function stopLoop(projectPath) {
89
+ const pidFile = join(projectPath, PID_FILE);
90
+ if (!existsSync(pidFile)) {
91
+ return {
92
+ success: false,
93
+ message: 'No loop running.',
94
+ };
95
+ }
96
+ try {
97
+ const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
98
+ // Kill the process and its children
99
+ try {
100
+ // Kill process group (negative PID)
101
+ process.kill(-pid, 'SIGTERM');
102
+ }
103
+ catch {
104
+ // Try killing just the process
105
+ process.kill(pid, 'SIGTERM');
106
+ }
107
+ // Clean up PID file
108
+ unlinkSync(pidFile);
109
+ return {
110
+ success: true,
111
+ message: 'Loop stopped.',
112
+ };
113
+ }
114
+ catch (error) {
115
+ // Clean up PID file even if kill failed
116
+ try {
117
+ unlinkSync(pidFile);
118
+ }
119
+ catch {
120
+ // Ignore
121
+ }
122
+ return {
123
+ success: false,
124
+ message: `Error stopping loop: ${error}`,
125
+ };
126
+ }
127
+ }
128
+ /**
129
+ * Get loop process info
130
+ */
131
+ export function getLoopInfo(projectPath) {
132
+ const pidFile = join(projectPath, PID_FILE);
133
+ const logFile = join(projectPath, '.ralph/loop.log');
134
+ const running = isLoopRunning(projectPath);
135
+ let pid;
136
+ let logTail;
137
+ if (existsSync(pidFile)) {
138
+ try {
139
+ pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
140
+ }
141
+ catch {
142
+ // Ignore
143
+ }
144
+ }
145
+ if (existsSync(logFile)) {
146
+ try {
147
+ // Get last 500 chars of log
148
+ const content = readFileSync(logFile, 'utf-8');
149
+ logTail = content.slice(-500);
150
+ }
151
+ catch {
152
+ // Ignore
153
+ }
154
+ }
155
+ return { running, pid, logTail };
156
+ }
157
+ //# sourceMappingURL=loop-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-runner.js","sourceRoot":"","sources":["../../src/loopgram/loop-runner.ts"],"names":[],"mappings":"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"}