@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 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/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(` 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
- }
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 backlog');
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 backlog');
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(' notify Send notifications (test, send, config)');
30
- console.log(' batch Execute tasks autonomously');
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(' notify test [ch] Test notification channel');
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(' codespaces init Add devcontainer config\n');
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 'notify':
86
- const { default: notify } = await import('./notify.js');
87
- await notify(args);
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(`❌ Unknown command: ${command}`);
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
- if (migratable.length > 0) {
26
- console.log('\n 🔄 Run "pwn migrate" to automatically migrate content:');
27
- console.log(' pwn migrate --dry-run # Preview changes');
28
- console.log(' pwn migrate # Execute migration\n');
29
- } else {
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, backlog)');
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/ (notifications, etc)');
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(` 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, 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/backlog.md',
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
- // Only mention backup if content is actually different (not empty)
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 only if it has custom content (different from template)
175
- if (existsSync(claudeMdPath) && currentContent && currentContent !== templateContent) {
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, backlog)');
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.5.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
- "codespaces",
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
  }