@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.
Files changed (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +265 -251
  3. package/cli/batch.js +333 -333
  4. package/cli/codespaces.js +303 -303
  5. package/cli/index.js +112 -91
  6. package/cli/inject.js +90 -67
  7. package/cli/knowledge.js +531 -531
  8. package/cli/migrate.js +466 -0
  9. package/cli/notify.js +135 -135
  10. package/cli/patterns.js +665 -665
  11. package/cli/save.js +206 -0
  12. package/cli/status.js +91 -91
  13. package/cli/update.js +189 -0
  14. package/cli/validate.js +61 -61
  15. package/package.json +70 -70
  16. package/src/core/inject.js +300 -204
  17. package/src/core/state.js +91 -91
  18. package/src/core/validate.js +202 -202
  19. package/src/core/workspace.js +176 -176
  20. package/src/index.js +20 -20
  21. package/src/knowledge/gc.js +308 -308
  22. package/src/knowledge/lifecycle.js +401 -401
  23. package/src/knowledge/promote.js +364 -364
  24. package/src/knowledge/references.js +342 -342
  25. package/src/patterns/matcher.js +218 -218
  26. package/src/patterns/registry.js +375 -375
  27. package/src/patterns/triggers.js +423 -423
  28. package/src/services/batch-service.js +849 -849
  29. package/src/services/notification-service.js +342 -342
  30. package/templates/codespaces/devcontainer.json +52 -52
  31. package/templates/codespaces/setup.sh +70 -70
  32. package/templates/workspace/.ai/README.md +164 -164
  33. package/templates/workspace/.ai/agents/README.md +204 -204
  34. package/templates/workspace/.ai/agents/claude.md +625 -625
  35. package/templates/workspace/.ai/config/README.md +79 -79
  36. package/templates/workspace/.ai/config/notifications.template.json +20 -20
  37. package/templates/workspace/.ai/memory/deadends.md +79 -79
  38. package/templates/workspace/.ai/memory/decisions.md +58 -58
  39. package/templates/workspace/.ai/memory/patterns.md +65 -65
  40. package/templates/workspace/.ai/patterns/backend/README.md +126 -126
  41. package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
  42. package/templates/workspace/.ai/patterns/index.md +256 -256
  43. package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
  44. package/templates/workspace/.ai/patterns/universal/README.md +141 -141
  45. package/templates/workspace/.ai/state.template.json +8 -8
  46. package/templates/workspace/.ai/tasks/active.md +77 -77
  47. package/templates/workspace/.ai/tasks/backlog.md +95 -95
  48. 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
+ }