ralphblaster-agent 1.2.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/LICENSE +21 -0
- package/README.md +253 -0
- package/bin/ralph-agent.js +87 -0
- package/package.json +59 -0
- package/src/api-client.js +344 -0
- package/src/config.js +21 -0
- package/src/executor.js +1014 -0
- package/src/index.js +243 -0
- package/src/logger.js +96 -0
- package/src/ralph/prompt.md +165 -0
- package/src/ralph/ralph.sh +239 -0
- package/src/ralph-instance-manager.js +171 -0
- package/src/worktree-manager.js +170 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
const ApiClient = require('./api-client');
|
|
2
|
+
const Executor = require('./executor');
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
const logger = require('./logger');
|
|
5
|
+
|
|
6
|
+
// Timing constants
|
|
7
|
+
const SHUTDOWN_DELAY_MS = 500;
|
|
8
|
+
const ERROR_RETRY_DELAY_MS = 5000;
|
|
9
|
+
const HEARTBEAT_INTERVAL_MS = 60000;
|
|
10
|
+
|
|
11
|
+
class RalphAgent {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.apiClient = new ApiClient();
|
|
14
|
+
this.executor = new Executor(this.apiClient);
|
|
15
|
+
this.isRunning = false;
|
|
16
|
+
this.currentJob = null;
|
|
17
|
+
this.heartbeatInterval = null;
|
|
18
|
+
this.jobCompleting = false; // Flag to prevent heartbeat race conditions
|
|
19
|
+
|
|
20
|
+
// Rate limiting state
|
|
21
|
+
this.consecutiveErrors = 0;
|
|
22
|
+
this.lastRequestTime = 0;
|
|
23
|
+
this.minRequestInterval = 1000; // Minimum 1s between requests
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Start the agent
|
|
28
|
+
*/
|
|
29
|
+
async start() {
|
|
30
|
+
this.isRunning = true;
|
|
31
|
+
|
|
32
|
+
logger.info('Ralph Agent starting...');
|
|
33
|
+
logger.info(`API URL: ${config.apiUrl}`);
|
|
34
|
+
|
|
35
|
+
// Setup graceful shutdown
|
|
36
|
+
this.setupShutdownHandlers();
|
|
37
|
+
|
|
38
|
+
// Start polling loop
|
|
39
|
+
await this.pollLoop();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Stop the agent
|
|
44
|
+
*/
|
|
45
|
+
async stop() {
|
|
46
|
+
logger.info('Ralph Agent stopping...');
|
|
47
|
+
this.isRunning = false;
|
|
48
|
+
|
|
49
|
+
// Stop heartbeat first to prevent updates during shutdown
|
|
50
|
+
this.stopHeartbeat();
|
|
51
|
+
|
|
52
|
+
// Kill any running Claude process
|
|
53
|
+
if (this.executor.currentProcess) {
|
|
54
|
+
logger.warn('Terminating running Claude process');
|
|
55
|
+
await this.executor.killCurrentProcess();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If currently executing a job, mark it as failed
|
|
59
|
+
if (this.currentJob) {
|
|
60
|
+
logger.warn(`Marking job #${this.currentJob.id} as failed due to shutdown`);
|
|
61
|
+
try {
|
|
62
|
+
await this.apiClient.markJobFailed(
|
|
63
|
+
this.currentJob.id,
|
|
64
|
+
'Agent shutdown during execution'
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error('Failed to mark job as failed during shutdown', error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.info('Ralph Agent stopped');
|
|
72
|
+
// Give async operations time to complete before exiting
|
|
73
|
+
setTimeout(() => process.exit(0), SHUTDOWN_DELAY_MS);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Main polling loop with rate limiting and exponential backoff
|
|
78
|
+
*/
|
|
79
|
+
async pollLoop() {
|
|
80
|
+
while (this.isRunning) {
|
|
81
|
+
try {
|
|
82
|
+
// Enforce minimum interval between requests
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const timeSinceLastRequest = now - this.lastRequestTime;
|
|
85
|
+
if (timeSinceLastRequest < this.minRequestInterval) {
|
|
86
|
+
await this.sleep(this.minRequestInterval - timeSinceLastRequest);
|
|
87
|
+
}
|
|
88
|
+
this.lastRequestTime = Date.now();
|
|
89
|
+
|
|
90
|
+
// Check for next job (long polling - server waits up to 30s)
|
|
91
|
+
const job = await this.apiClient.getNextJob();
|
|
92
|
+
|
|
93
|
+
// Reset error counter on successful API call
|
|
94
|
+
this.consecutiveErrors = 0;
|
|
95
|
+
|
|
96
|
+
if (job) {
|
|
97
|
+
await this.processJob(job);
|
|
98
|
+
// After processing, immediately poll for next job
|
|
99
|
+
} else {
|
|
100
|
+
// No jobs available after long poll timeout
|
|
101
|
+
// Small delay before reconnecting to prevent hammering
|
|
102
|
+
await this.sleep(1000); // 1s minimum between polls
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.consecutiveErrors++;
|
|
106
|
+
logger.error(`Error in poll loop (consecutive: ${this.consecutiveErrors})`, error.message);
|
|
107
|
+
|
|
108
|
+
// Exponential backoff: 5s, 10s, 20s, 40s, max 60s
|
|
109
|
+
const backoffTime = Math.min(
|
|
110
|
+
ERROR_RETRY_DELAY_MS * Math.pow(2, this.consecutiveErrors - 1),
|
|
111
|
+
60000
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
logger.info(`Backing off for ${backoffTime}ms before retry`);
|
|
115
|
+
await this.sleep(backoffTime);
|
|
116
|
+
|
|
117
|
+
// Circuit breaker: Stop if too many consecutive errors
|
|
118
|
+
if (this.consecutiveErrors >= 10) {
|
|
119
|
+
logger.error('Too many consecutive errors (10+), shutting down');
|
|
120
|
+
await this.stop();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Process a job
|
|
128
|
+
* @param {Object} job - Job object from API
|
|
129
|
+
*/
|
|
130
|
+
async processJob(job) {
|
|
131
|
+
this.currentJob = job;
|
|
132
|
+
this.jobCompleting = false;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// Mark job as running
|
|
136
|
+
await this.apiClient.markJobRunning(job.id);
|
|
137
|
+
|
|
138
|
+
// Start heartbeat to keep job alive
|
|
139
|
+
this.startHeartbeat(job.id);
|
|
140
|
+
|
|
141
|
+
// Execute the job with progress callback
|
|
142
|
+
const result = await this.executor.execute(job, async (chunk) => {
|
|
143
|
+
// Send progress update to server
|
|
144
|
+
try {
|
|
145
|
+
await this.apiClient.sendProgress(job.id, chunk);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.warn(`Failed to send progress update for job #${job.id}`, error.message);
|
|
148
|
+
// Don't fail the job if progress update fails
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Set flag to prevent heartbeat race conditions, then stop heartbeat
|
|
153
|
+
this.jobCompleting = true;
|
|
154
|
+
this.stopHeartbeat();
|
|
155
|
+
|
|
156
|
+
// Mark job as completed
|
|
157
|
+
await this.apiClient.markJobCompleted(job.id, result);
|
|
158
|
+
|
|
159
|
+
logger.info(`Job #${job.id} completed successfully`);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// Set flag to prevent heartbeat race conditions, then stop heartbeat
|
|
162
|
+
this.jobCompleting = true;
|
|
163
|
+
this.stopHeartbeat();
|
|
164
|
+
|
|
165
|
+
// Mark job as failed (pass full error object to include categorization)
|
|
166
|
+
await this.apiClient.markJobFailed(
|
|
167
|
+
job.id,
|
|
168
|
+
error, // Pass full error object instead of just message
|
|
169
|
+
error.partialOutput || null
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
logger.error(`Job #${job.id} failed`, error.message);
|
|
173
|
+
} finally {
|
|
174
|
+
// Clear current job reference and reset completion flag
|
|
175
|
+
this.currentJob = null;
|
|
176
|
+
this.jobCompleting = false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Start heartbeat for job
|
|
182
|
+
* @param {number} jobId - Job ID
|
|
183
|
+
*/
|
|
184
|
+
startHeartbeat(jobId) {
|
|
185
|
+
// Send heartbeat every 60 seconds to prevent timeout
|
|
186
|
+
this.heartbeatInterval = setInterval(() => {
|
|
187
|
+
// Check if job is completing to prevent race conditions
|
|
188
|
+
if (this.jobCompleting) {
|
|
189
|
+
logger.debug('Skipping heartbeat - job is completing');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.apiClient.sendHeartbeat(jobId).catch(err => {
|
|
194
|
+
logger.warn('Heartbeat failed', err.message);
|
|
195
|
+
});
|
|
196
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Stop heartbeat
|
|
201
|
+
*/
|
|
202
|
+
stopHeartbeat() {
|
|
203
|
+
if (this.heartbeatInterval) {
|
|
204
|
+
clearInterval(this.heartbeatInterval);
|
|
205
|
+
this.heartbeatInterval = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Setup graceful shutdown handlers
|
|
211
|
+
*/
|
|
212
|
+
setupShutdownHandlers() {
|
|
213
|
+
process.on('SIGINT', () => {
|
|
214
|
+
logger.info('Received SIGINT signal');
|
|
215
|
+
this.stop();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
process.on('SIGTERM', () => {
|
|
219
|
+
logger.info('Received SIGTERM signal');
|
|
220
|
+
this.stop();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
process.on('uncaughtException', (error) => {
|
|
224
|
+
logger.error('Uncaught exception', error);
|
|
225
|
+
this.stop();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
229
|
+
logger.error('Unhandled rejection', reason);
|
|
230
|
+
this.stop();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Sleep helper
|
|
236
|
+
* @param {number} ms - Milliseconds to sleep
|
|
237
|
+
*/
|
|
238
|
+
sleep(ms) {
|
|
239
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = RalphAgent;
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const config = require('./config');
|
|
2
|
+
|
|
3
|
+
const LOG_LEVELS = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
info: 2,
|
|
7
|
+
debug: 3
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const currentLevel = LOG_LEVELS[config.logLevel] || LOG_LEVELS.info;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Redact sensitive data from logs
|
|
14
|
+
* @param {any} data - Data to redact
|
|
15
|
+
* @returns {any} Redacted data
|
|
16
|
+
*/
|
|
17
|
+
function redactSensitiveData(data) {
|
|
18
|
+
if (!data) return data;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Convert to string for pattern matching
|
|
22
|
+
let dataStr = typeof data === 'string' ? data : JSON.stringify(data);
|
|
23
|
+
|
|
24
|
+
// Redact common token patterns
|
|
25
|
+
dataStr = dataStr
|
|
26
|
+
.replace(/"Authorization":\s*"Bearer [^"]+"/g, '"Authorization": "Bearer [REDACTED]"')
|
|
27
|
+
.replace(/Authorization:\s*Bearer\s+[^\s,}]+/g, 'Authorization: Bearer [REDACTED]')
|
|
28
|
+
.replace(/RALPH_API_TOKEN=[^\s&]+/g, 'RALPH_API_TOKEN=[REDACTED]')
|
|
29
|
+
.replace(/"apiToken":\s*"[^"]+"/g, '"apiToken": "[REDACTED]"')
|
|
30
|
+
.replace(/"token":\s*"[^"]+"/g, '"token": "[REDACTED]"')
|
|
31
|
+
.replace(/"api_token":\s*"[^"]+"/g, '"api_token": "[REDACTED]"')
|
|
32
|
+
.replace(/Bearer\s+[A-Za-z0-9_-]{20,}/g, 'Bearer [REDACTED]');
|
|
33
|
+
|
|
34
|
+
// Return in original format
|
|
35
|
+
if (typeof data === 'string') {
|
|
36
|
+
return dataStr;
|
|
37
|
+
} else {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(dataStr);
|
|
40
|
+
} catch {
|
|
41
|
+
return dataStr; // Return string if can't parse back
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// If redaction fails, return safe placeholder
|
|
46
|
+
return '[REDACTION_ERROR]';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Safe JSON stringify that handles circular references
|
|
52
|
+
* @param {*} obj - Object to stringify
|
|
53
|
+
* @returns {string} JSON string or error message
|
|
54
|
+
*/
|
|
55
|
+
function safeStringify(obj) {
|
|
56
|
+
const seen = new WeakSet();
|
|
57
|
+
try {
|
|
58
|
+
return JSON.stringify(obj, (key, value) => {
|
|
59
|
+
// Handle circular references
|
|
60
|
+
if (typeof value === 'object' && value !== null) {
|
|
61
|
+
if (seen.has(value)) {
|
|
62
|
+
return '[Circular]';
|
|
63
|
+
}
|
|
64
|
+
seen.add(value);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}, 2);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return `[Unable to stringify: ${error.message}]`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function log(level, message, data = null) {
|
|
74
|
+
if (LOG_LEVELS[level] <= currentLevel) {
|
|
75
|
+
const timestamp = new Date().toISOString();
|
|
76
|
+
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
|
|
77
|
+
|
|
78
|
+
// Redact sensitive data from message
|
|
79
|
+
const safeMessage = redactSensitiveData(message);
|
|
80
|
+
|
|
81
|
+
if (data) {
|
|
82
|
+
// Redact and stringify data
|
|
83
|
+
const redactedData = redactSensitiveData(data);
|
|
84
|
+
console.log(prefix, safeMessage, safeStringify(redactedData));
|
|
85
|
+
} else {
|
|
86
|
+
console.log(prefix, safeMessage);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
error: (msg, data) => log('error', msg, data),
|
|
93
|
+
warn: (msg, data) => log('warn', msg, data),
|
|
94
|
+
info: (msg, data) => log('info', msg, data),
|
|
95
|
+
debug: (msg, data) => log('debug', msg, data)
|
|
96
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Ralph Agent Instructions
|
|
2
|
+
|
|
3
|
+
You are an autonomous coding agent working on a software project.
|
|
4
|
+
|
|
5
|
+
## CRITICAL: Worktree Awareness
|
|
6
|
+
|
|
7
|
+
This Ralph instance works in an isolated git worktree, NOT the main repository.
|
|
8
|
+
|
|
9
|
+
**Environment variables (always available):**
|
|
10
|
+
- `RALPH_WORKTREE_PATH`: Your worktree directory where code lives (e.g., ~/src/myproject-worktrees/feature-name)
|
|
11
|
+
- `RALPH_INSTANCE_DIR`: Instance directory where state files live (e.g., ~/src/myproject/ralph-instances/prd-feature)
|
|
12
|
+
- `RALPH_MAIN_REPO`: Main repository path (e.g., ~/src/myproject)
|
|
13
|
+
|
|
14
|
+
**File locations:**
|
|
15
|
+
- **Code files**: Work in `$RALPH_WORKTREE_PATH` - this is your working directory
|
|
16
|
+
- **State files**: Read/write from `$RALPH_INSTANCE_DIR`:
|
|
17
|
+
- `$RALPH_INSTANCE_DIR/prd.json` - Your task configuration
|
|
18
|
+
- `$RALPH_INSTANCE_DIR/progress.txt` - Your progress log
|
|
19
|
+
|
|
20
|
+
**Working directory rules:**
|
|
21
|
+
1. ALWAYS run code/git operations from worktree: `cd $RALPH_WORKTREE_PATH`
|
|
22
|
+
2. Read/write state files using absolute paths (the environment variables above)
|
|
23
|
+
|
|
24
|
+
## Permissions
|
|
25
|
+
|
|
26
|
+
You have FULL PERMISSION to:
|
|
27
|
+
- Edit ANY files in the repository (no need to ask for approval)
|
|
28
|
+
- Write new files as required by user stories
|
|
29
|
+
- Run bash commands for git operations, quality checks, testing, etc.
|
|
30
|
+
- Delete files if necessary for completing user stories
|
|
31
|
+
|
|
32
|
+
DO NOT wait for permission or approval - you are authorized to make all necessary changes autonomously.
|
|
33
|
+
|
|
34
|
+
## Your Task
|
|
35
|
+
|
|
36
|
+
1. Read the PRD at `$RALPH_INSTANCE_DIR/prd.json`
|
|
37
|
+
2. Read the progress log at `$RALPH_INSTANCE_DIR/progress.txt` (check Codebase Patterns section first)
|
|
38
|
+
3. Navigate to worktree: `cd $RALPH_WORKTREE_PATH` (SKIP branch checkout - you're already on the correct branch!)
|
|
39
|
+
4. Pick the **highest priority** user story where `passes: false`
|
|
40
|
+
5. Implement that single user story
|
|
41
|
+
6. Run quality checks (e.g., typecheck, lint, test - use whatever your project requires)
|
|
42
|
+
7. Update AGENTS.md files if you discover reusable patterns (see below)
|
|
43
|
+
8. If checks pass, commit ALL changes with message: `feat: [Story ID] - [Story Title]`
|
|
44
|
+
9. Update the PRD at `$RALPH_INSTANCE_DIR/prd.json` to set `passes: true` for the completed story
|
|
45
|
+
10. Append your progress to `$RALPH_INSTANCE_DIR/progress.txt`
|
|
46
|
+
|
|
47
|
+
## Example Commands
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Read configuration
|
|
51
|
+
cat $RALPH_INSTANCE_DIR/prd.json
|
|
52
|
+
|
|
53
|
+
# Navigate to worktree
|
|
54
|
+
cd $RALPH_WORKTREE_PATH
|
|
55
|
+
|
|
56
|
+
# Check git status
|
|
57
|
+
git status
|
|
58
|
+
|
|
59
|
+
# Make code changes (you're in the worktree)
|
|
60
|
+
# ... edit files, run tests, etc ...
|
|
61
|
+
|
|
62
|
+
# Run quality checks
|
|
63
|
+
npm run typecheck
|
|
64
|
+
npm test
|
|
65
|
+
|
|
66
|
+
# Commit changes
|
|
67
|
+
git add .
|
|
68
|
+
git commit -m "feat: US-001 - Add feature"
|
|
69
|
+
|
|
70
|
+
# Update state files (use absolute paths with environment variables!)
|
|
71
|
+
# Use Edit or Write tool with: $RALPH_INSTANCE_DIR/prd.json
|
|
72
|
+
# Use Edit or Write tool with: $RALPH_INSTANCE_DIR/progress.txt
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Progress Report Format
|
|
76
|
+
|
|
77
|
+
APPEND to progress.txt (never replace, always append):
|
|
78
|
+
```
|
|
79
|
+
## [Date/Time] - [Story ID]
|
|
80
|
+
Thread: https://ampcode.com/threads/$AMP_CURRENT_THREAD_ID
|
|
81
|
+
- What was implemented
|
|
82
|
+
- Files changed
|
|
83
|
+
- **Learnings for future iterations:**
|
|
84
|
+
- Patterns discovered (e.g., "this codebase uses X for Y")
|
|
85
|
+
- Gotchas encountered (e.g., "don't forget to update Z when changing W")
|
|
86
|
+
- Useful context (e.g., "the evaluation panel is in component X")
|
|
87
|
+
---
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Include the thread URL so future iterations can use the `read_thread` tool to reference previous work if needed.
|
|
91
|
+
|
|
92
|
+
The learnings section is critical - it helps future iterations avoid repeating mistakes and understand the codebase better.
|
|
93
|
+
|
|
94
|
+
## Consolidate Patterns
|
|
95
|
+
|
|
96
|
+
If you discover a **reusable pattern** that future iterations should know, add it to the `## Codebase Patterns` section at the TOP of progress.txt (create it if it doesn't exist). This section should consolidate the most important learnings:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
## Codebase Patterns
|
|
100
|
+
- Example: Use `sql<number>` template for aggregations
|
|
101
|
+
- Example: Always use `IF NOT EXISTS` for migrations
|
|
102
|
+
- Example: Export types from actions.ts for UI components
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Only add patterns that are **general and reusable**, not story-specific details.
|
|
106
|
+
|
|
107
|
+
## Update AGENTS.md Files
|
|
108
|
+
|
|
109
|
+
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
110
|
+
|
|
111
|
+
1. **Identify directories with edited files** - Look at which directories you modified
|
|
112
|
+
2. **Check for existing AGENTS.md** - Look for AGENTS.md in those directories or parent directories
|
|
113
|
+
3. **Add valuable learnings** - If you discovered something future developers/agents should know:
|
|
114
|
+
- API patterns or conventions specific to that module
|
|
115
|
+
- Gotchas or non-obvious requirements
|
|
116
|
+
- Dependencies between files
|
|
117
|
+
- Testing approaches for that area
|
|
118
|
+
- Configuration or environment requirements
|
|
119
|
+
|
|
120
|
+
**Examples of good AGENTS.md additions:**
|
|
121
|
+
- "When modifying X, also update Y to keep them in sync"
|
|
122
|
+
- "This module uses pattern Z for all API calls"
|
|
123
|
+
- "Tests require the dev server running on PORT 3000"
|
|
124
|
+
- "Field names must match the template exactly"
|
|
125
|
+
|
|
126
|
+
**Do NOT add:**
|
|
127
|
+
- Story-specific implementation details
|
|
128
|
+
- Temporary debugging notes
|
|
129
|
+
- Information already in progress.txt
|
|
130
|
+
|
|
131
|
+
Only update AGENTS.md if you have **genuinely reusable knowledge** that would help future work in that directory.
|
|
132
|
+
|
|
133
|
+
## Quality Requirements
|
|
134
|
+
|
|
135
|
+
- ALL commits must pass your project's quality checks (typecheck, lint, test)
|
|
136
|
+
- Do NOT commit broken code
|
|
137
|
+
- Keep changes focused and minimal
|
|
138
|
+
- Follow existing code patterns
|
|
139
|
+
|
|
140
|
+
## Browser Testing (Required for Frontend Stories)
|
|
141
|
+
|
|
142
|
+
For any story that changes UI, you MUST verify it works in the browser:
|
|
143
|
+
|
|
144
|
+
1. Load the `dev-browser` skill
|
|
145
|
+
2. Navigate to the relevant page
|
|
146
|
+
3. Verify the UI changes work as expected
|
|
147
|
+
4. Take a screenshot if helpful for the progress log
|
|
148
|
+
|
|
149
|
+
A frontend story is NOT complete until browser verification passes.
|
|
150
|
+
|
|
151
|
+
## Stop Condition
|
|
152
|
+
|
|
153
|
+
After completing a user story, check if ALL stories have `passes: true`.
|
|
154
|
+
|
|
155
|
+
If ALL stories are complete and passing, reply with:
|
|
156
|
+
<promise>COMPLETE</promise>
|
|
157
|
+
|
|
158
|
+
If there are still stories with `passes: false`, end your response normally (another iteration will pick up the next story).
|
|
159
|
+
|
|
160
|
+
## Important
|
|
161
|
+
|
|
162
|
+
- Work on ONE story per iteration
|
|
163
|
+
- Commit frequently
|
|
164
|
+
- Keep CI green
|
|
165
|
+
- Read the Codebase Patterns section in progress.txt before starting
|