@fermindi/pwn-cli 0.1.1 → 0.3.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/LICENSE +21 -21
- package/README.md +265 -251
- package/cli/batch.js +333 -333
- package/cli/codespaces.js +303 -303
- package/cli/index.js +112 -91
- package/cli/inject.js +90 -67
- package/cli/knowledge.js +531 -531
- package/cli/migrate.js +466 -0
- package/cli/notify.js +135 -135
- package/cli/patterns.js +665 -665
- package/cli/save.js +206 -0
- package/cli/status.js +91 -91
- package/cli/update.js +189 -0
- package/cli/validate.js +61 -61
- package/package.json +70 -70
- package/src/core/inject.js +300 -204
- package/src/core/state.js +91 -91
- package/src/core/validate.js +202 -202
- package/src/core/workspace.js +176 -176
- package/src/index.js +20 -20
- package/src/knowledge/gc.js +308 -308
- package/src/knowledge/lifecycle.js +401 -401
- package/src/knowledge/promote.js +364 -364
- package/src/knowledge/references.js +342 -342
- package/src/patterns/matcher.js +218 -218
- package/src/patterns/registry.js +375 -375
- package/src/patterns/triggers.js +423 -423
- package/src/services/batch-service.js +849 -849
- package/src/services/notification-service.js +342 -342
- package/templates/codespaces/devcontainer.json +52 -52
- package/templates/codespaces/setup.sh +70 -70
- package/templates/workspace/.ai/README.md +164 -164
- package/templates/workspace/.ai/agents/README.md +204 -204
- package/templates/workspace/.ai/agents/claude.md +625 -625
- package/templates/workspace/.ai/config/README.md +79 -79
- package/templates/workspace/.ai/config/notifications.template.json +20 -20
- package/templates/workspace/.ai/memory/deadends.md +79 -79
- package/templates/workspace/.ai/memory/decisions.md +58 -58
- package/templates/workspace/.ai/memory/patterns.md +65 -65
- package/templates/workspace/.ai/patterns/backend/README.md +126 -126
- package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
- package/templates/workspace/.ai/patterns/index.md +256 -256
- package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
- package/templates/workspace/.ai/patterns/universal/README.md +141 -141
- package/templates/workspace/.ai/state.template.json +8 -8
- package/templates/workspace/.ai/tasks/active.md +77 -77
- package/templates/workspace/.ai/tasks/backlog.md +95 -95
- package/templates/workspace/.ai/workflows/batch-task.md +356 -356
package/cli/save.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { createInterface } from 'readline';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interactive prompt helper
|
|
8
|
+
*/
|
|
9
|
+
function prompt(question) {
|
|
10
|
+
const rl = createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
rl.question(question, (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(answer);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate timestamp-based filename
|
|
25
|
+
*/
|
|
26
|
+
function generateFilename() {
|
|
27
|
+
const now = new Date();
|
|
28
|
+
const date = now.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
29
|
+
return `${date}-session.md`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format current date
|
|
34
|
+
*/
|
|
35
|
+
function formatDate() {
|
|
36
|
+
return new Date().toISOString().split('T')[0];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default async function saveCommand(args = []) {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const aiDir = join(cwd, '.ai');
|
|
42
|
+
|
|
43
|
+
// Check if .ai/ exists
|
|
44
|
+
if (!existsSync(aiDir)) {
|
|
45
|
+
console.log('❌ No .ai/ directory found');
|
|
46
|
+
console.log(' Run "pwn inject" first to initialize workspace');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('💾 PWN Save - Persist session context\n');
|
|
51
|
+
|
|
52
|
+
const isInteractive = !args.includes('--no-input') && process.stdin.isTTY;
|
|
53
|
+
const messageArg = args.find(a => a.startsWith('--message=') || a.startsWith('-m='));
|
|
54
|
+
let summary = messageArg ? messageArg.split('=')[1] : null;
|
|
55
|
+
|
|
56
|
+
// Paths
|
|
57
|
+
const archiveDir = join(aiDir, 'memory', 'archive');
|
|
58
|
+
const decisionsPath = join(aiDir, 'memory', 'decisions.md');
|
|
59
|
+
const patternsPath = join(aiDir, 'memory', 'patterns.md');
|
|
60
|
+
const deadendsPath = join(aiDir, 'memory', 'deadends.md');
|
|
61
|
+
const activePath = join(aiDir, 'tasks', 'active.md');
|
|
62
|
+
const statePath = join(aiDir, 'state.json');
|
|
63
|
+
|
|
64
|
+
// Ensure archive directory exists
|
|
65
|
+
if (!existsSync(archiveDir)) {
|
|
66
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get summary
|
|
70
|
+
if (!summary && isInteractive) {
|
|
71
|
+
console.log('📝 What did you accomplish in this session?');
|
|
72
|
+
console.log(' (Brief summary - press Enter twice to finish)\n');
|
|
73
|
+
summary = await prompt('> ');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!summary) {
|
|
77
|
+
summary = 'Session checkpoint';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create archive entry
|
|
81
|
+
const filename = generateFilename();
|
|
82
|
+
const archivePath = join(archiveDir, filename);
|
|
83
|
+
|
|
84
|
+
// Check if file already exists, append number if needed
|
|
85
|
+
let finalPath = archivePath;
|
|
86
|
+
let counter = 1;
|
|
87
|
+
while (existsSync(finalPath)) {
|
|
88
|
+
finalPath = join(archiveDir, filename.replace('.md', `-${counter}.md`));
|
|
89
|
+
counter++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Gather current state
|
|
93
|
+
const sections = [];
|
|
94
|
+
|
|
95
|
+
sections.push(`# Session: ${formatDate()}\n`);
|
|
96
|
+
sections.push(`## Summary\n\n${summary}\n`);
|
|
97
|
+
|
|
98
|
+
// Capture active tasks
|
|
99
|
+
if (existsSync(activePath)) {
|
|
100
|
+
const active = readFileSync(activePath, 'utf8');
|
|
101
|
+
const completedTasks = active.match(/- \[x\].*/g) || [];
|
|
102
|
+
const pendingTasks = active.match(/- \[ \].*/g) || [];
|
|
103
|
+
|
|
104
|
+
if (completedTasks.length > 0 || pendingTasks.length > 0) {
|
|
105
|
+
sections.push(`## Tasks\n`);
|
|
106
|
+
if (completedTasks.length > 0) {
|
|
107
|
+
sections.push(`### Completed\n${completedTasks.join('\n')}\n`);
|
|
108
|
+
}
|
|
109
|
+
if (pendingTasks.length > 0) {
|
|
110
|
+
sections.push(`### In Progress\n${pendingTasks.join('\n')}\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Prompt for decisions
|
|
116
|
+
if (isInteractive) {
|
|
117
|
+
console.log('\n🎯 Any decisions made? (DEC-XXX format, or press Enter to skip)');
|
|
118
|
+
const decision = await prompt('> ');
|
|
119
|
+
if (decision.trim()) {
|
|
120
|
+
sections.push(`## Decisions Made\n\n${decision}\n`);
|
|
121
|
+
|
|
122
|
+
// Optionally append to decisions.md
|
|
123
|
+
if (existsSync(decisionsPath)) {
|
|
124
|
+
const decContent = readFileSync(decisionsPath, 'utf8');
|
|
125
|
+
// Find next DEC number
|
|
126
|
+
const decMatches = decContent.match(/DEC-(\d+)/g) || [];
|
|
127
|
+
const maxNum = decMatches.reduce((max, match) => {
|
|
128
|
+
const num = parseInt(match.replace('DEC-', ''));
|
|
129
|
+
return num > max ? num : max;
|
|
130
|
+
}, 0);
|
|
131
|
+
const nextNum = String(maxNum + 1).padStart(3, '0');
|
|
132
|
+
|
|
133
|
+
const newDecision = `\n## DEC-${nextNum}: ${decision.split(':')[0] || 'Decision'}\n**Date:** ${formatDate()}\n**Context:** Session save\n**Decision:** ${decision}\n`;
|
|
134
|
+
writeFileSync(decisionsPath, decContent + newDecision);
|
|
135
|
+
console.log(` → Added DEC-${nextNum} to decisions.md`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Prompt for patterns
|
|
141
|
+
if (isInteractive) {
|
|
142
|
+
console.log('\n🔄 Any patterns discovered? (press Enter to skip)');
|
|
143
|
+
const pattern = await prompt('> ');
|
|
144
|
+
if (pattern.trim()) {
|
|
145
|
+
sections.push(`## Patterns Learned\n\n${pattern}\n`);
|
|
146
|
+
|
|
147
|
+
// Optionally append to patterns.md
|
|
148
|
+
if (existsSync(patternsPath)) {
|
|
149
|
+
const patContent = readFileSync(patternsPath, 'utf8');
|
|
150
|
+
const newPattern = `\n### ${formatDate()} Pattern\n${pattern}\n`;
|
|
151
|
+
writeFileSync(patternsPath, patContent + newPattern);
|
|
152
|
+
console.log(' → Added to patterns.md');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Prompt for dead-ends
|
|
158
|
+
if (isInteractive) {
|
|
159
|
+
console.log('\n❌ Any dead-ends to avoid? (press Enter to skip)');
|
|
160
|
+
const deadend = await prompt('> ');
|
|
161
|
+
if (deadend.trim()) {
|
|
162
|
+
sections.push(`## Dead-ends\n\n${deadend}\n`);
|
|
163
|
+
|
|
164
|
+
// Optionally append to deadends.md
|
|
165
|
+
if (existsSync(deadendsPath)) {
|
|
166
|
+
const deContent = readFileSync(deadendsPath, 'utf8');
|
|
167
|
+
// Find next DE number
|
|
168
|
+
const deMatches = deContent.match(/DE-(\d+)/g) || [];
|
|
169
|
+
const maxNum = deMatches.reduce((max, match) => {
|
|
170
|
+
const num = parseInt(match.replace('DE-', ''));
|
|
171
|
+
return num > max ? num : max;
|
|
172
|
+
}, 0);
|
|
173
|
+
const nextNum = String(maxNum + 1).padStart(3, '0');
|
|
174
|
+
|
|
175
|
+
const newDeadend = `\n## DE-${nextNum}: ${deadend.split(':')[0] || 'Dead-end'}\n**Date:** ${formatDate()}\n**Attempted:** ${deadend}\n**Problem:** See above\n**Solution:** TBD\n`;
|
|
176
|
+
writeFileSync(deadendsPath, deContent + newDeadend);
|
|
177
|
+
console.log(` → Added DE-${nextNum} to deadends.md`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add timestamp
|
|
183
|
+
sections.push(`\n---\n*Saved: ${new Date().toISOString()}*\n`);
|
|
184
|
+
|
|
185
|
+
// Write archive file
|
|
186
|
+
writeFileSync(finalPath, sections.join('\n'));
|
|
187
|
+
|
|
188
|
+
// Update state.json
|
|
189
|
+
if (existsSync(statePath)) {
|
|
190
|
+
try {
|
|
191
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
192
|
+
state.last_save = new Date().toISOString();
|
|
193
|
+
state.last_save_file = finalPath.replace(cwd, '').replace(/^[\/\\]/, '');
|
|
194
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
195
|
+
} catch {
|
|
196
|
+
// Ignore state errors
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(`\n✅ Session saved to ${finalPath.replace(cwd, '').replace(/^[\/\\]/, '')}`);
|
|
201
|
+
console.log('\n📖 Next session will have access to:');
|
|
202
|
+
console.log(' - .ai/memory/archive/ (session history)');
|
|
203
|
+
console.log(' - .ai/memory/decisions.md');
|
|
204
|
+
console.log(' - .ai/memory/patterns.md');
|
|
205
|
+
console.log(' - .ai/memory/deadends.md\n');
|
|
206
|
+
}
|
package/cli/status.js
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { getWorkspaceInfo } from '../src/core/workspace.js';
|
|
3
|
-
import { validate } from '../src/core/validate.js';
|
|
4
|
-
|
|
5
|
-
export default async function statusCommand() {
|
|
6
|
-
const info = getWorkspaceInfo();
|
|
7
|
-
|
|
8
|
-
if (!info.exists) {
|
|
9
|
-
console.log('❌ No PWN workspace found\n');
|
|
10
|
-
console.log(' Run: pwn inject');
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Validate workspace
|
|
15
|
-
const validation = validate();
|
|
16
|
-
|
|
17
|
-
console.log('📊 PWN Workspace Status\n');
|
|
18
|
-
|
|
19
|
-
// Developer info
|
|
20
|
-
if (info.state) {
|
|
21
|
-
console.log(`👤 Developer: ${info.state.developer}`);
|
|
22
|
-
console.log(`📅 Session: ${formatDate(info.state.session_started)}`);
|
|
23
|
-
if (info.state.current_task) {
|
|
24
|
-
console.log(`🎯 Task: ${info.state.current_task}`);
|
|
25
|
-
}
|
|
26
|
-
console.log();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Tasks summary
|
|
30
|
-
console.log('📋 Tasks');
|
|
31
|
-
console.log(` Active: ${info.tasks.active.pending} pending, ${info.tasks.active.completed} completed`);
|
|
32
|
-
console.log(` Backlog: ${info.tasks.backlog.total} items`);
|
|
33
|
-
console.log();
|
|
34
|
-
|
|
35
|
-
// Memory summary
|
|
36
|
-
console.log('🧠 Memory');
|
|
37
|
-
console.log(` Decisions: ${info.memory.decisions}`);
|
|
38
|
-
console.log(` Patterns: ${info.memory.patterns}`);
|
|
39
|
-
console.log(` Dead-ends: ${info.memory.deadends}`);
|
|
40
|
-
console.log();
|
|
41
|
-
|
|
42
|
-
// Patterns summary
|
|
43
|
-
console.log('🎨 Patterns');
|
|
44
|
-
console.log(` Triggers: ${info.patterns.triggers}`);
|
|
45
|
-
console.log(` Categories: ${info.patterns.categories.join(', ') || 'none'}`);
|
|
46
|
-
console.log();
|
|
47
|
-
|
|
48
|
-
// Validation status
|
|
49
|
-
if (validation.valid) {
|
|
50
|
-
console.log('✅ Workspace structure valid');
|
|
51
|
-
} else {
|
|
52
|
-
console.log('⚠️ Workspace has issues:');
|
|
53
|
-
for (const issue of validation.issues) {
|
|
54
|
-
console.log(` - ${issue}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (validation.warnings.length > 0) {
|
|
59
|
-
console.log('\n⚠️ Warnings:');
|
|
60
|
-
for (const warning of validation.warnings) {
|
|
61
|
-
console.log(` - ${warning}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Format ISO date to human readable
|
|
68
|
-
* @param {string} isoDate - ISO date string
|
|
69
|
-
* @returns {string} Formatted date
|
|
70
|
-
*/
|
|
71
|
-
function formatDate(isoDate) {
|
|
72
|
-
if (!isoDate) return 'unknown';
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const date = new Date(isoDate);
|
|
76
|
-
const now = new Date();
|
|
77
|
-
const diffMs = now - date;
|
|
78
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
79
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
80
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
81
|
-
|
|
82
|
-
if (diffMins < 1) return 'just now';
|
|
83
|
-
if (diffMins < 60) return `${diffMins}m ago`;
|
|
84
|
-
if (diffHours < 24) return `${diffHours}h ago`;
|
|
85
|
-
if (diffDays < 7) return `${diffDays}d ago`;
|
|
86
|
-
|
|
87
|
-
return date.toLocaleDateString();
|
|
88
|
-
} catch {
|
|
89
|
-
return isoDate;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getWorkspaceInfo } from '../src/core/workspace.js';
|
|
3
|
+
import { validate } from '../src/core/validate.js';
|
|
4
|
+
|
|
5
|
+
export default async function statusCommand() {
|
|
6
|
+
const info = getWorkspaceInfo();
|
|
7
|
+
|
|
8
|
+
if (!info.exists) {
|
|
9
|
+
console.log('❌ No PWN workspace found\n');
|
|
10
|
+
console.log(' Run: pwn inject');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Validate workspace
|
|
15
|
+
const validation = validate();
|
|
16
|
+
|
|
17
|
+
console.log('📊 PWN Workspace Status\n');
|
|
18
|
+
|
|
19
|
+
// Developer info
|
|
20
|
+
if (info.state) {
|
|
21
|
+
console.log(`👤 Developer: ${info.state.developer}`);
|
|
22
|
+
console.log(`📅 Session: ${formatDate(info.state.session_started)}`);
|
|
23
|
+
if (info.state.current_task) {
|
|
24
|
+
console.log(`🎯 Task: ${info.state.current_task}`);
|
|
25
|
+
}
|
|
26
|
+
console.log();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Tasks summary
|
|
30
|
+
console.log('📋 Tasks');
|
|
31
|
+
console.log(` Active: ${info.tasks.active.pending} pending, ${info.tasks.active.completed} completed`);
|
|
32
|
+
console.log(` Backlog: ${info.tasks.backlog.total} items`);
|
|
33
|
+
console.log();
|
|
34
|
+
|
|
35
|
+
// Memory summary
|
|
36
|
+
console.log('🧠 Memory');
|
|
37
|
+
console.log(` Decisions: ${info.memory.decisions}`);
|
|
38
|
+
console.log(` Patterns: ${info.memory.patterns}`);
|
|
39
|
+
console.log(` Dead-ends: ${info.memory.deadends}`);
|
|
40
|
+
console.log();
|
|
41
|
+
|
|
42
|
+
// Patterns summary
|
|
43
|
+
console.log('🎨 Patterns');
|
|
44
|
+
console.log(` Triggers: ${info.patterns.triggers}`);
|
|
45
|
+
console.log(` Categories: ${info.patterns.categories.join(', ') || 'none'}`);
|
|
46
|
+
console.log();
|
|
47
|
+
|
|
48
|
+
// Validation status
|
|
49
|
+
if (validation.valid) {
|
|
50
|
+
console.log('✅ Workspace structure valid');
|
|
51
|
+
} else {
|
|
52
|
+
console.log('⚠️ Workspace has issues:');
|
|
53
|
+
for (const issue of validation.issues) {
|
|
54
|
+
console.log(` - ${issue}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (validation.warnings.length > 0) {
|
|
59
|
+
console.log('\n⚠️ Warnings:');
|
|
60
|
+
for (const warning of validation.warnings) {
|
|
61
|
+
console.log(` - ${warning}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format ISO date to human readable
|
|
68
|
+
* @param {string} isoDate - ISO date string
|
|
69
|
+
* @returns {string} Formatted date
|
|
70
|
+
*/
|
|
71
|
+
function formatDate(isoDate) {
|
|
72
|
+
if (!isoDate) return 'unknown';
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const date = new Date(isoDate);
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const diffMs = now - date;
|
|
78
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
79
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
80
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
81
|
+
|
|
82
|
+
if (diffMins < 1) return 'just now';
|
|
83
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
84
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
85
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
86
|
+
|
|
87
|
+
return date.toLocaleDateString();
|
|
88
|
+
} catch {
|
|
89
|
+
return isoDate;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/cli/update.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, cpSync, renameSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Files that should be updated (framework files)
|
|
10
|
+
* These are safe to overwrite - they don't contain user data
|
|
11
|
+
*/
|
|
12
|
+
const FRAMEWORK_FILES = [
|
|
13
|
+
'agents/claude.md',
|
|
14
|
+
'agents/README.md',
|
|
15
|
+
'patterns/index.md',
|
|
16
|
+
'patterns/frontend/README.md',
|
|
17
|
+
'patterns/backend/README.md',
|
|
18
|
+
'patterns/universal/README.md',
|
|
19
|
+
'workflows/batch-task.md',
|
|
20
|
+
'config/README.md',
|
|
21
|
+
'README.md',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Files that should NOT be updated (user data)
|
|
26
|
+
* These contain user customizations
|
|
27
|
+
*/
|
|
28
|
+
const USER_FILES = [
|
|
29
|
+
'memory/decisions.md',
|
|
30
|
+
'memory/patterns.md',
|
|
31
|
+
'memory/deadends.md',
|
|
32
|
+
'memory/archive/',
|
|
33
|
+
'tasks/active.md',
|
|
34
|
+
'tasks/backlog.md',
|
|
35
|
+
'state.json',
|
|
36
|
+
'config/notifications.json',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export default async function updateCommand(args = []) {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const aiDir = join(cwd, '.ai');
|
|
42
|
+
const dryRun = args.includes('--dry-run');
|
|
43
|
+
|
|
44
|
+
console.log('🔄 PWN Update\n');
|
|
45
|
+
|
|
46
|
+
// Check if .ai/ exists
|
|
47
|
+
if (!existsSync(aiDir)) {
|
|
48
|
+
console.log('❌ No .ai/ directory found');
|
|
49
|
+
console.log(' Run "pwn inject" first to initialize workspace');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get template path
|
|
54
|
+
const templateDir = join(__dirname, '../templates/workspace/.ai');
|
|
55
|
+
|
|
56
|
+
if (!existsSync(templateDir)) {
|
|
57
|
+
console.log('❌ Template directory not found');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Get installed version from state.json or README
|
|
62
|
+
let currentVersion = 'unknown';
|
|
63
|
+
const statePath = join(aiDir, 'state.json');
|
|
64
|
+
if (existsSync(statePath)) {
|
|
65
|
+
try {
|
|
66
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
67
|
+
currentVersion = state.pwn_version || 'unknown';
|
|
68
|
+
} catch {
|
|
69
|
+
// Ignore
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Get new version from package.json
|
|
74
|
+
const packagePath = join(__dirname, '../package.json');
|
|
75
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
76
|
+
const newVersion = pkg.version;
|
|
77
|
+
|
|
78
|
+
console.log(` Current: ${currentVersion}`);
|
|
79
|
+
console.log(` New: ${newVersion}\n`);
|
|
80
|
+
|
|
81
|
+
if (dryRun) {
|
|
82
|
+
console.log('📋 Dry run - showing what would be updated:\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const updated = [];
|
|
86
|
+
const skipped = [];
|
|
87
|
+
const backed_up = [];
|
|
88
|
+
|
|
89
|
+
// Update framework files in .ai/
|
|
90
|
+
for (const file of FRAMEWORK_FILES) {
|
|
91
|
+
const templateFile = join(templateDir, file);
|
|
92
|
+
const targetFile = join(aiDir, file);
|
|
93
|
+
|
|
94
|
+
if (!existsSync(templateFile)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const templateContent = readFileSync(templateFile, 'utf8');
|
|
99
|
+
const targetExists = existsSync(targetFile);
|
|
100
|
+
const targetContent = targetExists ? readFileSync(targetFile, 'utf8') : '';
|
|
101
|
+
|
|
102
|
+
if (templateContent !== targetContent) {
|
|
103
|
+
if (dryRun) {
|
|
104
|
+
console.log(` 📝 Would update: .ai/${file}`);
|
|
105
|
+
} else {
|
|
106
|
+
// Ensure directory exists
|
|
107
|
+
const dir = dirname(targetFile);
|
|
108
|
+
if (!existsSync(dir)) {
|
|
109
|
+
const { mkdirSync } = await import('fs');
|
|
110
|
+
mkdirSync(dir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
writeFileSync(targetFile, templateContent);
|
|
113
|
+
console.log(` 📝 Updated: .ai/${file}`);
|
|
114
|
+
}
|
|
115
|
+
updated.push(file);
|
|
116
|
+
} else {
|
|
117
|
+
skipped.push(file);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Update CLAUDE.md in root
|
|
122
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
123
|
+
const templateClaudeMd = join(templateDir, 'agents', 'claude.md');
|
|
124
|
+
const backupClaudeMdPath = join(cwd, '~CLAUDE.md');
|
|
125
|
+
|
|
126
|
+
if (existsSync(templateClaudeMd)) {
|
|
127
|
+
const templateContent = readFileSync(templateClaudeMd, 'utf8');
|
|
128
|
+
const currentContent = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, 'utf8') : '';
|
|
129
|
+
|
|
130
|
+
if (templateContent !== currentContent) {
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
console.log(` 📝 Would update: CLAUDE.md`);
|
|
133
|
+
if (currentContent) {
|
|
134
|
+
console.log(` 📦 Would backup: CLAUDE.md → ~CLAUDE.md`);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Backup existing CLAUDE.md
|
|
138
|
+
if (existsSync(claudeMdPath) && currentContent) {
|
|
139
|
+
renameSync(claudeMdPath, backupClaudeMdPath);
|
|
140
|
+
console.log(` 📦 Backed up: CLAUDE.md → ~CLAUDE.md`);
|
|
141
|
+
backed_up.push('CLAUDE.md');
|
|
142
|
+
}
|
|
143
|
+
// Copy new template
|
|
144
|
+
writeFileSync(claudeMdPath, templateContent);
|
|
145
|
+
console.log(` 📝 Updated: CLAUDE.md`);
|
|
146
|
+
}
|
|
147
|
+
updated.push('CLAUDE.md');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Update state.json with new version
|
|
152
|
+
if (!dryRun && existsSync(statePath)) {
|
|
153
|
+
try {
|
|
154
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
155
|
+
state.pwn_version = newVersion;
|
|
156
|
+
state.last_updated = new Date().toISOString();
|
|
157
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
158
|
+
} catch {
|
|
159
|
+
// Ignore
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Summary
|
|
164
|
+
console.log('');
|
|
165
|
+
if (updated.length === 0) {
|
|
166
|
+
console.log('✅ Already up to date!\n');
|
|
167
|
+
} else if (dryRun) {
|
|
168
|
+
console.log(`📋 Would update ${updated.length} file(s)\n`);
|
|
169
|
+
console.log(' Run without --dry-run to apply updates');
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`✅ Updated ${updated.length} file(s)\n`);
|
|
172
|
+
|
|
173
|
+
if (backed_up.length > 0) {
|
|
174
|
+
console.log('📦 Backed up files:');
|
|
175
|
+
for (const f of backed_up) {
|
|
176
|
+
console.log(` ${f} → ~${f}`);
|
|
177
|
+
}
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log(' 💡 Merge your custom instructions into CLAUDE.md');
|
|
180
|
+
console.log(' 🗑️ Delete backup files when done\n');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Show what was preserved
|
|
185
|
+
console.log('🔒 Preserved (user data):');
|
|
186
|
+
console.log(' .ai/memory/ (decisions, patterns, deadends)');
|
|
187
|
+
console.log(' .ai/tasks/ (active, backlog)');
|
|
188
|
+
console.log(' .ai/state.json (session state)\n');
|
|
189
|
+
}
|