@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.
- package/README.md +14 -10
- package/cli/backlog.js +60 -0
- package/cli/batch.js +112 -12
- package/cli/index.js +9 -31
- package/cli/inject.js +8 -32
- package/cli/status.js +1 -1
- package/cli/update.js +78 -27
- package/package.json +6 -3
- package/src/core/inject.js +41 -45
- package/src/core/state.js +0 -1
- package/src/core/validate.js +14 -1
- package/src/core/workspace.js +13 -11
- package/src/index.js +0 -1
- package/src/services/batch-runner.js +769 -0
- package/src/services/batch-service.js +185 -74
- package/src/ui/backlog-viewer.js +394 -0
- package/templates/workspace/.ai/agents/claude.md +47 -146
- package/templates/workspace/.ai/batch/tasks/.gitkeep +0 -0
- package/templates/workspace/.ai/memory/patterns.md +57 -11
- package/templates/workspace/.ai/tasks/active.md +1 -1
- package/templates/workspace/.ai/workflows/batch-task.md +43 -67
- package/templates/workspace/.claude/commands/save.md +0 -42
- package/cli/codespaces.js +0 -303
- package/cli/migrate.js +0 -466
- package/cli/mode.js +0 -206
- package/cli/notify.js +0 -135
- package/src/services/notification-service.js +0 -342
- package/templates/codespaces/devcontainer.json +0 -52
- package/templates/codespaces/setup.sh +0 -70
- package/templates/workspace/.ai/config/notifications.template.json +0 -20
- package/templates/workspace/.ai/tasks/backlog.md +0 -95
- package/templates/workspace/.claude/commands/mode.md +0 -103
- package/templates/workspace/.claude/settings.json +0 -15
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/backlog.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { hasWorkspace } from '../src/core/state.js';
|
|
3
|
+
import { parsePrdTasks } from '../src/services/batch-service.js';
|
|
4
|
+
import { listTaskFiles } from '../src/services/batch-runner.js';
|
|
5
|
+
import { startViewer, printPlain } from '../src/ui/backlog-viewer.js';
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
export default async function backlogCommand(args = []) {
|
|
10
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
11
|
+
showHelp();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!hasWorkspace()) {
|
|
16
|
+
console.log('❌ No PWN workspace found\n');
|
|
17
|
+
console.log(' Run: pwn inject');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const stories = parsePrdTasks(cwd);
|
|
23
|
+
const taskFiles = listTaskFiles(cwd);
|
|
24
|
+
|
|
25
|
+
// Read project name from prd.json
|
|
26
|
+
let project = 'project';
|
|
27
|
+
const prdPath = join(cwd, '.ai', 'tasks', 'prd.json');
|
|
28
|
+
if (existsSync(prdPath)) {
|
|
29
|
+
try {
|
|
30
|
+
const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
|
|
31
|
+
project = prd.project || project;
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const noInteractive = args.includes('--no-interactive') || !process.stdout.isTTY;
|
|
36
|
+
|
|
37
|
+
if (noInteractive) {
|
|
38
|
+
printPlain({ project, stories, taskFiles });
|
|
39
|
+
} else {
|
|
40
|
+
await startViewer({ project, stories, taskFiles });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function showHelp() {
|
|
45
|
+
console.log('📋 PWN Backlog Viewer\n');
|
|
46
|
+
console.log('Usage: pwn backlog [options]\n');
|
|
47
|
+
console.log('Options:');
|
|
48
|
+
console.log(' --no-interactive Plain text output (for CI/piping)');
|
|
49
|
+
console.log(' --help, -h Show this help\n');
|
|
50
|
+
console.log('Keybindings (list view):');
|
|
51
|
+
console.log(' ↑/k Move up');
|
|
52
|
+
console.log(' ↓/j Move down');
|
|
53
|
+
console.log(' Enter Open full detail view');
|
|
54
|
+
console.log(' Home/End First/last story');
|
|
55
|
+
console.log(' q/Ctrl+C Quit\n');
|
|
56
|
+
console.log('Keybindings (detail view):');
|
|
57
|
+
console.log(' ↑/k ↓/j Scroll content');
|
|
58
|
+
console.log(' ←/→ Previous/next story');
|
|
59
|
+
console.log(' Esc/Bksp Back to list');
|
|
60
|
+
}
|
package/cli/batch.js
CHANGED
|
@@ -27,6 +27,17 @@ export default async function batchCommand(args = []) {
|
|
|
27
27
|
return showConfig();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
if (subcommand === 'tasks') {
|
|
31
|
+
return showTasks(args.slice(1));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (subcommand === 'run') {
|
|
35
|
+
const runArgs = parseRunOptions(args.slice(1));
|
|
36
|
+
const { runBatch } = await import('../src/services/batch-runner.js');
|
|
37
|
+
await runBatch(runArgs, process.cwd());
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
// Parse options
|
|
31
42
|
const options = parseOptions(args);
|
|
32
43
|
|
|
@@ -79,6 +90,31 @@ function parseOptions(args) {
|
|
|
79
90
|
return options;
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Parse options for `pwn batch run`
|
|
95
|
+
*/
|
|
96
|
+
function parseRunOptions(args) {
|
|
97
|
+
const options = {};
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < args.length; i++) {
|
|
100
|
+
const arg = args[i];
|
|
101
|
+
|
|
102
|
+
if (arg === '--dry-run') {
|
|
103
|
+
options.dryRun = true;
|
|
104
|
+
} else if (arg === '--phase') {
|
|
105
|
+
options.phase = args[++i];
|
|
106
|
+
} else if (arg === '--no-plan') {
|
|
107
|
+
options.noPlan = true;
|
|
108
|
+
} else if (arg === '--rate-limit-wait') {
|
|
109
|
+
options.rateLimitWait = parseInt(args[++i], 10);
|
|
110
|
+
} else if (/^\d+$/.test(arg)) {
|
|
111
|
+
options.maxIterations = parseInt(arg, 10);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return options;
|
|
116
|
+
}
|
|
117
|
+
|
|
82
118
|
/**
|
|
83
119
|
* Show batch status
|
|
84
120
|
*/
|
|
@@ -109,12 +145,8 @@ function showStatus() {
|
|
|
109
145
|
// Task counts
|
|
110
146
|
console.log('\n📝 Tasks\n');
|
|
111
147
|
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
|
-
}
|
|
148
|
+
console.log(` Stories: ${status.tasks.storiesDone}/${status.tasks.storiesTotal} done`);
|
|
149
|
+
console.log(` Pending: ${status.tasks.storiesPending}`);
|
|
118
150
|
|
|
119
151
|
// Batch history
|
|
120
152
|
if (status.batchState?.completed?.length > 0) {
|
|
@@ -133,7 +165,7 @@ function showStatus() {
|
|
|
133
165
|
console.log(` Priority: ${nextTask.priority}`);
|
|
134
166
|
}
|
|
135
167
|
} else {
|
|
136
|
-
console.log('\n No tasks available in
|
|
168
|
+
console.log('\n No tasks available in prd.json');
|
|
137
169
|
}
|
|
138
170
|
}
|
|
139
171
|
|
|
@@ -156,8 +188,6 @@ function showConfig() {
|
|
|
156
188
|
console.log(` Create PR: ${config.create_pr ? 'Yes' : 'No'}`);
|
|
157
189
|
console.log(` Branch Format: ${config.branch_format}`);
|
|
158
190
|
console.log(` Commit Format: ${config.commit_format}`);
|
|
159
|
-
console.log(`\n Notify Complete: ${config.notify_on_complete ? 'Yes' : 'No'}`);
|
|
160
|
-
console.log(` Notify Error: ${config.notify_on_error ? 'Yes' : 'No'}`);
|
|
161
191
|
console.log('\n📁 Config location: .ai/state.json (batch_config)');
|
|
162
192
|
}
|
|
163
193
|
|
|
@@ -185,7 +215,7 @@ async function dryRun(options) {
|
|
|
185
215
|
}
|
|
186
216
|
|
|
187
217
|
if (tasks.length === 0) {
|
|
188
|
-
console.log(' No tasks available in
|
|
218
|
+
console.log(' No tasks available in prd.json');
|
|
189
219
|
return;
|
|
190
220
|
}
|
|
191
221
|
|
|
@@ -297,6 +327,67 @@ async function resumeBatch(options) {
|
|
|
297
327
|
console.log(` Completed: ${result.completed?.length || 0} tasks`);
|
|
298
328
|
}
|
|
299
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Show task files from .ai/batch/tasks/
|
|
332
|
+
*/
|
|
333
|
+
async function showTasks(args) {
|
|
334
|
+
const { listTaskFiles, deleteTaskFile } = await import('../src/services/batch-runner.js');
|
|
335
|
+
const cwd = process.cwd();
|
|
336
|
+
const failedOnly = args.includes('--failed');
|
|
337
|
+
const cleanMode = args.includes('--clean');
|
|
338
|
+
|
|
339
|
+
if (cleanMode) {
|
|
340
|
+
const completed = listTaskFiles(cwd, { statusFilter: 'completed' });
|
|
341
|
+
if (completed.length === 0) {
|
|
342
|
+
console.log('No completed task files to clean.');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
let cleaned = 0;
|
|
346
|
+
for (const task of completed) {
|
|
347
|
+
if (deleteTaskFile(task.id, cwd)) cleaned++;
|
|
348
|
+
}
|
|
349
|
+
console.log(`Cleaned ${cleaned} completed task file(s).`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const filter = failedOnly ? { statusFilter: 'failed' } : {};
|
|
354
|
+
const tasks = listTaskFiles(cwd, filter);
|
|
355
|
+
|
|
356
|
+
if (tasks.length === 0) {
|
|
357
|
+
console.log(failedOnly ? 'No failed tasks.' : 'No task files found.');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const title = failedOnly ? 'Failed Tasks' : 'Batch Task Files';
|
|
362
|
+
console.log(`\n${title}\n`);
|
|
363
|
+
|
|
364
|
+
for (const task of tasks) {
|
|
365
|
+
const statusColor = task.status === 'completed' ? 'green'
|
|
366
|
+
: task.status === 'failed' ? 'red'
|
|
367
|
+
: 'yellow';
|
|
368
|
+
const statusLabel = chalk[statusColor](task.status.toUpperCase());
|
|
369
|
+
const complexity = task.complexity ? ` [${task.complexity}]` : '';
|
|
370
|
+
const estimate = task.estimated_time_seconds
|
|
371
|
+
? ` ~${Math.round(task.estimated_time_seconds)}s`
|
|
372
|
+
: '';
|
|
373
|
+
|
|
374
|
+
console.log(` ${task.id}: ${task.title}`);
|
|
375
|
+
console.log(` Status: ${statusLabel}${complexity}${estimate}`);
|
|
376
|
+
|
|
377
|
+
if (task.status === 'failed' && task.failure_reason) {
|
|
378
|
+
const reason = task.failure_reason.length > 120
|
|
379
|
+
? task.failure_reason.slice(0, 120) + '...'
|
|
380
|
+
: task.failure_reason;
|
|
381
|
+
console.log(` Reason: ${chalk.dim(reason)}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (task.plan && task.plan.length > 0 && task.plan[0] !== 'fallback - no plan available') {
|
|
385
|
+
console.log(` Plan: ${chalk.dim(task.plan.join(' → '))}`);
|
|
386
|
+
}
|
|
387
|
+
console.log('');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
300
391
|
/**
|
|
301
392
|
* Show help
|
|
302
393
|
*/
|
|
@@ -305,6 +396,8 @@ function showHelp() {
|
|
|
305
396
|
console.log('Usage: pwn batch [command] [options]\n');
|
|
306
397
|
console.log('Commands:');
|
|
307
398
|
console.log(' (default) Execute next available task(s)');
|
|
399
|
+
console.log(' run Run autonomous batch loop (Node.js TUI)');
|
|
400
|
+
console.log(' tasks List batch task files');
|
|
308
401
|
console.log(' status Show batch status');
|
|
309
402
|
console.log(' config Show batch configuration\n');
|
|
310
403
|
console.log('Options:');
|
|
@@ -317,14 +410,21 @@ function showHelp() {
|
|
|
317
410
|
console.log(' --continue Continue on errors');
|
|
318
411
|
console.log(' --no-commit Skip auto-commit');
|
|
319
412
|
console.log(' --no-branch Skip branch creation');
|
|
413
|
+
console.log(' --no-plan Skip planning phase (use fixed 10min timeout)');
|
|
414
|
+
console.log(' --rate-limit-wait <s> Seconds to wait on rate limit (default: 1800)');
|
|
320
415
|
console.log(' --help, -h Show this help\n');
|
|
321
416
|
console.log('Examples:');
|
|
322
417
|
console.log(' pwn batch # Execute next task');
|
|
418
|
+
console.log(' pwn batch run # Run autonomous batch loop');
|
|
419
|
+
console.log(' pwn batch run --dry-run # Preview next story');
|
|
420
|
+
console.log(' pwn batch run --phase 3 # Run specific phase');
|
|
421
|
+
console.log(' pwn batch run --no-plan # Skip planning, fixed timeout');
|
|
422
|
+
console.log(' pwn batch tasks # List all task files');
|
|
423
|
+
console.log(' pwn batch tasks --failed # Show only failed tasks');
|
|
424
|
+
console.log(' pwn batch tasks --clean # Delete completed task files');
|
|
323
425
|
console.log(' pwn batch --count 5 # Execute 5 tasks');
|
|
324
426
|
console.log(' pwn batch --dry-run # Preview execution');
|
|
325
|
-
console.log(' pwn batch --priority high # Only high priority');
|
|
326
427
|
console.log(' pwn batch --resume # Resume paused batch');
|
|
327
|
-
console.log(' pwn batch --resume --skip # Resume, skip current');
|
|
328
428
|
console.log(' pwn batch status # Show status');
|
|
329
429
|
console.log(' pwn batch config # Show configuration\n');
|
|
330
430
|
console.log('Configuration:');
|
package/cli/index.js
CHANGED
|
@@ -22,31 +22,24 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
22
22
|
console.log('Commands:');
|
|
23
23
|
console.log(' inject Inject .ai/ workspace into current project');
|
|
24
24
|
console.log(' update Update PWN framework files to latest version');
|
|
25
|
-
console.log(' migrate Migrate existing AI files to PWN structure');
|
|
26
25
|
console.log(' save Save session context to memory');
|
|
27
26
|
console.log(' status Show workspace status');
|
|
28
27
|
console.log(' validate Validate workspace structure');
|
|
29
|
-
console.log('
|
|
30
|
-
console.log(' batch Execute tasks
|
|
31
|
-
console.log(' mode Manage session mode (interactive/batch)');
|
|
28
|
+
console.log(' backlog Interactive backlog viewer (prd.json)');
|
|
29
|
+
console.log(' batch Execute tasks (batch run = autonomous loop)');
|
|
32
30
|
console.log(' patterns Manage patterns and triggers');
|
|
33
31
|
console.log(' knowledge Knowledge lifecycle management');
|
|
34
|
-
console.log(' codespaces GitHub Codespaces integration');
|
|
35
32
|
console.log(' --version, -v Show version');
|
|
36
33
|
console.log(' --help, -h Show help\n');
|
|
37
34
|
console.log('Options:');
|
|
38
35
|
console.log(' inject --force Overwrite existing .ai/ directory');
|
|
39
36
|
console.log(' update --dry-run Preview update without changes');
|
|
40
|
-
console.log(' migrate --dry-run Preview migration without changes');
|
|
41
37
|
console.log(' save --message=X Save with custom summary');
|
|
42
38
|
console.log(' validate --verbose Show detailed structure report');
|
|
43
|
-
console.log('
|
|
44
|
-
console.log(' batch --count 5 Execute 5 tasks');
|
|
45
|
-
console.log(' mode batch --max-tasks=3 Configure batch mode');
|
|
39
|
+
console.log(' batch run Run autonomous batch loop');
|
|
46
40
|
console.log(' patterns eval <f> Evaluate triggers for file');
|
|
47
|
-
console.log(' knowledge status Show knowledge system status');
|
|
48
|
-
console.log('
|
|
49
|
-
console.log('Documentation: https://github.com/anthropics/pwn');
|
|
41
|
+
console.log(' knowledge status Show knowledge system status\n');
|
|
42
|
+
console.log('Documentation: https://github.com/fermindi/pwn');
|
|
50
43
|
process.exit(0);
|
|
51
44
|
}
|
|
52
45
|
|
|
@@ -62,11 +55,6 @@ switch (command) {
|
|
|
62
55
|
await update(args);
|
|
63
56
|
break;
|
|
64
57
|
|
|
65
|
-
case 'migrate':
|
|
66
|
-
const { default: migrate } = await import('./migrate.js');
|
|
67
|
-
await migrate(args);
|
|
68
|
-
break;
|
|
69
|
-
|
|
70
58
|
case 'save':
|
|
71
59
|
const { default: save } = await import('./save.js');
|
|
72
60
|
await save(args);
|
|
@@ -82,9 +70,9 @@ switch (command) {
|
|
|
82
70
|
await validate(args);
|
|
83
71
|
break;
|
|
84
72
|
|
|
85
|
-
case '
|
|
86
|
-
const { default:
|
|
87
|
-
await
|
|
73
|
+
case 'backlog':
|
|
74
|
+
const { default: backlogCmd } = await import('./backlog.js');
|
|
75
|
+
await backlogCmd(args);
|
|
88
76
|
break;
|
|
89
77
|
|
|
90
78
|
case 'batch':
|
|
@@ -92,11 +80,6 @@ switch (command) {
|
|
|
92
80
|
await batchCmd(args);
|
|
93
81
|
break;
|
|
94
82
|
|
|
95
|
-
case 'mode':
|
|
96
|
-
const { default: modeCmd } = await import('./mode.js');
|
|
97
|
-
await modeCmd(args);
|
|
98
|
-
break;
|
|
99
|
-
|
|
100
83
|
case 'patterns':
|
|
101
84
|
const { default: patternsCmd } = await import('./patterns.js');
|
|
102
85
|
await patternsCmd(args);
|
|
@@ -107,13 +90,8 @@ switch (command) {
|
|
|
107
90
|
await knowledgeCmd(args);
|
|
108
91
|
break;
|
|
109
92
|
|
|
110
|
-
case 'codespaces':
|
|
111
|
-
const { default: codespacesCmd } = await import('./codespaces.js');
|
|
112
|
-
await codespacesCmd(args);
|
|
113
|
-
break;
|
|
114
|
-
|
|
115
93
|
default:
|
|
116
|
-
console.log(
|
|
94
|
+
console.log(`Unknown command: ${command}`);
|
|
117
95
|
console.log(' Run: pwn --help');
|
|
118
96
|
process.exit(1);
|
|
119
97
|
}
|
package/cli/inject.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
2
|
import { inject, detectKnownAIFiles } from '../src/core/inject.js';
|
|
5
3
|
|
|
6
4
|
export default async function injectCommand(args = []) {
|
|
@@ -22,17 +20,11 @@ export default async function injectCommand(args = []) {
|
|
|
22
20
|
console.log(` Type: ${file.type} - ${file.description}`);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log('\n 💡 Consider migrating content manually to .ai/ structure:');
|
|
31
|
-
console.log(' - Instructions → .ai/agents/claude.md');
|
|
32
|
-
console.log(' - Decisions → .ai/memory/decisions.md');
|
|
33
|
-
console.log(' - Patterns → .ai/memory/patterns.md');
|
|
34
|
-
console.log(' - Tasks → .ai/tasks/active.md\n');
|
|
35
|
-
}
|
|
23
|
+
console.log('\n 💡 Consider migrating content manually to .ai/ structure:');
|
|
24
|
+
console.log(' - Instructions → .ai/agents/claude.md');
|
|
25
|
+
console.log(' - Decisions → .ai/memory/decisions.md');
|
|
26
|
+
console.log(' - Patterns → .ai/memory/patterns.md');
|
|
27
|
+
console.log(' - Tasks → .ai/tasks/active.md\n');
|
|
36
28
|
}
|
|
37
29
|
|
|
38
30
|
if (!result.success) {
|
|
@@ -61,31 +53,15 @@ export default async function injectCommand(args = []) {
|
|
|
61
53
|
console.log('📁 Created structure:');
|
|
62
54
|
console.log(' .ai/');
|
|
63
55
|
console.log(' ├── memory/ (decisions, patterns, dead-ends)');
|
|
64
|
-
console.log(' ├── tasks/ (active work,
|
|
56
|
+
console.log(' ├── tasks/ (active work, prd.json)');
|
|
65
57
|
console.log(' ├── patterns/ (auto-applied patterns)');
|
|
58
|
+
console.log(' ├── batch/ (batch runner, prompts)');
|
|
66
59
|
console.log(' ├── workflows/ (batch execution)');
|
|
67
60
|
console.log(' ├── agents/ (AI agent configs)');
|
|
68
|
-
console.log(' └── config/ (
|
|
61
|
+
console.log(' └── config/ (project config)');
|
|
69
62
|
console.log(' .claude/');
|
|
70
63
|
console.log(' └── commands/ (slash commands: /save)\n');
|
|
71
64
|
|
|
72
|
-
// Show ntfy topic if generated
|
|
73
|
-
const notifyPath = join(process.cwd(), '.ai', 'config', 'notifications.json');
|
|
74
|
-
if (existsSync(notifyPath)) {
|
|
75
|
-
try {
|
|
76
|
-
const config = JSON.parse(readFileSync(notifyPath, 'utf8'));
|
|
77
|
-
const topic = config.channels?.ntfy?.topic;
|
|
78
|
-
if (topic && !topic.includes('your-unique')) {
|
|
79
|
-
console.log('🔔 Notifications:');
|
|
80
|
-
console.log(` ntfy topic: ${topic}`);
|
|
81
|
-
console.log(` Subscribe: https://ntfy.sh/${topic}`);
|
|
82
|
-
console.log(' Enable: Edit .ai/config/notifications.json\n');
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
// Ignore
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
65
|
console.log('📖 Next steps:');
|
|
90
66
|
console.log(' 1. Read: .ai/README.md');
|
|
91
67
|
console.log(' 2. Start working with AI assistance\n');
|
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, detectAvailableGates } from '../src/services/batch-service.js';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
|
|
@@ -17,6 +18,8 @@ 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/prompt.md',
|
|
22
|
+
'batch/progress.txt',
|
|
20
23
|
'config/README.md',
|
|
21
24
|
'README.md',
|
|
22
25
|
];
|
|
@@ -26,7 +29,6 @@ const FRAMEWORK_FILES = [
|
|
|
26
29
|
*/
|
|
27
30
|
const CLAUDE_COMMANDS = [
|
|
28
31
|
'save.md',
|
|
29
|
-
'mode.md',
|
|
30
32
|
];
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -39,9 +41,8 @@ const USER_FILES = [
|
|
|
39
41
|
'memory/deadends.md',
|
|
40
42
|
'memory/archive/',
|
|
41
43
|
'tasks/active.md',
|
|
42
|
-
'tasks/
|
|
44
|
+
'tasks/prd.json',
|
|
43
45
|
'state.json',
|
|
44
|
-
'config/notifications.json',
|
|
45
46
|
];
|
|
46
47
|
|
|
47
48
|
export default async function updateCommand(args = []) {
|
|
@@ -122,6 +123,46 @@ export default async function updateCommand(args = []) {
|
|
|
122
123
|
}
|
|
123
124
|
// ============================================
|
|
124
125
|
|
|
126
|
+
// ============================================
|
|
127
|
+
// MIGRATION: backlog.md → prd.json
|
|
128
|
+
// Convert legacy backlog.md to structured prd.json
|
|
129
|
+
// ============================================
|
|
130
|
+
const backlogPath = join(aiDir, 'tasks', 'backlog.md');
|
|
131
|
+
const prdPath = join(aiDir, 'tasks', 'prd.json');
|
|
132
|
+
|
|
133
|
+
if (existsSync(backlogPath)) {
|
|
134
|
+
// Check if prd.json is missing or is the default template (empty stories)
|
|
135
|
+
let needsMigration = !existsSync(prdPath);
|
|
136
|
+
if (!needsMigration && existsSync(prdPath)) {
|
|
137
|
+
try {
|
|
138
|
+
const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
|
|
139
|
+
needsMigration = !prd.stories || prd.stories.length === 0;
|
|
140
|
+
} catch {
|
|
141
|
+
needsMigration = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (needsMigration) {
|
|
146
|
+
const backlogContent = readFileSync(backlogPath, 'utf8');
|
|
147
|
+
const prd = convertBacklogToPrd(backlogContent);
|
|
148
|
+
|
|
149
|
+
if (prd.stories.length > 0) {
|
|
150
|
+
if (dryRun) {
|
|
151
|
+
console.log(` 🔄 Would migrate: .ai/tasks/backlog.md → prd.json (${prd.stories.length} stories)`);
|
|
152
|
+
console.log(` 📦 Would backup: backlog.md → ~backlog.md`);
|
|
153
|
+
} else {
|
|
154
|
+
writeFileSync(prdPath, JSON.stringify(prd, null, 2));
|
|
155
|
+
renameSync(backlogPath, join(aiDir, 'tasks', '~backlog.md'));
|
|
156
|
+
console.log(` 🔄 Migrated: backlog.md → prd.json (${prd.stories.length} stories)`);
|
|
157
|
+
console.log(` 📦 Backed up: backlog.md → .ai/tasks/~backlog.md`);
|
|
158
|
+
backed_up.push('backlog.md');
|
|
159
|
+
}
|
|
160
|
+
updated.push('tasks/backlog.md → prd.json');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ============================================
|
|
165
|
+
|
|
125
166
|
// Update framework files in .ai/
|
|
126
167
|
for (const file of FRAMEWORK_FILES) {
|
|
127
168
|
const templateFile = join(templateDir, file);
|
|
@@ -166,13 +207,12 @@ export default async function updateCommand(args = []) {
|
|
|
166
207
|
if (templateContent !== currentContent) {
|
|
167
208
|
if (dryRun) {
|
|
168
209
|
console.log(` 📝 Would update: CLAUDE.md`);
|
|
169
|
-
|
|
170
|
-
if (currentContent && currentContent !== templateContent) {
|
|
210
|
+
if (currentContent) {
|
|
171
211
|
console.log(` 📦 Would backup: CLAUDE.md → ~CLAUDE.md`);
|
|
172
212
|
}
|
|
173
213
|
} else {
|
|
174
|
-
// Backup existing CLAUDE.md
|
|
175
|
-
if (existsSync(claudeMdPath) && currentContent
|
|
214
|
+
// Backup existing CLAUDE.md
|
|
215
|
+
if (existsSync(claudeMdPath) && currentContent) {
|
|
176
216
|
renameSync(claudeMdPath, backupClaudeMdPath);
|
|
177
217
|
console.log(` 📦 Backed up: CLAUDE.md → ~CLAUDE.md`);
|
|
178
218
|
backed_up.push('CLAUDE.md');
|
|
@@ -218,25 +258,6 @@ export default async function updateCommand(args = []) {
|
|
|
218
258
|
}
|
|
219
259
|
}
|
|
220
260
|
|
|
221
|
-
// Update .claude/settings.json (hooks) - only if doesn't exist (user config)
|
|
222
|
-
const claudeSettingsTemplate = join(__dirname, '../templates/workspace/.claude/settings.json');
|
|
223
|
-
const claudeSettingsTarget = join(cwd, '.claude', 'settings.json');
|
|
224
|
-
|
|
225
|
-
if (existsSync(claudeSettingsTemplate) && !existsSync(claudeSettingsTarget)) {
|
|
226
|
-
if (dryRun) {
|
|
227
|
-
console.log(` 📝 Would create: .claude/settings.json (notification hooks)`);
|
|
228
|
-
} else {
|
|
229
|
-
const claudeDir = join(cwd, '.claude');
|
|
230
|
-
if (!existsSync(claudeDir)) {
|
|
231
|
-
mkdirSync(claudeDir, { recursive: true });
|
|
232
|
-
}
|
|
233
|
-
const templateContent = readFileSync(claudeSettingsTemplate, 'utf8');
|
|
234
|
-
writeFileSync(claudeSettingsTarget, templateContent);
|
|
235
|
-
console.log(` 📝 Created: .claude/settings.json (notification hooks)`);
|
|
236
|
-
}
|
|
237
|
-
updated.push('.claude/settings.json');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
261
|
// Update state.json with new version
|
|
241
262
|
if (!dryRun && existsSync(statePath)) {
|
|
242
263
|
try {
|
|
@@ -249,6 +270,36 @@ export default async function updateCommand(args = []) {
|
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
272
|
|
|
273
|
+
// Re-detect quality gates and update skip_gates
|
|
274
|
+
if (!dryRun) {
|
|
275
|
+
const gates = detectAvailableGates(cwd);
|
|
276
|
+
console.log('\n⚙️ Quality Gates Detection');
|
|
277
|
+
for (const gate of gates.available) {
|
|
278
|
+
console.log(` ✅ ${gate.padEnd(12)} ${gates.details[gate]}`);
|
|
279
|
+
}
|
|
280
|
+
for (const gate of gates.missing) {
|
|
281
|
+
console.log(` ⚠️ ${gate.padEnd(12)} not detected`);
|
|
282
|
+
}
|
|
283
|
+
if (gates.missing.length > 0) {
|
|
284
|
+
console.log(` → Auto-configured skip_gates: ${JSON.stringify(gates.missing)}`);
|
|
285
|
+
} else {
|
|
286
|
+
console.log(` → All gates available`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Update skip_gates in state.json
|
|
290
|
+
if (existsSync(statePath)) {
|
|
291
|
+
try {
|
|
292
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
293
|
+
if (!state.batch_config) state.batch_config = {};
|
|
294
|
+
state.batch_config.skip_gates = gates.missing;
|
|
295
|
+
state.last_updated = new Date().toISOString();
|
|
296
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
297
|
+
} catch {
|
|
298
|
+
// Ignore
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
252
303
|
// Summary
|
|
253
304
|
console.log('');
|
|
254
305
|
if (updated.length === 0) {
|
|
@@ -273,7 +324,7 @@ export default async function updateCommand(args = []) {
|
|
|
273
324
|
// Show what was preserved
|
|
274
325
|
console.log('🔒 Preserved (user data):');
|
|
275
326
|
console.log(' .ai/memory/ (decisions, patterns, deadends)');
|
|
276
|
-
console.log(' .ai/tasks/ (active,
|
|
327
|
+
console.log(' .ai/tasks/ (active, prd.json)');
|
|
277
328
|
console.log(' .ai/state.json (session state)');
|
|
278
329
|
console.log(' .claude/ (your custom commands preserved)\n');
|
|
279
330
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fermindi/pwn-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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": {
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"./core/validate": "./src/core/validate.js",
|
|
15
15
|
"./core/workspace": "./src/core/workspace.js",
|
|
16
16
|
"./services/batch": "./src/services/batch-service.js",
|
|
17
|
-
"./services/notifications": "./src/services/notification-service.js",
|
|
18
17
|
"./patterns/registry": "./src/patterns/registry.js",
|
|
19
18
|
"./patterns/triggers": "./src/patterns/triggers.js",
|
|
20
19
|
"./knowledge/lifecycle": "./src/knowledge/lifecycle.js",
|
|
@@ -47,7 +46,7 @@
|
|
|
47
46
|
"memory",
|
|
48
47
|
"decisions",
|
|
49
48
|
"batch",
|
|
50
|
-
"
|
|
49
|
+
"claude-code",
|
|
51
50
|
"developer-tools",
|
|
52
51
|
"productivity"
|
|
53
52
|
],
|
|
@@ -66,5 +65,9 @@
|
|
|
66
65
|
},
|
|
67
66
|
"devDependencies": {
|
|
68
67
|
"vitest": "^2.0.0"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"chalk": "^5.6.2",
|
|
71
|
+
"ora": "^9.3.0"
|
|
69
72
|
}
|
|
70
73
|
}
|