@fermindi/pwn-cli 0.6.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/cli/backlog.js +60 -0
- package/cli/batch.js +101 -14
- package/cli/index.js +7 -29
- package/cli/inject.js +8 -33
- package/cli/update.js +31 -24
- package/package.json +6 -3
- package/src/core/inject.js +18 -39
- package/src/core/state.js +0 -1
- package/src/core/validate.js +1 -3
- package/src/index.js +0 -1
- package/src/services/batch-runner.js +769 -0
- package/src/services/batch-service.js +107 -19
- package/src/ui/backlog-viewer.js +394 -0
- package/templates/workspace/.ai/batch/tasks/.gitkeep +0 -0
- 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/.claude/commands/mode.md +0 -104
- package/templates/workspace/.claude/settings.json +0 -24
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
|
@@ -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
|
|
34
|
-
|
|
35
|
-
|
|
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,31 @@ 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 === '--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
|
+
|
|
97
118
|
/**
|
|
98
119
|
* Show batch status
|
|
99
120
|
*/
|
|
@@ -167,8 +188,6 @@ function showConfig() {
|
|
|
167
188
|
console.log(` Create PR: ${config.create_pr ? 'Yes' : 'No'}`);
|
|
168
189
|
console.log(` Branch Format: ${config.branch_format}`);
|
|
169
190
|
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
191
|
console.log('\nš Config location: .ai/state.json (batch_config)');
|
|
173
192
|
}
|
|
174
193
|
|
|
@@ -308,6 +327,67 @@ async function resumeBatch(options) {
|
|
|
308
327
|
console.log(` Completed: ${result.completed?.length || 0} tasks`);
|
|
309
328
|
}
|
|
310
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
|
+
|
|
311
391
|
/**
|
|
312
392
|
* Show help
|
|
313
393
|
*/
|
|
@@ -316,7 +396,8 @@ function showHelp() {
|
|
|
316
396
|
console.log('Usage: pwn batch [command] [options]\n');
|
|
317
397
|
console.log('Commands:');
|
|
318
398
|
console.log(' (default) Execute next available task(s)');
|
|
319
|
-
console.log(' run Run
|
|
399
|
+
console.log(' run Run autonomous batch loop (Node.js TUI)');
|
|
400
|
+
console.log(' tasks List batch task files');
|
|
320
401
|
console.log(' status Show batch status');
|
|
321
402
|
console.log(' config Show batch configuration\n');
|
|
322
403
|
console.log('Options:');
|
|
@@ -329,12 +410,18 @@ function showHelp() {
|
|
|
329
410
|
console.log(' --continue Continue on errors');
|
|
330
411
|
console.log(' --no-commit Skip auto-commit');
|
|
331
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)');
|
|
332
415
|
console.log(' --help, -h Show this help\n');
|
|
333
416
|
console.log('Examples:');
|
|
334
417
|
console.log(' pwn batch # Execute next task');
|
|
335
418
|
console.log(' pwn batch run # Run autonomous batch loop');
|
|
336
419
|
console.log(' pwn batch run --dry-run # Preview next story');
|
|
337
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');
|
|
338
425
|
console.log(' pwn batch --count 5 # Execute 5 tasks');
|
|
339
426
|
console.log(' pwn batch --dry-run # Preview execution');
|
|
340
427
|
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('
|
|
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('
|
|
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) {
|
|
@@ -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/ (
|
|
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.
|
|
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
|
}
|
package/src/core/inject.js
CHANGED
|
@@ -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
|
|
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 }
|
package/src/core/validate.js
CHANGED
package/src/index.js
CHANGED