@fermindi/pwn-cli 0.6.0 → 0.8.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/cli/backlog.js ADDED
@@ -0,0 +1,73 @@
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
+ let stories = parsePrdTasks(cwd);
23
+ const taskFiles = listTaskFiles(cwd);
24
+
25
+ // Parse --filter
26
+ const filterIdx = args.findIndex(a => a === '--filter');
27
+ const filter = filterIdx !== -1 ? args[filterIdx + 1] : null;
28
+ if (filter) {
29
+ const re = new RegExp(filter, 'i');
30
+ stories = stories.filter(s => re.test(s.id) || re.test(s.title));
31
+ }
32
+
33
+ // Read project name from prd.json
34
+ let project = 'project';
35
+ const prdPath = join(cwd, '.ai', 'tasks', 'prd.json');
36
+ if (existsSync(prdPath)) {
37
+ try {
38
+ const prd = JSON.parse(readFileSync(prdPath, 'utf8'));
39
+ project = prd.project || project;
40
+ } catch {}
41
+ }
42
+
43
+ const noInteractive = args.includes('--no-interactive') || !process.stdout.isTTY;
44
+ const label = filter ? `${project} (filter: ${filter})` : project;
45
+
46
+ if (noInteractive) {
47
+ printPlain({ project: label, stories, taskFiles });
48
+ } else {
49
+ await startViewer({ project: label, stories, taskFiles });
50
+ }
51
+ }
52
+
53
+ function showHelp() {
54
+ console.log('šŸ“‹ PWN Backlog Viewer\n');
55
+ console.log('Usage: pwn backlog [options]\n');
56
+ console.log('Options:');
57
+ console.log(' --filter <pattern> Filter stories by ID or title (regex, case-insensitive)');
58
+ console.log(' --no-interactive Plain text output (for CI/piping)');
59
+ console.log(' --help, -h Show this help\n');
60
+ console.log('Examples:');
61
+ console.log(' pwn backlog --filter SEC # Only SEC-* stories');
62
+ console.log(' pwn backlog --filter "API|AUTH" # Stories matching API or AUTH\n');
63
+ console.log('Keybindings (list view):');
64
+ console.log(' ↑/k Move up');
65
+ console.log(' ↓/j Move down');
66
+ console.log(' Enter Open full detail view');
67
+ console.log(' Home/End First/last story');
68
+ console.log(' q/Ctrl+C Quit\n');
69
+ console.log('Keybindings (detail view):');
70
+ console.log(' ↑/k ↓/j Scroll content');
71
+ console.log(' ←/→ Previous/next story');
72
+ console.log(' Esc/Bksp Back to list');
73
+ }
package/cli/batch.js CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'child_process';
3
- import { join } from 'path';
4
2
  import * as batch from '../src/services/batch-service.js';
5
3
  import { hasWorkspace } from '../src/core/state.js';
6
4
 
@@ -29,16 +27,14 @@ export default async function batchCommand(args = []) {
29
27
  return showConfig();
30
28
  }
31
29
 
30
+ if (subcommand === 'tasks') {
31
+ return showTasks(args.slice(1));
32
+ }
33
+
32
34
  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
- }
35
+ const runArgs = parseRunOptions(args.slice(1));
36
+ const { runBatch } = await import('../src/services/batch-runner.js');
37
+ await runBatch(runArgs, process.cwd());
42
38
  return;
43
39
  }
44
40
 
@@ -94,6 +90,33 @@ function parseOptions(args) {
94
90
  return options;
95
91
  }
96
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 === '--filter') {
107
+ options.filter = args[++i];
108
+ } else if (arg === '--no-plan') {
109
+ options.noPlan = true;
110
+ } else if (arg === '--rate-limit-wait') {
111
+ options.rateLimitWait = parseInt(args[++i], 10);
112
+ } else if (/^\d+$/.test(arg)) {
113
+ options.maxIterations = parseInt(arg, 10);
114
+ }
115
+ }
116
+
117
+ return options;
118
+ }
119
+
97
120
  /**
98
121
  * Show batch status
99
122
  */
@@ -167,8 +190,6 @@ function showConfig() {
167
190
  console.log(` Create PR: ${config.create_pr ? 'Yes' : 'No'}`);
168
191
  console.log(` Branch Format: ${config.branch_format}`);
169
192
  console.log(` Commit Format: ${config.commit_format}`);
170
- console.log(`\n Notify Complete: ${config.notify_on_complete ? 'Yes' : 'No'}`);
171
- console.log(` Notify Error: ${config.notify_on_error ? 'Yes' : 'No'}`);
172
193
  console.log('\nšŸ“ Config location: .ai/state.json (batch_config)');
173
194
  }
174
195
 
@@ -308,6 +329,67 @@ async function resumeBatch(options) {
308
329
  console.log(` Completed: ${result.completed?.length || 0} tasks`);
309
330
  }
310
331
 
332
+ /**
333
+ * Show task files from .ai/batch/tasks/
334
+ */
335
+ async function showTasks(args) {
336
+ const { listTaskFiles, deleteTaskFile } = await import('../src/services/batch-runner.js');
337
+ const cwd = process.cwd();
338
+ const failedOnly = args.includes('--failed');
339
+ const cleanMode = args.includes('--clean');
340
+
341
+ if (cleanMode) {
342
+ const completed = listTaskFiles(cwd, { statusFilter: 'completed' });
343
+ if (completed.length === 0) {
344
+ console.log('No completed task files to clean.');
345
+ return;
346
+ }
347
+ let cleaned = 0;
348
+ for (const task of completed) {
349
+ if (deleteTaskFile(task.id, cwd)) cleaned++;
350
+ }
351
+ console.log(`Cleaned ${cleaned} completed task file(s).`);
352
+ return;
353
+ }
354
+
355
+ const filter = failedOnly ? { statusFilter: 'failed' } : {};
356
+ const tasks = listTaskFiles(cwd, filter);
357
+
358
+ if (tasks.length === 0) {
359
+ console.log(failedOnly ? 'No failed tasks.' : 'No task files found.');
360
+ return;
361
+ }
362
+
363
+ const title = failedOnly ? 'Failed Tasks' : 'Batch Task Files';
364
+ console.log(`\n${title}\n`);
365
+
366
+ for (const task of tasks) {
367
+ const statusColor = task.status === 'completed' ? 'green'
368
+ : task.status === 'failed' ? 'red'
369
+ : 'yellow';
370
+ const statusLabel = chalk[statusColor](task.status.toUpperCase());
371
+ const complexity = task.complexity ? ` [${task.complexity}]` : '';
372
+ const estimate = task.estimated_time_seconds
373
+ ? ` ~${Math.round(task.estimated_time_seconds)}s`
374
+ : '';
375
+
376
+ console.log(` ${task.id}: ${task.title}`);
377
+ console.log(` Status: ${statusLabel}${complexity}${estimate}`);
378
+
379
+ if (task.status === 'failed' && task.failure_reason) {
380
+ const reason = task.failure_reason.length > 120
381
+ ? task.failure_reason.slice(0, 120) + '...'
382
+ : task.failure_reason;
383
+ console.log(` Reason: ${chalk.dim(reason)}`);
384
+ }
385
+
386
+ if (task.plan && task.plan.length > 0 && task.plan[0] !== 'fallback - no plan available') {
387
+ console.log(` Plan: ${chalk.dim(task.plan.join(' → '))}`);
388
+ }
389
+ console.log('');
390
+ }
391
+ }
392
+
311
393
  /**
312
394
  * Show help
313
395
  */
@@ -316,7 +398,8 @@ function showHelp() {
316
398
  console.log('Usage: pwn batch [command] [options]\n');
317
399
  console.log('Commands:');
318
400
  console.log(' (default) Execute next available task(s)');
319
- console.log(' run Run batch_runner.sh (autonomous loop)');
401
+ console.log(' run Run autonomous batch loop (Node.js TUI)');
402
+ console.log(' tasks List batch task files');
320
403
  console.log(' status Show batch status');
321
404
  console.log(' config Show batch configuration\n');
322
405
  console.log('Options:');
@@ -329,12 +412,21 @@ function showHelp() {
329
412
  console.log(' --continue Continue on errors');
330
413
  console.log(' --no-commit Skip auto-commit');
331
414
  console.log(' --no-branch Skip branch creation');
415
+ console.log(' --filter <pattern> Filter tasks by ID or title (regex, case-insensitive)');
416
+ console.log(' --no-plan Skip planning phase (use fixed 10min timeout)');
417
+ console.log(' --rate-limit-wait <s> Seconds to wait on rate limit (default: 1800)');
332
418
  console.log(' --help, -h Show this help\n');
333
419
  console.log('Examples:');
334
420
  console.log(' pwn batch # Execute next task');
335
421
  console.log(' pwn batch run # Run autonomous batch loop');
336
422
  console.log(' pwn batch run --dry-run # Preview next story');
337
423
  console.log(' pwn batch run --phase 3 # Run specific phase');
424
+ console.log(' pwn batch run --filter ORCH # Run only ORCH-* tasks');
425
+ console.log(' pwn batch run -f "UNI|DATA" # Run UNI or DATA tasks');
426
+ console.log(' pwn batch run --no-plan # Skip planning, fixed timeout');
427
+ console.log(' pwn batch tasks # List all task files');
428
+ console.log(' pwn batch tasks --failed # Show only failed tasks');
429
+ console.log(' pwn batch tasks --clean # Delete completed task files');
338
430
  console.log(' pwn batch --count 5 # Execute 5 tasks');
339
431
  console.log(' pwn batch --dry-run # Preview execution');
340
432
  console.log(' pwn batch --resume # Resume paused batch');
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)');
28
+ console.log(' backlog Interactive backlog viewer (prd.json)');
30
29
  console.log(' batch Execute tasks (batch run = autonomous loop)');
31
- console.log(' mode Manage session mode (interactive/batch)');
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
39
  console.log(' batch run Run autonomous batch loop');
45
- console.log(' mode batch --max-tasks=3 Configure batch mode');
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) {
@@ -63,30 +55,13 @@ export default async function injectCommand(args = []) {
63
55
  console.log(' ā”œā”€ā”€ memory/ (decisions, patterns, dead-ends)');
64
56
  console.log(' ā”œā”€ā”€ tasks/ (active work, prd.json)');
65
57
  console.log(' ā”œā”€ā”€ patterns/ (auto-applied patterns)');
66
- console.log(' ā”œā”€ā”€ batch/ (batch runner, prompts)
67
- ā”œā”€ā”€ workflows/ (batch execution)');
58
+ console.log(' ā”œā”€ā”€ batch/ (batch runner, prompts)');
59
+ console.log(' ā”œā”€ā”€ workflows/ (batch execution)');
68
60
  console.log(' ā”œā”€ā”€ agents/ (AI agent configs)');
69
- console.log(' └── config/ (notifications, etc)');
61
+ console.log(' └── config/ (project config)');
70
62
  console.log(' .claude/');
71
63
  console.log(' └── commands/ (slash commands: /save)\n');
72
64
 
73
- // Show ntfy topic if generated
74
- const notifyPath = join(process.cwd(), '.ai', 'config', 'notifications.json');
75
- if (existsSync(notifyPath)) {
76
- try {
77
- const config = JSON.parse(readFileSync(notifyPath, 'utf8'));
78
- const topic = config.channels?.ntfy?.topic;
79
- if (topic && !topic.includes('your-unique')) {
80
- console.log('šŸ”” Notifications:');
81
- console.log(` ntfy topic: ${topic}`);
82
- console.log(` Subscribe: https://ntfy.sh/${topic}`);
83
- console.log(' Enable: Edit .ai/config/notifications.json\n');
84
- }
85
- } catch {
86
- // Ignore
87
- }
88
- }
89
-
90
65
  console.log('šŸ“– Next steps:');
91
66
  console.log(' 1. Read: .ai/README.md');
92
67
  console.log(' 2. Start working with AI assistance\n');
package/cli/update.js CHANGED
@@ -2,7 +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
+ import { convertBacklogToPrd, detectAvailableGates } from '../src/services/batch-service.js';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
 
@@ -18,10 +18,8 @@ const FRAMEWORK_FILES = [
18
18
  'patterns/backend/backend.template.md',
19
19
  'patterns/universal/universal.template.md',
20
20
  'workflows/batch-task.md',
21
- 'batch/batch_runner.sh',
22
21
  'batch/prompt.md',
23
22
  'batch/progress.txt',
24
- 'batch/prd_status.sh',
25
23
  'config/README.md',
26
24
  'README.md',
27
25
  ];
@@ -31,7 +29,6 @@ const FRAMEWORK_FILES = [
31
29
  */
32
30
  const CLAUDE_COMMANDS = [
33
31
  'save.md',
34
- 'mode.md',
35
32
  ];
36
33
 
37
34
  /**
@@ -46,7 +43,6 @@ const USER_FILES = [
46
43
  'tasks/active.md',
47
44
  'tasks/prd.json',
48
45
  'state.json',
49
- 'config/notifications.json',
50
46
  ];
51
47
 
52
48
  export default async function updateCommand(args = []) {
@@ -262,25 +258,6 @@ export default async function updateCommand(args = []) {
262
258
  }
263
259
  }
264
260
 
265
- // Update .claude/settings.json (hooks) - only if doesn't exist (user config)
266
- const claudeSettingsTemplate = join(__dirname, '../templates/workspace/.claude/settings.json');
267
- const claudeSettingsTarget = join(cwd, '.claude', 'settings.json');
268
-
269
- if (existsSync(claudeSettingsTemplate) && !existsSync(claudeSettingsTarget)) {
270
- if (dryRun) {
271
- console.log(` šŸ“ Would create: .claude/settings.json (notification hooks)`);
272
- } else {
273
- const claudeDir = join(cwd, '.claude');
274
- if (!existsSync(claudeDir)) {
275
- mkdirSync(claudeDir, { recursive: true });
276
- }
277
- const templateContent = readFileSync(claudeSettingsTemplate, 'utf8');
278
- writeFileSync(claudeSettingsTarget, templateContent);
279
- console.log(` šŸ“ Created: .claude/settings.json (notification hooks)`);
280
- }
281
- updated.push('.claude/settings.json');
282
- }
283
-
284
261
  // Update state.json with new version
285
262
  if (!dryRun && existsSync(statePath)) {
286
263
  try {
@@ -293,6 +270,36 @@ export default async function updateCommand(args = []) {
293
270
  }
294
271
  }
295
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
+
296
303
  // Summary
297
304
  console.log('');
298
305
  if (updated.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fermindi/pwn-cli",
3
- "version": "0.6.0",
3
+ "version": "0.8.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
  }
@@ -1,9 +1,9 @@
1
1
  import { existsSync, cpSync, renameSync, readFileSync, appendFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { randomUUID } from 'crypto';
5
4
  import { initState } from './state.js';
6
- import { convertBacklogToPrd } from '../services/batch-service.js';
5
+ import { convertBacklogToPrd, detectAvailableGates } from '../services/batch-service.js';
6
+ import { updateState } from './state.js';
7
7
 
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
9
 
@@ -175,18 +175,24 @@ export async function inject(options = {}) {
175
175
  renameSync(templateState, stateFile);
176
176
  }
177
177
 
178
- // Rename notifications.template.json → notifications.json and generate unique topic
179
- const templateNotify = join(targetDir, 'config', 'notifications.template.json');
180
- const notifyFile = join(targetDir, 'config', 'notifications.json');
181
-
182
- if (existsSync(templateNotify)) {
183
- renameSync(templateNotify, notifyFile);
184
- initNotifications(notifyFile);
185
- }
186
-
187
178
  // Initialize state.json with current user
188
179
  initState(cwd);
189
180
 
181
+ // Detect available quality gates and auto-configure skip_gates
182
+ const gates = detectAvailableGates(cwd);
183
+ log('\nāš™ļø Quality Gates Detection');
184
+ for (const gate of gates.available) {
185
+ log(` āœ… ${gate.padEnd(12)} ${gates.details[gate]}`);
186
+ }
187
+ for (const gate of gates.missing) {
188
+ log(` āš ļø ${gate.padEnd(12)} not detected`);
189
+ }
190
+ if (gates.missing.length > 0) {
191
+ log(` → Auto-configured skip_gates: ${JSON.stringify(gates.missing)}`);
192
+ updateState({ batch_config: { skip_gates: gates.missing } }, cwd);
193
+ }
194
+ log('');
195
+
190
196
  // Write migrated prd.json (from backlog.md) if available
191
197
  if (migratedPrd) {
192
198
  const prdPath = join(targetDir, 'tasks', 'prd.json');
@@ -242,15 +248,6 @@ export async function inject(options = {}) {
242
248
  }
243
249
  }
244
250
 
245
- // Copy settings.json with hooks (if doesn't exist)
246
- const settingsSource = join(claudeTemplateDir, 'settings.json');
247
- const settingsTarget = join(claudeDir, 'settings.json');
248
- if (existsSync(settingsSource) && !existsSync(settingsTarget)) {
249
- cpSync(settingsSource, settingsTarget);
250
- if (!silent) {
251
- console.log('šŸ“ Created .claude/settings.json with notification hooks');
252
- }
253
- }
254
251
  }
255
252
 
256
253
  // Backup other AI files (not CLAUDE.md) to .ai/
@@ -280,24 +277,6 @@ export async function inject(options = {}) {
280
277
  }
281
278
  }
282
279
 
283
- /**
284
- * Initialize notifications.json with unique topic
285
- * @param {string} notifyFile - Path to notifications.json
286
- */
287
- function initNotifications(notifyFile) {
288
- try {
289
- const content = readFileSync(notifyFile, 'utf8');
290
- const config = JSON.parse(content);
291
-
292
- // Generate unique topic ID
293
- const uniqueId = randomUUID().split('-')[0]; // First segment: 8 chars
294
- config.channels.ntfy.topic = `pwn-${uniqueId}`;
295
-
296
- writeFileSync(notifyFile, JSON.stringify(config, null, 2));
297
- } catch {
298
- // Ignore errors - notifications will use defaults
299
- }
300
- }
301
280
 
302
281
  /**
303
282
  * Backup existing AI files to .ai/ root with ~ prefix
@@ -351,7 +330,7 @@ function updateGitignore(cwd, silent = false) {
351
330
  }
352
331
 
353
332
  if (!gitignoreContent.includes('.ai/state.json')) {
354
- const pwnSection = '\n# PWN\n.ai/state.json\n.ai/config/notifications.json\n';
333
+ const pwnSection = '\n# PWN\n.ai/state.json\n';
355
334
  appendFileSync(gitignorePath, pwnSection);
356
335
  if (!silent) {
357
336
  console.log('šŸ“ Updated .gitignore');
package/src/core/state.js CHANGED
@@ -95,7 +95,6 @@ export function initState(cwd = process.cwd()) {
95
95
  const state = {
96
96
  developer: getGitUser(),
97
97
  session_started: new Date().toISOString(),
98
- session_mode: 'interactive',
99
98
  current_task: null,
100
99
  context_loaded: [],
101
100
  batch_config: { ...DEFAULT_BATCH_CONFIG }
@@ -27,10 +27,8 @@ const REQUIRED_STRUCTURE = {
27
27
  'tasks/prd.json'
28
28
  ],
29
29
  batchFiles: [
30
- 'batch/batch_runner.sh',
31
30
  'batch/prompt.md',
32
- 'batch/progress.txt',
33
- 'batch/prd_status.sh'
31
+ 'batch/progress.txt'
34
32
  ],
35
33
  agentFiles: [
36
34
  'agents/README.md',
package/src/index.js CHANGED
@@ -5,7 +5,6 @@ export { validate } from './core/validate.js';
5
5
  export { getWorkspaceInfo } from './core/workspace.js';
6
6
 
7
7
  // Services
8
- export * as notifications from './services/notification-service.js';
9
8
  export * as batch from './services/batch-service.js';
10
9
 
11
10
  // Patterns