@fermindi/pwn-cli 0.5.0 → 0.7.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.
@@ -0,0 +1,394 @@
1
+ /**
2
+ * PWN Backlog Viewer — Interactive TUI
3
+ *
4
+ * Navigable terminal viewer for prd.json stories.
5
+ * Uses raw readline + ANSI escape codes (no extra deps).
6
+ */
7
+
8
+ import readline from 'readline';
9
+ import chalk from 'chalk';
10
+
11
+ /**
12
+ * Launch the interactive backlog viewer.
13
+ * @param {object} options
14
+ * @param {string} options.project - Project name
15
+ * @param {Array<object>} options.stories - Stories from prd.json
16
+ * @param {Array<object>} options.taskFiles - Task files from .ai/batch/tasks/
17
+ * @returns {Promise<void>} Resolves when user quits
18
+ */
19
+ export function startViewer({ project, stories, taskFiles }) {
20
+ return new Promise((resolve) => {
21
+ if (stories.length === 0) {
22
+ console.log(chalk.yellow('No stories found in prd.json'));
23
+ resolve();
24
+ return;
25
+ }
26
+
27
+ let selectedIndex = 0;
28
+ let detailMode = false; // true = full-screen detail view
29
+ let detailScroll = 0; // scroll offset inside detail view
30
+ const taskMap = new Map(taskFiles.map(t => [t.id, t]));
31
+
32
+ function getStatus(story) {
33
+ if (story.passes) return 'done';
34
+ const tf = taskMap.get(story.id);
35
+ if (tf) {
36
+ if (tf.status === 'completed') return 'done';
37
+ if (tf.status === 'planned' || tf.status === 'running') return 'active';
38
+ }
39
+ return 'pending';
40
+ }
41
+
42
+ function statusIcon(status) {
43
+ if (status === 'done') return chalk.green('✅');
44
+ if (status === 'active') return chalk.yellow('🔄');
45
+ return chalk.dim('⏳');
46
+ }
47
+
48
+ function statusLabel(status) {
49
+ if (status === 'done') return chalk.green('done');
50
+ if (status === 'active') return chalk.yellow('active');
51
+ return chalk.dim('pending');
52
+ }
53
+
54
+ function truncate(str, maxLen) {
55
+ if (!str) return '';
56
+ if (str.length <= maxLen) return str;
57
+ return str.slice(0, maxLen - 1) + '…';
58
+ }
59
+
60
+ function buildDetailLines(story, cols) {
61
+ const status = getStatus(story);
62
+ const contentLines = [];
63
+
64
+ contentLines.push(chalk.bold(` ${story.id} · ${story.title}`));
65
+ contentLines.push('');
66
+
67
+ const phase = story.phase || '—';
68
+ const effort = story.effort || '—';
69
+ const deps = story.dependencies?.length > 0 ? story.dependencies.join(', ') : 'none';
70
+ contentLines.push(` Phase: ${phase} | Effort: ${effort} | Status: ${statusLabel(status)} | Deps: ${deps}`);
71
+ contentLines.push('');
72
+
73
+ if (story.acceptance_criteria?.length > 0) {
74
+ contentLines.push(chalk.bold(' Acceptance Criteria:'));
75
+ for (const ac of story.acceptance_criteria) {
76
+ const check = status === 'done' ? chalk.green('✓') : chalk.dim('○');
77
+ contentLines.push(` ${check} ${truncate(ac, cols - 6)}`);
78
+ }
79
+ contentLines.push('');
80
+ }
81
+
82
+ if (story.notes) {
83
+ contentLines.push(chalk.bold(' Notes:'));
84
+ // Word-wrap notes to terminal width
85
+ const words = story.notes.split(' ');
86
+ let line = ' ';
87
+ for (const word of words) {
88
+ if (line.length + word.length + 1 > cols - 2) {
89
+ contentLines.push(chalk.dim(line));
90
+ line = ' ' + word;
91
+ } else {
92
+ line += (line.length > 1 ? ' ' : '') + word;
93
+ }
94
+ }
95
+ if (line.length > 1) contentLines.push(chalk.dim(line));
96
+ contentLines.push('');
97
+ }
98
+
99
+ // Show task file info if available
100
+ const tf = taskMap.get(story.id);
101
+ if (tf) {
102
+ contentLines.push(chalk.bold(' Batch Task Info:'));
103
+ if (tf.complexity) contentLines.push(` Complexity: ${tf.complexity}`);
104
+ if (tf.estimated_time_seconds) contentLines.push(` Estimated: ~${tf.estimated_time_seconds}s`);
105
+ if (tf.plan?.length > 0 && tf.plan[0] !== 'fallback - no plan available') {
106
+ contentLines.push(chalk.bold(' Plan:'));
107
+ for (const step of tf.plan) {
108
+ contentLines.push(` · ${truncate(step, cols - 8)}`);
109
+ }
110
+ }
111
+ if (tf.failure_reason) {
112
+ contentLines.push(chalk.red(` Failed: ${tf.failure_reason}`));
113
+ }
114
+ contentLines.push('');
115
+ }
116
+
117
+ return contentLines;
118
+ }
119
+
120
+ function renderDetailView() {
121
+ const rows = process.stdout.rows || 24;
122
+ const cols = process.stdout.columns || 80;
123
+ const story = stories[selectedIndex];
124
+
125
+ process.stdout.write('\x1b[H\x1b[2J');
126
+
127
+ const lines = [];
128
+
129
+ // Header
130
+ const header = `📋 ${story.id} — Detail View`;
131
+ const pos = `${selectedIndex + 1}/${stories.length}`;
132
+ const headerLine = `${header}${' '.repeat(Math.max(1, cols - header.length - pos.length - 2))}${pos}`;
133
+ lines.push(chalk.bold(headerLine));
134
+ lines.push(chalk.dim('─'.repeat(cols)));
135
+
136
+ // Build full content and apply scroll
137
+ const contentLines = buildDetailLines(story, cols);
138
+ const maxContent = rows - 4; // header(2) + footer(2)
139
+ const maxScroll = Math.max(0, contentLines.length - maxContent);
140
+ if (detailScroll > maxScroll) detailScroll = maxScroll;
141
+
142
+ const visible = contentLines.slice(detailScroll, detailScroll + maxContent);
143
+ for (const line of visible) {
144
+ lines.push(line);
145
+ }
146
+
147
+ // Scroll indicator
148
+ const scrollInfo = contentLines.length > maxContent
149
+ ? ` (${detailScroll + 1}-${Math.min(detailScroll + maxContent, contentLines.length)}/${contentLines.length})`
150
+ : '';
151
+
152
+ // Pad
153
+ while (lines.length < rows - 2) {
154
+ lines.push('');
155
+ }
156
+
157
+ lines.push(chalk.dim('─'.repeat(cols)));
158
+ lines.push(chalk.dim(` ↑/k ↓/j scroll ←/→ prev/next story Esc/Backspace back to list q quit${scrollInfo}`));
159
+
160
+ process.stdout.write(lines.join('\n'));
161
+ }
162
+
163
+ function render() {
164
+ if (detailMode) {
165
+ renderDetailView();
166
+ return;
167
+ }
168
+
169
+ const rows = process.stdout.rows || 24;
170
+ const cols = process.stdout.columns || 80;
171
+ const doneCount = stories.filter(s => getStatus(s) === 'done').length;
172
+
173
+ // Move cursor to top-left and clear screen
174
+ process.stdout.write('\x1b[H\x1b[2J');
175
+
176
+ const lines = [];
177
+
178
+ // Header
179
+ const header = `📋 Product Backlog — ${project}`;
180
+ const progress = `${doneCount}/${stories.length} done`;
181
+ const headerLine = `${header}${' '.repeat(Math.max(1, cols - header.length - progress.length - 2))}${progress}`;
182
+ lines.push(chalk.bold(headerLine));
183
+ lines.push(chalk.dim('─'.repeat(cols)));
184
+
185
+ // Story list — compute how many rows we can show
186
+ const detailReserved = 12; // lines reserved for detail panel + footer
187
+ const maxListRows = Math.max(3, rows - lines.length - detailReserved);
188
+
189
+ // Scrolling window
190
+ let scrollOffset = 0;
191
+ if (selectedIndex >= scrollOffset + maxListRows) {
192
+ scrollOffset = selectedIndex - maxListRows + 1;
193
+ }
194
+ if (selectedIndex < scrollOffset) {
195
+ scrollOffset = selectedIndex;
196
+ }
197
+
198
+ const visibleStories = stories.slice(scrollOffset, scrollOffset + maxListRows);
199
+
200
+ for (let i = 0; i < visibleStories.length; i++) {
201
+ const story = visibleStories[i];
202
+ const idx = scrollOffset + i;
203
+ const status = getStatus(story);
204
+ const icon = statusIcon(status);
205
+ const pointer = idx === selectedIndex ? chalk.cyan('▸') : ' ';
206
+ const id = chalk.bold(story.id);
207
+ const title = truncate(story.title, cols - story.id.length - 12);
208
+ const line = ` ${pointer} ${id} ${title} ${icon}`;
209
+ lines.push(idx === selectedIndex ? chalk.cyan(line) : line);
210
+ }
211
+
212
+ // Separator
213
+ lines.push(chalk.dim('─'.repeat(cols)));
214
+
215
+ // Detail panel for selected story
216
+ const story = stories[selectedIndex];
217
+ const status = getStatus(story);
218
+
219
+ lines.push(chalk.bold(` ${story.id} · ${story.title}`));
220
+
221
+ const phase = story.phase || '—';
222
+ const effort = story.effort || '—';
223
+ const deps = story.dependencies?.length > 0 ? story.dependencies.join(', ') : 'none';
224
+ lines.push(` Phase: ${phase} | Effort: ${effort} | Status: ${statusLabel(status)} | Deps: ${deps}`);
225
+ lines.push('');
226
+
227
+ // Acceptance criteria
228
+ if (story.acceptance_criteria?.length > 0) {
229
+ lines.push(chalk.bold(' Acceptance Criteria:'));
230
+ const remainingRows = rows - lines.length - 3; // leave room for notes + footer
231
+ const maxAC = Math.max(1, remainingRows - 2);
232
+ const acs = story.acceptance_criteria.slice(0, maxAC);
233
+ for (const ac of acs) {
234
+ const check = status === 'done' ? chalk.green('✓') : chalk.dim('○');
235
+ lines.push(` ${check} ${truncate(ac, cols - 6)}`);
236
+ }
237
+ if (story.acceptance_criteria.length > maxAC) {
238
+ lines.push(chalk.dim(` ... +${story.acceptance_criteria.length - maxAC} more — press Enter for full view`));
239
+ }
240
+ }
241
+
242
+ // Notes
243
+ if (story.notes) {
244
+ lines.push('');
245
+ lines.push(chalk.dim(` Notes: ${truncate(story.notes, cols - 10)}`));
246
+ }
247
+
248
+ // Pad to fill screen
249
+ while (lines.length < rows - 2) {
250
+ lines.push('');
251
+ }
252
+
253
+ // Footer separator + keybindings
254
+ lines.push(chalk.dim('─'.repeat(cols)));
255
+ lines.push(chalk.dim(' ↑/k up ↓/j down Enter detail Home/End first/last q quit'));
256
+
257
+ process.stdout.write(lines.join('\n'));
258
+ }
259
+
260
+ // Enter alternate screen buffer and hide cursor
261
+ process.stdout.write('\x1b[?1049h\x1b[?25l');
262
+
263
+ // Enable raw mode for keypress detection
264
+ readline.emitKeypressEvents(process.stdin);
265
+ if (process.stdin.isTTY) {
266
+ process.stdin.setRawMode(true);
267
+ }
268
+ process.stdin.resume();
269
+
270
+ render();
271
+
272
+ function cleanup() {
273
+ // Restore screen buffer and show cursor
274
+ process.stdout.write('\x1b[?25h\x1b[?1049l');
275
+ if (process.stdin.isTTY) {
276
+ process.stdin.setRawMode(false);
277
+ }
278
+ process.stdin.pause();
279
+ process.stdin.removeListener('keypress', onKeypress);
280
+ resolve();
281
+ }
282
+
283
+ function onKeypress(_str, key) {
284
+ if (!key) return;
285
+
286
+ if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
287
+ cleanup();
288
+ return;
289
+ }
290
+
291
+ if (detailMode) {
292
+ // Detail view keybindings
293
+ if (key.name === 'escape' || key.name === 'backspace') {
294
+ detailMode = false;
295
+ detailScroll = 0;
296
+ render();
297
+ } else if (key.name === 'up' || key.name === 'k') {
298
+ if (detailScroll > 0) {
299
+ detailScroll--;
300
+ render();
301
+ }
302
+ } else if (key.name === 'down' || key.name === 'j') {
303
+ detailScroll++;
304
+ render();
305
+ } else if (key.name === 'left') {
306
+ if (selectedIndex > 0) {
307
+ selectedIndex--;
308
+ detailScroll = 0;
309
+ render();
310
+ }
311
+ } else if (key.name === 'right') {
312
+ if (selectedIndex < stories.length - 1) {
313
+ selectedIndex++;
314
+ detailScroll = 0;
315
+ render();
316
+ }
317
+ } else if (key.name === 'home') {
318
+ detailScroll = 0;
319
+ render();
320
+ } else if (key.name === 'end') {
321
+ detailScroll = 9999; // clamped in renderDetailView
322
+ render();
323
+ }
324
+ return;
325
+ }
326
+
327
+ // List view keybindings
328
+ if (key.name === 'return') {
329
+ detailMode = true;
330
+ detailScroll = 0;
331
+ render();
332
+ } else if (key.name === 'up' || key.name === 'k') {
333
+ if (selectedIndex > 0) {
334
+ selectedIndex--;
335
+ render();
336
+ }
337
+ } else if (key.name === 'down' || key.name === 'j') {
338
+ if (selectedIndex < stories.length - 1) {
339
+ selectedIndex++;
340
+ render();
341
+ }
342
+ } else if (key.name === 'home') {
343
+ selectedIndex = 0;
344
+ render();
345
+ } else if (key.name === 'end') {
346
+ selectedIndex = stories.length - 1;
347
+ render();
348
+ }
349
+ }
350
+
351
+ process.stdin.on('keypress', onKeypress);
352
+
353
+ // Handle terminal resize
354
+ process.stdout.on('resize', render);
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Print backlog as plain text (non-interactive mode).
360
+ * @param {object} options
361
+ * @param {string} options.project - Project name
362
+ * @param {Array<object>} options.stories - Stories from prd.json
363
+ * @param {Array<object>} options.taskFiles - Task files from .ai/batch/tasks/
364
+ */
365
+ export function printPlain({ project, stories, taskFiles }) {
366
+ if (stories.length === 0) {
367
+ console.log('No stories found in prd.json');
368
+ return;
369
+ }
370
+
371
+ const taskMap = new Map(taskFiles.map(t => [t.id, t]));
372
+
373
+ function getStatus(story) {
374
+ if (story.passes) return 'done';
375
+ const tf = taskMap.get(story.id);
376
+ if (tf) {
377
+ if (tf.status === 'completed') return 'done';
378
+ if (tf.status === 'planned' || tf.status === 'running') return 'active';
379
+ }
380
+ return 'pending';
381
+ }
382
+
383
+ const doneCount = stories.filter(s => getStatus(s) === 'done').length;
384
+
385
+ console.log(`\nProduct Backlog — ${project} (${doneCount}/${stories.length} done)\n`);
386
+
387
+ for (const story of stories) {
388
+ const status = getStatus(story);
389
+ const icon = status === 'done' ? '[x]' : status === 'active' ? '[~]' : '[ ]';
390
+ console.log(` ${icon} ${story.id} ${story.title}`);
391
+ }
392
+
393
+ console.log('');
394
+ }
@@ -38,9 +38,9 @@ Read these files in order:
38
38
  - Ask user before deviating from active decisions
39
39
 
40
40
  2. **`/.ai/memory/patterns.md`**
41
- - Index of patterns documented in `.ai/patterns/`
42
- - Check which patterns exist for this project
43
- - Add index entry when creating new pattern files
41
+ - Codebase patterns learned by team
42
+ - Apply these patterns to new work
43
+ - Add new patterns when discovered
44
44
 
45
45
  3. **`/.ai/memory/deadends.md`**
46
46
  - Failed approaches documented (DE-XXX)
@@ -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
@@ -208,42 +208,6 @@ If any gate fails:
208
208
  - Ask user if unsure how to proceed
209
209
  - Don't commit until gates pass
210
210
 
211
- ### Pattern Checkpoint (Mandatory Before Commit)
212
-
213
- Before every commit, review the work done and check if any pattern should be documented.
214
-
215
- **Document a pattern when ANY of these criteria are met:**
216
-
217
- 1. **User corrected your approach** - The user told you to do something differently. Document the correct approach so future sessions don't repeat the mistake.
218
- 2. **You chose between alternatives** - You evaluated 2+ options and picked one. Document what was chosen and why.
219
- 3. **Workaround for limitation** - A library, framework, or tool has a non-obvious limitation. Document the workaround.
220
- 4. **Convention established** - First time implementing a certain type of thing in this codebase (first API route, first test, first component). Document the convention for consistency.
221
- 5. **Repeated approach** - You used the same approach 2+ times in this session. Document it to avoid re-deriving it.
222
- 6. **Non-trivial solution** - The solution wasn't obvious or took investigation to find. Document it so it's immediately available next time.
223
-
224
- **If a criterion is met:**
225
-
226
- 1. Create pattern file: `.ai/patterns/{category}/{pattern-name}.md`
227
- ```markdown
228
- # Pattern Name
229
-
230
- ## Context
231
- When to apply this pattern.
232
-
233
- ## Pattern
234
- Description or code example.
235
-
236
- ## Rationale
237
- Why this approach works better.
238
-
239
- ## Examples
240
- - `src/path/file.ts:42`
241
- ```
242
- 2. Add index entry to `.ai/memory/patterns.md`
243
- 3. Commit pattern files together with the code change
244
-
245
- **If no criteria are met:** proceed to commit normally. Don't force patterns that aren't there.
246
-
247
211
  ### Commit Message Format
248
212
 
249
213
  Use conventional commits:
@@ -281,9 +245,15 @@ When completing work:
281
245
  - [x] US-042: My completed task (YYYY-MM-DD)
282
246
  ```
283
247
 
284
- 2. Document patterns following the **Pattern Checkpoint** criteria above
285
- - Create file in `.ai/patterns/{category}/{name}.md`
286
- - Add index entry in `.ai/memory/patterns.md`
248
+ 2. Update `/.ai/memory/patterns.md` if new pattern discovered
249
+ ```
250
+ ## New Pattern Category
251
+
252
+ ### Pattern Name
253
+ **Context:** When to use
254
+ **Pattern:** Description
255
+ **Rationale:** Why it's better
256
+ ```
287
257
 
288
258
  3. Add to `/.ai/memory/deadends.md` if approach failed
289
259
  ```
@@ -298,110 +268,41 @@ When completing work:
298
268
 
299
269
  ## Batch Mode
300
270
 
301
- When `session_mode` is "batch" (autonomous task execution):
271
+ Autonomous task execution via `.ai/batch/batch_runner.sh`.
302
272
 
303
- ### Your Role
304
- - Execute tasks from backlog autonomously
305
- - Follow quality gates strictly
306
- - Make conservative decisions (ask user when uncertain)
307
- - Commit and push changes automatically
308
- - Continue until backlog depleted or limits reached
273
+ The runner reads stories from `.ai/tasks/prd.json`, spawns fresh Claude sessions
274
+ per task, runs quality gates, and tracks progress.
309
275
 
310
- ### Batch Workflow
311
-
312
- ```
313
- 1. Read tasks/active.md
314
- ├─ If incomplete tasks: resume work
315
- └─ If no active tasks: read backlog.md
316
-
317
- 2. Select highest-priority unblocked task
318
- ├─ Check dependencies
319
- ├─ Verify not blocked
320
- └─ Move to tasks/active.md
321
-
322
- 3. Create feature branch
323
- └─ Name: feature/{task-id}-{slug}
324
-
325
- 4. Execute work following patterns
326
- ├─ Auto-apply triggers from patterns/index.md
327
- ├─ Keep commits atomic and descriptive
328
- └─ Test incrementally
329
-
330
- 5. Quality gates must pass
331
- ├─ If gate fails:
332
- │ ├─ Attempt auto-fix if pattern known
333
- │ ├─ Pause batch if can't fix
334
- │ └─ Notify user
335
- └─ If all pass: proceed to commit
336
-
337
- 6. Commit and push
338
- ├─ Stage all changes
339
- ├─ Commit with conventional format
340
- ├─ Push to origin
341
- └─ Create PR if configured
342
-
343
- 7. Clean up and continue
344
- ├─ Delete local feature branch
345
- ├─ Update tasks/active.md
346
- ├─ Check if more tasks to execute
347
- └─ Continue or stop
348
-
349
- 8. Completion report
350
- └─ Show summary: tasks done, time spent, any issues
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
351
282
  ```
352
283
 
353
- ### Batch Configuration
354
-
355
- Read from `/.ai/state.json`:
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 |
356
291
 
357
- ```json
358
- {
359
- "batch_config": {
360
- "max_tasks": 5,
361
- "max_duration_hours": 4,
362
- "quality_gates": ["typecheck", "lint", "test"],
363
- "skip_gates": [],
364
- "auto_commit": true,
365
- "auto_push": false,
366
- "create_pr": false,
367
- "branch_format": "feature/{id}-{slug}",
368
- "commit_format": "conventional"
369
- }
370
- }
371
- ```
372
-
373
- **Important:** Batch mode is conservative:
374
- - Don't skip quality gates without explicit config
375
- - Ask user if uncertain about task interpretation
376
- - Stop on first blocker and notify user
377
- - Don't force push or destructive operations
378
- - Log all decisions and timing
379
-
380
- ### Batch Error Handling
381
-
382
- **Build/Test Fails:**
383
- - Show error with full context
384
- - If pattern known: auto-fix
385
- - If unsure: pause and notify user
386
- - Don't proceed without resolution
387
-
388
- **Git Conflicts:**
389
- - Stop immediately
390
- - Notify user of conflict
391
- - Require manual resolution
392
- - Resume after conflict resolved
393
-
394
- **Missing Dependencies:**
395
- - Check if dependency task exists
396
- - Add to batch queue if available
397
- - Execute dependency first
398
- - Resume original task
399
-
400
- **Resource Limits Reached:**
401
- - Stop execution gracefully
402
- - Report progress
403
- - Create clean state for next batch
404
- - 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
405
306
 
406
307
  ---
407
308
 
@@ -430,7 +331,7 @@ Reference these files in this priority order:
430
331
  - Project changelog or git history
431
332
 
432
333
  5. **Fifth Priority: Planning**
433
- - `/.ai/tasks/backlog.md` - Future work
334
+ - `/.ai/tasks/prd.json` - Future work (structured)
434
335
  - Roadmap or project documentation
435
336
  - Upstream issues or features
436
337
 
@@ -620,7 +521,7 @@ When finishing a session:
620
521
  - Add completion date if done
621
522
 
622
523
  3. **Document learnings**
623
- - Document patterns in `.ai/patterns/` and index in `memory/patterns.md`
524
+ - Update `/.ai/memory/patterns.md` if discovered pattern
624
525
  - Update `/.ai/memory/deadends.md` if failed approach
625
526
  - Reference in commit messages
626
527
 
File without changes