@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 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 # Execute next task
56
- pwn batch --count 5 # Execute 5 tasks
57
- pwn batch status # Show progress
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
- │ │ └── backlog.md # Future work
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 tasks autonomously:
145
+ Execute stories autonomously via `batch_runner.sh`:
146
146
 
147
147
  ```bash
148
- pwn batch --count 5 --priority high
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
- - Quality gates (lint, test, typecheck)
153
- - Checkpoint-based pause/resume
154
- - Auto-commit after each task
155
- - Notifications on completion
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(` Backlog: ${status.tasks.backlogTotal} total`);
113
- if (status.tasks.backlogTotal > 0) {
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 backlog');
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 backlog');
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 autonomously');
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 --count 5 Execute 5 tasks');
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, backlog)');
64
+ console.log(' ├── tasks/ (active work, prd.json)');
65
65
  console.log(' ├── patterns/ (auto-applied patterns)');
66
- console.log(' ├── workflows/ (batch execution)');
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', 'backlog.md');
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 backlog.md`
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 backlog.');
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 backlog.');
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(` Backlog: ${info.tasks.backlog.total} items`);
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/backlog.md',
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
- // Only mention backup if content is actually different (not empty)
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 only if it has custom content (different from template)
175
- if (existsSync(claudeMdPath) && currentContent && currentContent !== templateContent) {
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, backlog)');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fermindi/pwn-cli",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Professional AI Workspace - Inject structured memory and automation into any project for AI-powered development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 (only if different) and copy PWN template to root
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 AND different from template
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
- // Only backup if content is different from PWN template
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
 
@@ -24,7 +24,13 @@ const REQUIRED_STRUCTURE = {
24
24
  ],
25
25
  taskFiles: [
26
26
  'tasks/active.md',
27
- 'tasks/backlog.md'
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
  ];
@@ -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 backlogPath = join(cwd, '.ai', 'tasks', 'backlog.md');
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 backlog.md
67
- if (existsSync(backlogPath)) {
68
- const content = readFileSync(backlogPath, 'utf8');
69
- const lines = content.split('\n');
70
-
71
- for (const line of lines) {
72
- if (line.match(/^- \[[ ]\]/) || line.match(/^\d+\./)) {
73
- summary.backlog.total++;
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 tasks from backlog.md
128
+ * Parse stories from prd.json
129
129
  * @param {string} cwd - Working directory
130
- * @returns {Array<object>} Array of tasks
130
+ * @returns {Array<object>} Array of stories
131
131
  */
132
- export function parseBacklogTasks(cwd = process.cwd()) {
133
- const backlogPath = join(cwd, '.ai', 'tasks', 'backlog.md');
134
-
135
- if (!existsSync(backlogPath)) {
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
- const content = readFileSync(backlogPath, 'utf8');
140
- return parseBacklogFromMarkdown(content);
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
- * Select next task to execute based on strategy
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 backlog
279
- let backlogTasks = parseBacklogTasks(cwd);
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
- // Filter by priority if specified
282
- if (priorityFilter) {
283
- backlogTasks = backlogTasks.filter(t => t.priority === priorityFilter);
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
- // Sort based on strategy
294
- switch (strategy) {
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
- case 'priority':
306
- default:
307
- // Sort by priority (highest first), then by position
308
- const priorityOrder = { 'high': 1, 'medium': 2, 'low': 3 };
309
- backlogTasks.sort((a, b) => {
310
- const aPriority = priorityOrder[a.priority] || 2;
311
- const bPriority = priorityOrder[b.priority] || 2;
312
- if (aPriority !== bPriority) return aPriority - bPriority;
313
- return a.line - b.line;
314
- });
315
- break;
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 backlogTasks = parseBacklogTasks(cwd);
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
- backlogTotal: backlogTasks.length,
615
- backlogHigh: backlogTasks.filter(t => t.priority === 'high').length,
616
- backlogMedium: backlogTasks.filter(t => t.priority === 'medium').length,
617
- backlogLow: backlogTasks.filter(t => t.priority === 'low').length
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/backlog.md`** (if needed)
75
- - Future work not yet started
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 highest priority unblocked item
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
- When `session_mode` is "batch" (autonomous task execution):
271
+ Autonomous task execution via `.ai/batch/batch_runner.sh`.
272
272
 
273
- ### Your Role
274
- - Execute tasks from backlog autonomously
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
- ```json
328
- {
329
- "batch_config": {
330
- "max_tasks": 5,
331
- "max_duration_hours": 4,
332
- "quality_gates": ["typecheck", "lint", "test"],
333
- "skip_gates": [],
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
- **Important:** Batch mode is conservative:
344
- - Don't skip quality gates without explicit config
345
- - Ask user if uncertain about task interpretation
346
- - Stop on first blocker and notify user
347
- - Don't force push or destructive operations
348
- - Log all decisions and timing
349
-
350
- ### Batch Error Handling
351
-
352
- **Build/Test Fails:**
353
- - Show error with full context
354
- - If pattern known: auto-fix
355
- - If unsure: pause and notify user
356
- - Don't proceed without resolution
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/backlog.md` - Future work
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 backlog.md for upcoming work
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 allows AI agents to autonomously:
8
- - Select tasks from backlog
9
- - Execute work in sequence
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
- - Commit changes following conventions
12
- - Continue until backlog depleted or threshold reached
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
- # Execute next available task
18
- pwn batch
17
+ # Preview next task
18
+ ./.ai/batch/batch_runner.sh --dry-run
19
19
 
20
- # Execute specific number of tasks
21
- pwn batch --count 5
20
+ # Run batch (default 20 iterations)
21
+ ./.ai/batch/batch_runner.sh
22
22
 
23
- # Dry-run: show what would execute
24
- pwn batch --dry-run
23
+ # Custom iteration limit
24
+ ./.ai/batch/batch_runner.sh 50
25
25
 
26
- # Execute with specific priority
27
- pwn batch --priority high
26
+ # Specific phase only
27
+ ./.ai/batch/batch_runner.sh --phase 3
28
28
 
29
- # Resume interrupted batch
30
- pwn batch --resume
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/active.md` for incomplete tasks
38
- 2. If active tasks exist and not blocked, resume
39
- 3. Otherwise, read `tasks/backlog.md`
40
- 4. Select highest priority unblocked task
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. Analyze task requirements
47
- 2. Create feature branch: `feature/[task-id]-[slug]`
48
- 3. Execute work following patterns in `patterns/index.md`
49
- 4. Test incrementally during development
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
- Before commit, verify:
53
+ Customize `run_quality_gates()` in `batch_runner.sh` for your stack:
55
54
 
55
+ **Node.js:**
56
56
  ```bash
57
- # Type checking
58
- [ ] npm run typecheck || tsc --noEmit
59
-
60
- # Linting
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
- # Security scan (if configured)
73
- [ ] npm run security
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
- - Output clear error message
79
- - Suggest fix if known
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 5: Completion
74
+ ### Phase 4: Completion
99
75
 
100
- 1. Verify commit is on remote
101
- 2. Delete local feature branch
102
- 3. Report completion to user
103
- 4. Continue to next task or stop
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 tasks autonomously from backlog
81
- - Creates feature branches per task
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
- - Continues until backlog empty or limits reached
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 tasks to `.ai/tasks/backlog.md`
98
- - Run `pwn batch` to start executing tasks
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 permissao\" --title \"PWN\""
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