@fermindi/pwn-cli 0.4.1 → 0.6.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/README.md +14 -10
- package/cli/batch.js +23 -10
- package/cli/index.js +2 -2
- package/cli/inject.js +3 -2
- package/cli/migrate.js +2 -2
- package/cli/mode.js +2 -2
- package/cli/status.js +1 -1
- package/cli/update.js +50 -6
- package/package.json +1 -1
- package/src/core/inject.js +25 -8
- package/src/core/validate.js +16 -1
- package/src/core/workspace.js +13 -11
- package/src/services/batch-service.js +78 -55
- package/templates/workspace/.ai/agents/claude.md +34 -103
- package/templates/workspace/.ai/tasks/active.md +1 -1
- package/templates/workspace/.ai/workflows/batch-task.md +43 -67
- package/templates/workspace/.claude/commands/mode.md +6 -5
- package/templates/workspace/.claude/settings.json +11 -2
- package/templates/workspace/.ai/tasks/backlog.md +0 -95
package/README.md
CHANGED
|
@@ -52,9 +52,9 @@ pwn notify test # Test notification channels
|
|
|
52
52
|
pwn notify send "msg" # Send notification
|
|
53
53
|
|
|
54
54
|
# Batch Execution
|
|
55
|
-
pwn batch
|
|
56
|
-
pwn batch --
|
|
57
|
-
pwn batch status
|
|
55
|
+
pwn batch run # Run autonomous batch loop
|
|
56
|
+
pwn batch run --dry-run # Preview next story
|
|
57
|
+
pwn batch status # Show progress
|
|
58
58
|
|
|
59
59
|
# Patterns
|
|
60
60
|
pwn patterns list # List all patterns
|
|
@@ -85,7 +85,7 @@ your-project/
|
|
|
85
85
|
│ │ └── deadends.md # Failed approaches (DE-XXX)
|
|
86
86
|
│ ├── tasks/
|
|
87
87
|
│ │ ├── active.md # Current work
|
|
88
|
-
│ │ └──
|
|
88
|
+
│ │ └── prd.json # Stories (structured JSON)
|
|
89
89
|
│ ├── patterns/
|
|
90
90
|
│ │ ├── index.md # Trigger mappings
|
|
91
91
|
│ │ ├── frontend/ # React, Vue, etc.
|
|
@@ -142,17 +142,21 @@ decisions tracking patterns cleanup
|
|
|
142
142
|
|
|
143
143
|
### Batch Execution
|
|
144
144
|
|
|
145
|
-
Execute
|
|
145
|
+
Execute stories autonomously via `batch_runner.sh`:
|
|
146
146
|
|
|
147
147
|
```bash
|
|
148
|
-
|
|
148
|
+
./.ai/batch/batch_runner.sh # Run batch loop
|
|
149
|
+
./.ai/batch/batch_runner.sh --dry-run # Preview next story
|
|
150
|
+
pwn batch run --phase 3 # Via CLI
|
|
149
151
|
```
|
|
150
152
|
|
|
151
153
|
Features:
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
154
|
+
- Reads stories from `.ai/tasks/prd.json`
|
|
155
|
+
- Quality gates (lint, test, typecheck) per task
|
|
156
|
+
- Retry with error context (up to 2x)
|
|
157
|
+
- Circuit breaker (3 consecutive failures)
|
|
158
|
+
- Rate limit detection + auto-wait
|
|
159
|
+
- Per-task logs in `logs/`
|
|
156
160
|
|
|
157
161
|
### Codespaces Integration
|
|
158
162
|
|
package/cli/batch.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { join } from 'path';
|
|
2
4
|
import * as batch from '../src/services/batch-service.js';
|
|
3
5
|
import { hasWorkspace } from '../src/core/state.js';
|
|
4
6
|
|
|
@@ -27,6 +29,19 @@ export default async function batchCommand(args = []) {
|
|
|
27
29
|
return showConfig();
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
if (subcommand === 'run') {
|
|
33
|
+
const runnerPath = join(process.cwd(), '.ai', 'batch', 'batch_runner.sh');
|
|
34
|
+
try {
|
|
35
|
+
execSync(`bash ${runnerPath} ${args.slice(1).join(' ')}`, {
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
stdio: 'inherit'
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
process.exit(error.status || 1);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
// Parse options
|
|
31
46
|
const options = parseOptions(args);
|
|
32
47
|
|
|
@@ -109,12 +124,8 @@ function showStatus() {
|
|
|
109
124
|
// Task counts
|
|
110
125
|
console.log('\n📝 Tasks\n');
|
|
111
126
|
console.log(` Active: ${status.tasks.activePending} pending, ${status.tasks.activeCompleted} completed`);
|
|
112
|
-
console.log(`
|
|
113
|
-
|
|
114
|
-
console.log(` - High: ${status.tasks.backlogHigh}`);
|
|
115
|
-
console.log(` - Medium: ${status.tasks.backlogMedium}`);
|
|
116
|
-
console.log(` - Low: ${status.tasks.backlogLow}`);
|
|
117
|
-
}
|
|
127
|
+
console.log(` Stories: ${status.tasks.storiesDone}/${status.tasks.storiesTotal} done`);
|
|
128
|
+
console.log(` Pending: ${status.tasks.storiesPending}`);
|
|
118
129
|
|
|
119
130
|
// Batch history
|
|
120
131
|
if (status.batchState?.completed?.length > 0) {
|
|
@@ -133,7 +144,7 @@ function showStatus() {
|
|
|
133
144
|
console.log(` Priority: ${nextTask.priority}`);
|
|
134
145
|
}
|
|
135
146
|
} else {
|
|
136
|
-
console.log('\n No tasks available in
|
|
147
|
+
console.log('\n No tasks available in prd.json');
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
|
|
@@ -185,7 +196,7 @@ async function dryRun(options) {
|
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
if (tasks.length === 0) {
|
|
188
|
-
console.log(' No tasks available in
|
|
199
|
+
console.log(' No tasks available in prd.json');
|
|
189
200
|
return;
|
|
190
201
|
}
|
|
191
202
|
|
|
@@ -305,6 +316,7 @@ function showHelp() {
|
|
|
305
316
|
console.log('Usage: pwn batch [command] [options]\n');
|
|
306
317
|
console.log('Commands:');
|
|
307
318
|
console.log(' (default) Execute next available task(s)');
|
|
319
|
+
console.log(' run Run batch_runner.sh (autonomous loop)');
|
|
308
320
|
console.log(' status Show batch status');
|
|
309
321
|
console.log(' config Show batch configuration\n');
|
|
310
322
|
console.log('Options:');
|
|
@@ -320,11 +332,12 @@ function showHelp() {
|
|
|
320
332
|
console.log(' --help, -h Show this help\n');
|
|
321
333
|
console.log('Examples:');
|
|
322
334
|
console.log(' pwn batch # Execute next task');
|
|
335
|
+
console.log(' pwn batch run # Run autonomous batch loop');
|
|
336
|
+
console.log(' pwn batch run --dry-run # Preview next story');
|
|
337
|
+
console.log(' pwn batch run --phase 3 # Run specific phase');
|
|
323
338
|
console.log(' pwn batch --count 5 # Execute 5 tasks');
|
|
324
339
|
console.log(' pwn batch --dry-run # Preview execution');
|
|
325
|
-
console.log(' pwn batch --priority high # Only high priority');
|
|
326
340
|
console.log(' pwn batch --resume # Resume paused batch');
|
|
327
|
-
console.log(' pwn batch --resume --skip # Resume, skip current');
|
|
328
341
|
console.log(' pwn batch status # Show status');
|
|
329
342
|
console.log(' pwn batch config # Show configuration\n');
|
|
330
343
|
console.log('Configuration:');
|
package/cli/index.js
CHANGED
|
@@ -27,7 +27,7 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
27
27
|
console.log(' status Show workspace status');
|
|
28
28
|
console.log(' validate Validate workspace structure');
|
|
29
29
|
console.log(' notify Send notifications (test, send, config)');
|
|
30
|
-
console.log(' batch Execute tasks
|
|
30
|
+
console.log(' batch Execute tasks (batch run = autonomous loop)');
|
|
31
31
|
console.log(' mode Manage session mode (interactive/batch)');
|
|
32
32
|
console.log(' patterns Manage patterns and triggers');
|
|
33
33
|
console.log(' knowledge Knowledge lifecycle management');
|
|
@@ -41,7 +41,7 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
41
41
|
console.log(' save --message=X Save with custom summary');
|
|
42
42
|
console.log(' validate --verbose Show detailed structure report');
|
|
43
43
|
console.log(' notify test [ch] Test notification channel');
|
|
44
|
-
console.log(' batch
|
|
44
|
+
console.log(' batch run Run autonomous batch loop');
|
|
45
45
|
console.log(' mode batch --max-tasks=3 Configure batch mode');
|
|
46
46
|
console.log(' patterns eval <f> Evaluate triggers for file');
|
|
47
47
|
console.log(' knowledge status Show knowledge system status');
|
package/cli/inject.js
CHANGED
|
@@ -61,9 +61,10 @@ export default async function injectCommand(args = []) {
|
|
|
61
61
|
console.log('📁 Created structure:');
|
|
62
62
|
console.log(' .ai/');
|
|
63
63
|
console.log(' ├── memory/ (decisions, patterns, dead-ends)');
|
|
64
|
-
console.log(' ├── tasks/ (active work,
|
|
64
|
+
console.log(' ├── tasks/ (active work, prd.json)');
|
|
65
65
|
console.log(' ├── patterns/ (auto-applied patterns)');
|
|
66
|
-
console.log(' ├──
|
|
66
|
+
console.log(' ├── batch/ (batch runner, prompts)
|
|
67
|
+
├── workflows/ (batch execution)');
|
|
67
68
|
console.log(' ├── agents/ (AI agent configs)');
|
|
68
69
|
console.log(' └── config/ (notifications, etc)');
|
|
69
70
|
console.log(' .claude/');
|
package/cli/migrate.js
CHANGED
|
@@ -300,7 +300,7 @@ function generateMigrationActions(parsed, cwd) {
|
|
|
300
300
|
|
|
301
301
|
// Tasks
|
|
302
302
|
if (parsed.tasks.length > 0) {
|
|
303
|
-
const targetPath = join(cwd, '.ai', 'tasks', '
|
|
303
|
+
const targetPath = join(cwd, '.ai', 'tasks', 'active.md');
|
|
304
304
|
let newContent = '\n## Migrated Tasks\n\n';
|
|
305
305
|
for (const task of parsed.tasks) {
|
|
306
306
|
newContent += task.replace(/^##\s+.+\n/, '') + '\n';
|
|
@@ -310,7 +310,7 @@ function generateMigrationActions(parsed, cwd) {
|
|
|
310
310
|
type: 'append',
|
|
311
311
|
target: targetPath,
|
|
312
312
|
content: newContent,
|
|
313
|
-
description: `Add ${parsed.tasks.length} task section(s) to
|
|
313
|
+
description: `Add ${parsed.tasks.length} task section(s) to active.md`
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
|
package/cli/mode.js
CHANGED
|
@@ -71,7 +71,7 @@ function setBatchMode(args) {
|
|
|
71
71
|
console.log('✅ Switched to batch mode (config reset to defaults)\n');
|
|
72
72
|
console.log('Config:');
|
|
73
73
|
printConfig(DEFAULT_BATCH_CONFIG);
|
|
74
|
-
console.log('\nRun \'pwn batch\' to start executing tasks from
|
|
74
|
+
console.log('\nRun \'pwn batch run\' to start executing tasks from prd.json.');
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -99,7 +99,7 @@ function setBatchMode(args) {
|
|
|
99
99
|
printConfig(getBatchConfig());
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
console.log('\nRun \'pwn batch\' to start executing tasks from
|
|
102
|
+
console.log('\nRun \'pwn batch run\' to start executing tasks from prd.json.');
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function parseConfigArgs(args) {
|
package/cli/status.js
CHANGED
|
@@ -29,7 +29,7 @@ export default async function statusCommand() {
|
|
|
29
29
|
// Tasks summary
|
|
30
30
|
console.log('📋 Tasks');
|
|
31
31
|
console.log(` Active: ${info.tasks.active.pending} pending, ${info.tasks.active.completed} completed`);
|
|
32
|
-
console.log(`
|
|
32
|
+
console.log(` Stories: ${info.tasks.backlog.done || 0}/${info.tasks.backlog.total} done, ${info.tasks.backlog.pending || 0} pending`);
|
|
33
33
|
console.log();
|
|
34
34
|
|
|
35
35
|
// Memory summary
|
package/cli/update.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, cpSync, renameSync, mkdirSync, readdirSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { convertBacklogToPrd } from '../src/services/batch-service.js';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
|
|
@@ -17,6 +18,10 @@ const FRAMEWORK_FILES = [
|
|
|
17
18
|
'patterns/backend/backend.template.md',
|
|
18
19
|
'patterns/universal/universal.template.md',
|
|
19
20
|
'workflows/batch-task.md',
|
|
21
|
+
'batch/batch_runner.sh',
|
|
22
|
+
'batch/prompt.md',
|
|
23
|
+
'batch/progress.txt',
|
|
24
|
+
'batch/prd_status.sh',
|
|
20
25
|
'config/README.md',
|
|
21
26
|
'README.md',
|
|
22
27
|
];
|
|
@@ -39,7 +44,7 @@ const USER_FILES = [
|
|
|
39
44
|
'memory/deadends.md',
|
|
40
45
|
'memory/archive/',
|
|
41
46
|
'tasks/active.md',
|
|
42
|
-
'tasks/
|
|
47
|
+
'tasks/prd.json',
|
|
43
48
|
'state.json',
|
|
44
49
|
'config/notifications.json',
|
|
45
50
|
];
|
|
@@ -122,6 +127,46 @@ export default async function updateCommand(args = []) {
|
|
|
122
127
|
}
|
|
123
128
|
// ============================================
|
|
124
129
|
|
|
130
|
+
// ============================================
|
|
131
|
+
// MIGRATION: backlog.md → prd.json
|
|
132
|
+
// Convert legacy backlog.md to structured prd.json
|
|
133
|
+
// ============================================
|
|
134
|
+
const backlogPath = join(aiDir, 'tasks', 'backlog.md');
|
|
135
|
+
const prdPath = join(aiDir, 'tasks', 'prd.json');
|
|
136
|
+
|
|
137
|
+
if (existsSync(backlogPath)) {
|
|
138
|
+
// Check if prd.json is missing or is the default template (empty stories)
|
|
139
|
+
let needsMigration = !existsSync(prdPath);
|
|
140
|
+
if (!needsMigration && existsSync(prdPath)) {
|
|
141
|
+
try {
|
|
142
|
+
const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
|
|
143
|
+
needsMigration = !prd.stories || prd.stories.length === 0;
|
|
144
|
+
} catch {
|
|
145
|
+
needsMigration = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (needsMigration) {
|
|
150
|
+
const backlogContent = readFileSync(backlogPath, 'utf8');
|
|
151
|
+
const prd = convertBacklogToPrd(backlogContent);
|
|
152
|
+
|
|
153
|
+
if (prd.stories.length > 0) {
|
|
154
|
+
if (dryRun) {
|
|
155
|
+
console.log(` 🔄 Would migrate: .ai/tasks/backlog.md → prd.json (${prd.stories.length} stories)`);
|
|
156
|
+
console.log(` 📦 Would backup: backlog.md → ~backlog.md`);
|
|
157
|
+
} else {
|
|
158
|
+
writeFileSync(prdPath, JSON.stringify(prd, null, 2));
|
|
159
|
+
renameSync(backlogPath, join(aiDir, 'tasks', '~backlog.md'));
|
|
160
|
+
console.log(` 🔄 Migrated: backlog.md → prd.json (${prd.stories.length} stories)`);
|
|
161
|
+
console.log(` 📦 Backed up: backlog.md → .ai/tasks/~backlog.md`);
|
|
162
|
+
backed_up.push('backlog.md');
|
|
163
|
+
}
|
|
164
|
+
updated.push('tasks/backlog.md → prd.json');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// ============================================
|
|
169
|
+
|
|
125
170
|
// Update framework files in .ai/
|
|
126
171
|
for (const file of FRAMEWORK_FILES) {
|
|
127
172
|
const templateFile = join(templateDir, file);
|
|
@@ -166,13 +211,12 @@ export default async function updateCommand(args = []) {
|
|
|
166
211
|
if (templateContent !== currentContent) {
|
|
167
212
|
if (dryRun) {
|
|
168
213
|
console.log(` 📝 Would update: CLAUDE.md`);
|
|
169
|
-
|
|
170
|
-
if (currentContent && currentContent !== templateContent) {
|
|
214
|
+
if (currentContent) {
|
|
171
215
|
console.log(` 📦 Would backup: CLAUDE.md → ~CLAUDE.md`);
|
|
172
216
|
}
|
|
173
217
|
} else {
|
|
174
|
-
// Backup existing CLAUDE.md
|
|
175
|
-
if (existsSync(claudeMdPath) && currentContent
|
|
218
|
+
// Backup existing CLAUDE.md
|
|
219
|
+
if (existsSync(claudeMdPath) && currentContent) {
|
|
176
220
|
renameSync(claudeMdPath, backupClaudeMdPath);
|
|
177
221
|
console.log(` 📦 Backed up: CLAUDE.md → ~CLAUDE.md`);
|
|
178
222
|
backed_up.push('CLAUDE.md');
|
|
@@ -273,7 +317,7 @@ export default async function updateCommand(args = []) {
|
|
|
273
317
|
// Show what was preserved
|
|
274
318
|
console.log('🔒 Preserved (user data):');
|
|
275
319
|
console.log(' .ai/memory/ (decisions, patterns, deadends)');
|
|
276
|
-
console.log(' .ai/tasks/ (active,
|
|
320
|
+
console.log(' .ai/tasks/ (active, prd.json)');
|
|
277
321
|
console.log(' .ai/state.json (session state)');
|
|
278
322
|
console.log(' .claude/ (your custom commands preserved)\n');
|
|
279
323
|
}
|
package/package.json
CHANGED
package/src/core/inject.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join, dirname } from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
5
|
import { initState } from './state.js';
|
|
6
|
+
import { convertBacklogToPrd } from '../services/batch-service.js';
|
|
6
7
|
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
|
|
@@ -147,6 +148,20 @@ export async function inject(options = {}) {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
// Migrate backlog.md → prd.json before overwriting (force path)
|
|
152
|
+
let migratedPrd = null;
|
|
153
|
+
if (force) {
|
|
154
|
+
const backlogPath = join(targetDir, 'tasks', 'backlog.md');
|
|
155
|
+
if (existsSync(backlogPath)) {
|
|
156
|
+
const backlogContent = readFileSync(backlogPath, 'utf8');
|
|
157
|
+
const prd = convertBacklogToPrd(backlogContent);
|
|
158
|
+
if (prd.stories.length > 0) {
|
|
159
|
+
migratedPrd = prd;
|
|
160
|
+
log(`🔄 Detected backlog.md with ${prd.stories.length} stories, will convert to prd.json`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
150
165
|
try {
|
|
151
166
|
// Copy workspace template
|
|
152
167
|
log('📦 Copying workspace template...');
|
|
@@ -172,31 +187,33 @@ export async function inject(options = {}) {
|
|
|
172
187
|
// Initialize state.json with current user
|
|
173
188
|
initState(cwd);
|
|
174
189
|
|
|
190
|
+
// Write migrated prd.json (from backlog.md) if available
|
|
191
|
+
if (migratedPrd) {
|
|
192
|
+
const prdPath = join(targetDir, 'tasks', 'prd.json');
|
|
193
|
+
writeFileSync(prdPath, JSON.stringify(migratedPrd, null, 2));
|
|
194
|
+
log(`📝 Created prd.json from backlog.md (${migratedPrd.stories.length} stories)`);
|
|
195
|
+
}
|
|
196
|
+
|
|
175
197
|
// Update .gitignore
|
|
176
198
|
updateGitignore(cwd, silent);
|
|
177
199
|
|
|
178
|
-
// Handle CLAUDE.md: backup existing
|
|
200
|
+
// Handle CLAUDE.md: backup existing and copy PWN template to root
|
|
179
201
|
let backupInfo = { backed_up: [] };
|
|
180
202
|
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
181
203
|
const backupClaudeMdPath = join(cwd, '~CLAUDE.md');
|
|
182
204
|
const templateClaudeMd = join(targetDir, 'agents', 'claude.md');
|
|
183
|
-
const templateContent = existsSync(templateClaudeMd) ? readFileSync(templateClaudeMd, 'utf8') : '';
|
|
184
205
|
|
|
185
|
-
// Backup existing CLAUDE.md if present
|
|
206
|
+
// Backup existing CLAUDE.md if present
|
|
186
207
|
if (backedUpContent['CLAUDE.md'] || backedUpContent['claude.md']) {
|
|
187
208
|
const originalName = backedUpContent['CLAUDE.md'] ? 'CLAUDE.md' : 'claude.md';
|
|
188
209
|
const originalPath = join(cwd, originalName);
|
|
189
|
-
const existingContent = backedUpContent[originalName]?.content || '';
|
|
190
210
|
|
|
191
|
-
|
|
192
|
-
if (existsSync(originalPath) && existingContent !== templateContent) {
|
|
211
|
+
if (existsSync(originalPath)) {
|
|
193
212
|
renameSync(originalPath, backupClaudeMdPath);
|
|
194
213
|
backupInfo.backed_up.push({ from: originalName, to: '~CLAUDE.md' });
|
|
195
214
|
if (!silent) {
|
|
196
215
|
console.log(`📦 Backed up ${originalName} → ~CLAUDE.md`);
|
|
197
216
|
}
|
|
198
|
-
} else if (existsSync(originalPath) && !silent) {
|
|
199
|
-
console.log(`⏭️ Skipped backup: ${originalName} is identical to PWN template`);
|
|
200
217
|
}
|
|
201
218
|
}
|
|
202
219
|
|
package/src/core/validate.js
CHANGED
|
@@ -24,7 +24,13 @@ const REQUIRED_STRUCTURE = {
|
|
|
24
24
|
],
|
|
25
25
|
taskFiles: [
|
|
26
26
|
'tasks/active.md',
|
|
27
|
-
'tasks/
|
|
27
|
+
'tasks/prd.json'
|
|
28
|
+
],
|
|
29
|
+
batchFiles: [
|
|
30
|
+
'batch/batch_runner.sh',
|
|
31
|
+
'batch/prompt.md',
|
|
32
|
+
'batch/progress.txt',
|
|
33
|
+
'batch/prd_status.sh'
|
|
28
34
|
],
|
|
29
35
|
agentFiles: [
|
|
30
36
|
'agents/README.md',
|
|
@@ -88,6 +94,14 @@ export function validate(cwd = process.cwd()) {
|
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
// Check batch files
|
|
98
|
+
for (const file of REQUIRED_STRUCTURE.batchFiles) {
|
|
99
|
+
const filePath = join(aiDir, file);
|
|
100
|
+
if (!existsSync(filePath)) {
|
|
101
|
+
warnings.push(`Missing batch file: .ai/${file}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
// Check agent files
|
|
92
106
|
for (const file of REQUIRED_STRUCTURE.agentFiles) {
|
|
93
107
|
const filePath = join(aiDir, file);
|
|
@@ -189,6 +203,7 @@ export function getStructureReport(cwd = process.cwd()) {
|
|
|
189
203
|
...REQUIRED_STRUCTURE.files,
|
|
190
204
|
...REQUIRED_STRUCTURE.memoryFiles,
|
|
191
205
|
...REQUIRED_STRUCTURE.taskFiles,
|
|
206
|
+
...REQUIRED_STRUCTURE.batchFiles,
|
|
192
207
|
...REQUIRED_STRUCTURE.agentFiles,
|
|
193
208
|
...REQUIRED_STRUCTURE.patternFiles
|
|
194
209
|
];
|
package/src/core/workspace.js
CHANGED
|
@@ -39,11 +39,11 @@ export function getWorkspaceInfo(cwd = process.cwd()) {
|
|
|
39
39
|
*/
|
|
40
40
|
function getTasksSummary(cwd) {
|
|
41
41
|
const activePath = join(cwd, '.ai', 'tasks', 'active.md');
|
|
42
|
-
const
|
|
42
|
+
const prdPath = join(cwd, '.ai', 'tasks', 'prd.json');
|
|
43
43
|
|
|
44
44
|
const summary = {
|
|
45
45
|
active: { total: 0, completed: 0, pending: 0 },
|
|
46
|
-
backlog: { total: 0 }
|
|
46
|
+
backlog: { total: 0, done: 0, pending: 0 }
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// Parse active.md
|
|
@@ -63,15 +63,17 @@ function getTasksSummary(cwd) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// Parse
|
|
67
|
-
if (existsSync(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
66
|
+
// Parse prd.json
|
|
67
|
+
if (existsSync(prdPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
|
|
70
|
+
summary.backlog = {
|
|
71
|
+
total: prd.stories.length,
|
|
72
|
+
done: prd.stories.filter(s => s.passes).length,
|
|
73
|
+
pending: prd.stories.filter(s => !s.passes).length
|
|
74
|
+
};
|
|
75
|
+
} catch {
|
|
76
|
+
// Invalid JSON, leave defaults
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -125,19 +125,26 @@ export function parseActiveTasks(cwd = process.cwd()) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Parse
|
|
128
|
+
* Parse stories from prd.json
|
|
129
129
|
* @param {string} cwd - Working directory
|
|
130
|
-
* @returns {Array<object>} Array of
|
|
130
|
+
* @returns {Array<object>} Array of stories
|
|
131
131
|
*/
|
|
132
|
-
export function
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
export function parsePrdTasks(cwd = process.cwd()) {
|
|
133
|
+
const prdPath = join(cwd, '.ai', 'tasks', 'prd.json');
|
|
134
|
+
if (!existsSync(prdPath)) return [];
|
|
135
|
+
try {
|
|
136
|
+
const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
|
|
137
|
+
return prd.stories || [];
|
|
138
|
+
} catch {
|
|
136
139
|
return [];
|
|
137
140
|
}
|
|
141
|
+
}
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
/**
|
|
144
|
+
* @deprecated Use parsePrdTasks instead
|
|
145
|
+
*/
|
|
146
|
+
export function parseBacklogTasks(cwd = process.cwd()) {
|
|
147
|
+
return parsePrdTasks(cwd);
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
/**
|
|
@@ -257,16 +264,36 @@ function parseBacklogFromMarkdown(content) {
|
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
/**
|
|
260
|
-
*
|
|
267
|
+
* Convert backlog.md content to prd.json format
|
|
268
|
+
* @param {string} backlogContent - Raw markdown content from backlog.md
|
|
269
|
+
* @param {string} projectName - Project name for prd.json
|
|
270
|
+
* @returns {object} PRD JSON structure
|
|
271
|
+
*/
|
|
272
|
+
export function convertBacklogToPrd(backlogContent, projectName = 'my-project') {
|
|
273
|
+
const tasks = parseBacklogFromMarkdown(backlogContent);
|
|
274
|
+
return {
|
|
275
|
+
project: projectName,
|
|
276
|
+
branch: "main",
|
|
277
|
+
stories: tasks.map(t => ({
|
|
278
|
+
id: t.id,
|
|
279
|
+
title: t.title,
|
|
280
|
+
phase: t.section ? `Phase ${t.section}` : "Phase 1",
|
|
281
|
+
effort: t.effort || "M",
|
|
282
|
+
dependencies: t.dependencies ? t.dependencies.split(',').map(d => d.trim()) : [],
|
|
283
|
+
passes: false,
|
|
284
|
+
acceptance_criteria: [],
|
|
285
|
+
notes: t.description || ""
|
|
286
|
+
}))
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Select next task to execute based on prd.json dependencies
|
|
261
292
|
* @param {string} cwd - Working directory
|
|
262
293
|
* @param {object} options - Selection options
|
|
263
294
|
* @returns {object|null} Selected task or null
|
|
264
295
|
*/
|
|
265
296
|
export function selectNextTask(cwd = process.cwd(), options = {}) {
|
|
266
|
-
const config = loadConfig(cwd);
|
|
267
|
-
const strategy = options.strategy || config.selection_strategy;
|
|
268
|
-
const priorityFilter = options.priority;
|
|
269
|
-
|
|
270
297
|
// First check active tasks for incomplete ones
|
|
271
298
|
const activeTasks = parseActiveTasks(cwd);
|
|
272
299
|
const pendingActive = activeTasks.filter(t => !t.completed && !t.blockedBy);
|
|
@@ -275,47 +302,40 @@ export function selectNextTask(cwd = process.cwd(), options = {}) {
|
|
|
275
302
|
return pendingActive[0];
|
|
276
303
|
}
|
|
277
304
|
|
|
278
|
-
// Then check
|
|
279
|
-
|
|
305
|
+
// Then check prd.json stories
|
|
306
|
+
const stories = parsePrdTasks(cwd);
|
|
307
|
+
const doneIds = stories.filter(s => s.passes).map(s => s.id);
|
|
280
308
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// Filter out blocked tasks
|
|
287
|
-
backlogTasks = backlogTasks.filter(t => !t.dependencies || t.dependencies === 'None');
|
|
288
|
-
|
|
289
|
-
if (backlogTasks.length === 0) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
309
|
+
const eligible = stories.find(s =>
|
|
310
|
+
!s.passes &&
|
|
311
|
+
s.dependencies.every(dep => doneIds.includes(dep)) &&
|
|
312
|
+
(!options.phase || s.phase === options.phase)
|
|
313
|
+
);
|
|
292
314
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
case 'effort':
|
|
296
|
-
// Sort by effort (smallest first)
|
|
297
|
-
const effortOrder = { 'XS': 1, 'S': 2, 'M': 3, 'L': 4, 'XL': 5 };
|
|
298
|
-
backlogTasks.sort((a, b) => {
|
|
299
|
-
const aEffort = effortOrder[a.effort] || 3;
|
|
300
|
-
const bEffort = effortOrder[b.effort] || 3;
|
|
301
|
-
return aEffort - bEffort;
|
|
302
|
-
});
|
|
303
|
-
break;
|
|
315
|
+
return eligible || null;
|
|
316
|
+
}
|
|
304
317
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Mark a story as done in prd.json
|
|
320
|
+
* @param {string} taskId - Story ID
|
|
321
|
+
* @param {string} cwd - Working directory
|
|
322
|
+
* @returns {boolean} Success
|
|
323
|
+
*/
|
|
324
|
+
export function markStoryDone(taskId, cwd = process.cwd()) {
|
|
325
|
+
const prdPath = join(cwd, '.ai', 'tasks', 'prd.json');
|
|
326
|
+
if (!existsSync(prdPath)) return false;
|
|
327
|
+
try {
|
|
328
|
+
const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
|
|
329
|
+
const story = prd.stories.find(s => s.id === taskId);
|
|
330
|
+
if (story) {
|
|
331
|
+
story.passes = true;
|
|
332
|
+
writeFileSync(prdPath, JSON.stringify(prd, null, 2));
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
// Invalid JSON
|
|
316
337
|
}
|
|
317
|
-
|
|
318
|
-
return backlogTasks[0];
|
|
338
|
+
return false;
|
|
319
339
|
}
|
|
320
340
|
|
|
321
341
|
/**
|
|
@@ -598,10 +618,12 @@ export function getStatus(cwd = process.cwd()) {
|
|
|
598
618
|
const config = loadConfig(cwd);
|
|
599
619
|
const batchState = getBatchState(cwd);
|
|
600
620
|
const activeTasks = parseActiveTasks(cwd);
|
|
601
|
-
const
|
|
621
|
+
const stories = parsePrdTasks(cwd);
|
|
602
622
|
|
|
603
623
|
const pendingActive = activeTasks.filter(t => !t.completed);
|
|
604
624
|
const completedActive = activeTasks.filter(t => t.completed);
|
|
625
|
+
const doneStories = stories.filter(s => s.passes);
|
|
626
|
+
const pendingStories = stories.filter(s => !s.passes);
|
|
605
627
|
|
|
606
628
|
return {
|
|
607
629
|
hasWorkspace: true,
|
|
@@ -611,10 +633,11 @@ export function getStatus(cwd = process.cwd()) {
|
|
|
611
633
|
activeTotal: activeTasks.length,
|
|
612
634
|
activePending: pendingActive.length,
|
|
613
635
|
activeCompleted: completedActive.length,
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
636
|
+
storiesTotal: stories.length,
|
|
637
|
+
storiesDone: doneStories.length,
|
|
638
|
+
storiesPending: pendingStories.length,
|
|
639
|
+
// Legacy compat
|
|
640
|
+
backlogTotal: stories.length
|
|
618
641
|
},
|
|
619
642
|
isRunning: batchState?.status === 'running',
|
|
620
643
|
isPaused: batchState?.status === 'paused',
|
|
@@ -71,10 +71,10 @@ If this is your first session:
|
|
|
71
71
|
- Blocked items and blockers
|
|
72
72
|
- Priority ordering
|
|
73
73
|
|
|
74
|
-
2. **`/.ai/tasks/
|
|
75
|
-
- Future work
|
|
74
|
+
2. **`/.ai/tasks/prd.json`** (if needed)
|
|
75
|
+
- Future work as structured JSON stories
|
|
76
76
|
- Use when no active work assigned
|
|
77
|
-
- Pick
|
|
77
|
+
- Pick next story with dependencies satisfied
|
|
78
78
|
|
|
79
79
|
3. **Project-specific notes** (if applicable)
|
|
80
80
|
- Check `/CLAUDE.md` in project root
|
|
@@ -268,110 +268,41 @@ When completing work:
|
|
|
268
268
|
|
|
269
269
|
## Batch Mode
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
Autonomous task execution via `.ai/batch/batch_runner.sh`.
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
- Follow quality gates strictly
|
|
276
|
-
- Make conservative decisions (ask user when uncertain)
|
|
277
|
-
- Commit and push changes automatically
|
|
278
|
-
- Continue until backlog depleted or limits reached
|
|
279
|
-
|
|
280
|
-
### Batch Workflow
|
|
273
|
+
The runner reads stories from `.ai/tasks/prd.json`, spawns fresh Claude sessions
|
|
274
|
+
per task, runs quality gates, and tracks progress.
|
|
281
275
|
|
|
276
|
+
### Usage
|
|
277
|
+
```bash
|
|
278
|
+
./.ai/batch/batch_runner.sh --dry-run # Preview next task
|
|
279
|
+
./.ai/batch/batch_runner.sh # Run (default 20 iterations)
|
|
280
|
+
./.ai/batch/batch_runner.sh 50 # Custom iteration limit
|
|
281
|
+
./.ai/batch/batch_runner.sh --phase 3 # Specific phase only
|
|
282
282
|
```
|
|
283
|
-
1. Read tasks/active.md
|
|
284
|
-
├─ If incomplete tasks: resume work
|
|
285
|
-
└─ If no active tasks: read backlog.md
|
|
286
|
-
|
|
287
|
-
2. Select highest-priority unblocked task
|
|
288
|
-
├─ Check dependencies
|
|
289
|
-
├─ Verify not blocked
|
|
290
|
-
└─ Move to tasks/active.md
|
|
291
|
-
|
|
292
|
-
3. Create feature branch
|
|
293
|
-
└─ Name: feature/{task-id}-{slug}
|
|
294
|
-
|
|
295
|
-
4. Execute work following patterns
|
|
296
|
-
├─ Auto-apply triggers from patterns/index.md
|
|
297
|
-
├─ Keep commits atomic and descriptive
|
|
298
|
-
└─ Test incrementally
|
|
299
|
-
|
|
300
|
-
5. Quality gates must pass
|
|
301
|
-
├─ If gate fails:
|
|
302
|
-
│ ├─ Attempt auto-fix if pattern known
|
|
303
|
-
│ ├─ Pause batch if can't fix
|
|
304
|
-
│ └─ Notify user
|
|
305
|
-
└─ If all pass: proceed to commit
|
|
306
|
-
|
|
307
|
-
6. Commit and push
|
|
308
|
-
├─ Stage all changes
|
|
309
|
-
├─ Commit with conventional format
|
|
310
|
-
├─ Push to origin
|
|
311
|
-
└─ Create PR if configured
|
|
312
|
-
|
|
313
|
-
7. Clean up and continue
|
|
314
|
-
├─ Delete local feature branch
|
|
315
|
-
├─ Update tasks/active.md
|
|
316
|
-
├─ Check if more tasks to execute
|
|
317
|
-
└─ Continue or stop
|
|
318
|
-
|
|
319
|
-
8. Completion report
|
|
320
|
-
└─ Show summary: tasks done, time spent, any issues
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Batch Configuration
|
|
324
|
-
|
|
325
|
-
Read from `/.ai/state.json`:
|
|
326
283
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
"auto_commit": true,
|
|
335
|
-
"auto_push": false,
|
|
336
|
-
"create_pr": false,
|
|
337
|
-
"branch_format": "feature/{id}-{slug}",
|
|
338
|
-
"commit_format": "conventional"
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
```
|
|
284
|
+
### State Files
|
|
285
|
+
| File | Purpose |
|
|
286
|
+
|------|---------|
|
|
287
|
+
| .ai/tasks/prd.json | Single source of truth for task status |
|
|
288
|
+
| .ai/batch/progress.txt | Append-only operational learnings |
|
|
289
|
+
| .ai/batch/prompt.md | Prompt template for Claude sessions |
|
|
290
|
+
| logs/ | Per-task Claude output logs |
|
|
342
291
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
-
|
|
354
|
-
-
|
|
355
|
-
-
|
|
356
|
-
-
|
|
357
|
-
|
|
358
|
-
**Git Conflicts:**
|
|
359
|
-
- Stop immediately
|
|
360
|
-
- Notify user of conflict
|
|
361
|
-
- Require manual resolution
|
|
362
|
-
- Resume after conflict resolved
|
|
363
|
-
|
|
364
|
-
**Missing Dependencies:**
|
|
365
|
-
- Check if dependency task exists
|
|
366
|
-
- Add to batch queue if available
|
|
367
|
-
- Execute dependency first
|
|
368
|
-
- Resume original task
|
|
369
|
-
|
|
370
|
-
**Resource Limits Reached:**
|
|
371
|
-
- Stop execution gracefully
|
|
372
|
-
- Report progress
|
|
373
|
-
- Create clean state for next batch
|
|
374
|
-
- Notify user of completion status
|
|
292
|
+
### How It Works
|
|
293
|
+
1. Read prd.json → find next incomplete story (deps satisfied)
|
|
294
|
+
2. Spawn: `claude --print --dangerously-skip-permissions -p "implement story X"`
|
|
295
|
+
3. Run quality gates (configurable per project)
|
|
296
|
+
4. If pass → mark story done in prd.json, commit
|
|
297
|
+
5. If fail → retry up to 2x with error context
|
|
298
|
+
6. Append learnings to progress.txt
|
|
299
|
+
7. Repeat until done or max iterations
|
|
300
|
+
|
|
301
|
+
### Safety
|
|
302
|
+
- Circuit breaker: 3 consecutive failures → stop
|
|
303
|
+
- Rate limit detection + auto-wait
|
|
304
|
+
- Graceful Ctrl+C shutdown
|
|
305
|
+
- Per-task logs for debugging
|
|
375
306
|
|
|
376
307
|
---
|
|
377
308
|
|
|
@@ -400,7 +331,7 @@ Reference these files in this priority order:
|
|
|
400
331
|
- Project changelog or git history
|
|
401
332
|
|
|
402
333
|
5. **Fifth Priority: Planning**
|
|
403
|
-
- `/.ai/tasks/
|
|
334
|
+
- `/.ai/tasks/prd.json` - Future work (structured)
|
|
404
335
|
- Roadmap or project documentation
|
|
405
336
|
- Upstream issues or features
|
|
406
337
|
|
|
@@ -70,7 +70,7 @@ Highlight 1-3 most important tasks for today.
|
|
|
70
70
|
|
|
71
71
|
## Notes
|
|
72
72
|
|
|
73
|
-
- Check
|
|
73
|
+
- Check prd.json for upcoming stories
|
|
74
74
|
- Move completed tasks to archive or backlog with completion date
|
|
75
75
|
- When stuck, create SPIKE task to investigate
|
|
76
76
|
- Reference decisions from `memory/decisions.md`
|
|
@@ -4,103 +4,79 @@ This file defines the template for autonomous batch task execution via the `pwn
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Batch task execution
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
Batch task execution uses `.ai/batch/batch_runner.sh` to autonomously:
|
|
8
|
+
- Read stories from `.ai/tasks/prd.json`
|
|
9
|
+
- Spawn fresh Claude sessions per task
|
|
10
10
|
- Run quality gates (tests, linting, type checking)
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- Track progress and learnings between iterations
|
|
12
|
+
- Handle retries, rate limits, and circuit breaking
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
#
|
|
18
|
-
|
|
17
|
+
# Preview next task
|
|
18
|
+
./.ai/batch/batch_runner.sh --dry-run
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
|
|
20
|
+
# Run batch (default 20 iterations)
|
|
21
|
+
./.ai/batch/batch_runner.sh
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
|
|
23
|
+
# Custom iteration limit
|
|
24
|
+
./.ai/batch/batch_runner.sh 50
|
|
25
25
|
|
|
26
|
-
#
|
|
27
|
-
|
|
26
|
+
# Specific phase only
|
|
27
|
+
./.ai/batch/batch_runner.sh --phase 3
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
pwn batch
|
|
29
|
+
# Via pwn CLI
|
|
30
|
+
pwn batch run
|
|
31
|
+
pwn batch run --dry-run
|
|
32
|
+
pwn batch status
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## Batch Execution Protocol
|
|
34
36
|
|
|
35
37
|
### Phase 1: Task Selection
|
|
36
38
|
|
|
37
|
-
1. Read `tasks/
|
|
38
|
-
2.
|
|
39
|
-
3.
|
|
40
|
-
4.
|
|
41
|
-
5. Verify dependencies are met
|
|
42
|
-
6. Move to `active.md`
|
|
39
|
+
1. Read `tasks/prd.json` for stories
|
|
40
|
+
2. Find next story where `passes == false`
|
|
41
|
+
3. Verify all dependencies have `passes == true`
|
|
42
|
+
4. Apply phase filter if specified
|
|
43
43
|
|
|
44
44
|
### Phase 2: Execution
|
|
45
45
|
|
|
46
|
-
1.
|
|
47
|
-
2.
|
|
48
|
-
3.
|
|
49
|
-
4.
|
|
50
|
-
5. Keep commits atomic and descriptive
|
|
46
|
+
1. Build prompt from `.ai/batch/prompt.md` template
|
|
47
|
+
2. Substitute story placeholders ({STORY_ID}, {STORY_TITLE}, etc.)
|
|
48
|
+
3. Spawn: `claude --print --dangerously-skip-permissions -p "<prompt>"`
|
|
49
|
+
4. Capture output to `logs/` for debugging
|
|
51
50
|
|
|
52
51
|
### Phase 3: Quality Gates
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
Customize `run_quality_gates()` in `batch_runner.sh` for your stack:
|
|
55
54
|
|
|
55
|
+
**Node.js:**
|
|
56
56
|
```bash
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
[ ] npm run lint || eslint .
|
|
62
|
-
|
|
63
|
-
# Unit tests
|
|
64
|
-
[ ] npm run test || jest
|
|
65
|
-
|
|
66
|
-
# Integration tests (if applicable)
|
|
67
|
-
[ ] npm run test:integration
|
|
68
|
-
|
|
69
|
-
# Build verification
|
|
70
|
-
[ ] npm run build
|
|
57
|
+
npm test
|
|
58
|
+
npm run lint
|
|
59
|
+
npm run typecheck
|
|
60
|
+
```
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
**Python:**
|
|
63
|
+
```bash
|
|
64
|
+
pytest --tb=short
|
|
65
|
+
ruff check src/
|
|
66
|
+
mypy src/ --ignore-missing-imports
|
|
74
67
|
```
|
|
75
68
|
|
|
76
69
|
**Gate Strategy:**
|
|
77
70
|
- Fail fast on first error
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- Option to skip non-critical gates with `--force`
|
|
81
|
-
|
|
82
|
-
### Phase 4: Commit & Cleanup
|
|
83
|
-
|
|
84
|
-
1. Stage all changes: `git add .`
|
|
85
|
-
2. Commit with message format:
|
|
86
|
-
```
|
|
87
|
-
feat: [task-id] - [description]
|
|
88
|
-
|
|
89
|
-
- Specific change 1
|
|
90
|
-
- Specific change 2
|
|
91
|
-
|
|
92
|
-
Fixes: [task-id]
|
|
93
|
-
```
|
|
94
|
-
3. Push to remote: `git push -u origin feature/[task-id]-[slug]`
|
|
95
|
-
4. Create PR if configured
|
|
96
|
-
5. Update `active.md`: mark complete with date
|
|
71
|
+
- Retry up to 2x with error context fed back to Claude
|
|
72
|
+
- Circuit breaker after 3 consecutive failures
|
|
97
73
|
|
|
98
|
-
### Phase
|
|
74
|
+
### Phase 4: Completion
|
|
99
75
|
|
|
100
|
-
1.
|
|
101
|
-
2.
|
|
102
|
-
3.
|
|
103
|
-
4. Continue to next
|
|
76
|
+
1. Mark story as `passes: true` in prd.json
|
|
77
|
+
2. Append learnings to `progress.txt`
|
|
78
|
+
3. Commit prd.json + progress.txt update
|
|
79
|
+
4. Continue to next story
|
|
104
80
|
|
|
105
81
|
## Batch Configuration
|
|
106
82
|
|
|
@@ -77,10 +77,10 @@ pwn mode interactive
|
|
|
77
77
|
|
|
78
78
|
### Batch Mode
|
|
79
79
|
|
|
80
|
-
- Claude executes
|
|
81
|
-
-
|
|
80
|
+
- Claude executes stories autonomously from prd.json
|
|
81
|
+
- Spawns fresh sessions per task via batch_runner.sh
|
|
82
82
|
- Runs quality gates before committing
|
|
83
|
-
-
|
|
83
|
+
- Retries with error context, circuit breaker on failures
|
|
84
84
|
- Good for repetitive tasks and overnight execution
|
|
85
85
|
|
|
86
86
|
---
|
|
@@ -94,9 +94,10 @@ After executing the command, explain to the user:
|
|
|
94
94
|
3. **Next steps** - What they can do next
|
|
95
95
|
|
|
96
96
|
For batch mode, remind them:
|
|
97
|
-
- Add
|
|
98
|
-
- Run `pwn batch` to start
|
|
97
|
+
- Add stories to `.ai/tasks/prd.json`
|
|
98
|
+
- Run `pwn batch run` to start the batch runner
|
|
99
99
|
- Use `pwn batch status` to check progress
|
|
100
|
+
- Use `.ai/batch/prd_status.sh` for detailed PRD view
|
|
100
101
|
|
|
101
102
|
For interactive mode, remind them:
|
|
102
103
|
- Work proceeds conversationally
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"Notification": [
|
|
4
4
|
{
|
|
5
|
-
"matcher": "",
|
|
5
|
+
"matcher": "permission_prompt",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "pwn notify send \"Claude Code aguardando
|
|
9
|
+
"command": "pwn notify send \"Claude Code aguardando permissão\" --title \"PWN\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "idle_prompt",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "pwn notify send \"Claude Code aguardando input\" --title \"PWN\""
|
|
10
19
|
}
|
|
11
20
|
]
|
|
12
21
|
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Backlog
|
|
2
|
-
|
|
3
|
-
This file contains prioritized future tasks not yet in active work.
|
|
4
|
-
|
|
5
|
-
## Format
|
|
6
|
-
|
|
7
|
-
Tasks are listed in priority order (highest to lowest):
|
|
8
|
-
|
|
9
|
-
```markdown
|
|
10
|
-
### US-XXX: Task Title
|
|
11
|
-
**Type:** Story | Bug | DevTask
|
|
12
|
-
**Priority:** High | Medium | Low
|
|
13
|
-
**Effort:** (T-shirt size: XS, S, M, L, XL)
|
|
14
|
-
**Description:** What needs to be done
|
|
15
|
-
**Acceptance Criteria:**
|
|
16
|
-
- [ ] Criterion 1
|
|
17
|
-
- [ ] Criterion 2
|
|
18
|
-
**Dependencies:** (Other tasks needed first)
|
|
19
|
-
**Notes:** Context, caveats, research needed
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Moving to Active
|
|
25
|
-
|
|
26
|
-
When starting a backlog task:
|
|
27
|
-
|
|
28
|
-
1. Create issue/ticket in your tracking system
|
|
29
|
-
2. Move to `active.md` with checkbox
|
|
30
|
-
3. Assign team member
|
|
31
|
-
4. Update priority based on sprint plan
|
|
32
|
-
5. Reference in commit messages
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Adding New Tasks
|
|
37
|
-
|
|
38
|
-
When new work is identified:
|
|
39
|
-
|
|
40
|
-
1. Assign next ID (US-XXX, BUG-XXX, etc.)
|
|
41
|
-
2. Add to appropriate section
|
|
42
|
-
3. Fill in all fields
|
|
43
|
-
4. Don't assign yet - wait for planning
|
|
44
|
-
5. Commit with message: `docs: add [ID] to backlog`
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Backlog Sections
|
|
49
|
-
|
|
50
|
-
### High Priority
|
|
51
|
-
Work that should start soon. Review weekly.
|
|
52
|
-
|
|
53
|
-
### Medium Priority
|
|
54
|
-
Important but can wait. Review biweekly.
|
|
55
|
-
|
|
56
|
-
### Low Priority
|
|
57
|
-
Nice-to-have improvements. Review monthly.
|
|
58
|
-
|
|
59
|
-
### Roadmap (Future Phases)
|
|
60
|
-
Longer-term projects for future planning.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Template for New Backlog Item
|
|
65
|
-
|
|
66
|
-
```markdown
|
|
67
|
-
### US-XXX: [Title]
|
|
68
|
-
**Type:** Story | Bug | DevTask
|
|
69
|
-
**Priority:** High | Medium | Low
|
|
70
|
-
**Effort:** S/M/L
|
|
71
|
-
**Description:** (What and why)
|
|
72
|
-
**Acceptance Criteria:**
|
|
73
|
-
- [ ] (Specific, measurable outcome)
|
|
74
|
-
**Dependencies:** (Other tasks)
|
|
75
|
-
**Notes:** (Research, concerns, constraints)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Management Guidelines
|
|
81
|
-
|
|
82
|
-
- Groom backlog every sprint (remove duplicates, clarify)
|
|
83
|
-
- Estimate effort using relative sizing (XS, S, M, L, XL)
|
|
84
|
-
- Link related tasks together
|
|
85
|
-
- Archive completed items with date + time spent
|
|
86
|
-
- Keep description brief - link to external tickets for details
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Cleanup
|
|
91
|
-
|
|
92
|
-
- Remove or update duplicates
|
|
93
|
-
- Close blocked items after 30 days of no progress
|
|
94
|
-
- Archive completed items to `completed/` section
|
|
95
|
-
- Re-estimate if context changes significantly
|