@fermindi/pwn-cli 0.1.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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/cli/batch.js +333 -0
  4. package/cli/codespaces.js +303 -0
  5. package/cli/index.js +91 -0
  6. package/cli/inject.js +53 -0
  7. package/cli/knowledge.js +531 -0
  8. package/cli/notify.js +135 -0
  9. package/cli/patterns.js +665 -0
  10. package/cli/status.js +91 -0
  11. package/cli/validate.js +61 -0
  12. package/package.json +70 -0
  13. package/src/core/inject.js +128 -0
  14. package/src/core/state.js +91 -0
  15. package/src/core/validate.js +202 -0
  16. package/src/core/workspace.js +176 -0
  17. package/src/index.js +20 -0
  18. package/src/knowledge/gc.js +308 -0
  19. package/src/knowledge/lifecycle.js +401 -0
  20. package/src/knowledge/promote.js +364 -0
  21. package/src/knowledge/references.js +342 -0
  22. package/src/patterns/matcher.js +218 -0
  23. package/src/patterns/registry.js +375 -0
  24. package/src/patterns/triggers.js +423 -0
  25. package/src/services/batch-service.js +849 -0
  26. package/src/services/notification-service.js +342 -0
  27. package/templates/codespaces/devcontainer.json +52 -0
  28. package/templates/codespaces/setup.sh +70 -0
  29. package/templates/workspace/.ai/README.md +164 -0
  30. package/templates/workspace/.ai/agents/README.md +204 -0
  31. package/templates/workspace/.ai/agents/claude.md +625 -0
  32. package/templates/workspace/.ai/config/.gitkeep +0 -0
  33. package/templates/workspace/.ai/config/README.md +79 -0
  34. package/templates/workspace/.ai/config/notifications.template.json +20 -0
  35. package/templates/workspace/.ai/memory/deadends.md +79 -0
  36. package/templates/workspace/.ai/memory/decisions.md +58 -0
  37. package/templates/workspace/.ai/memory/patterns.md +65 -0
  38. package/templates/workspace/.ai/patterns/backend/README.md +126 -0
  39. package/templates/workspace/.ai/patterns/frontend/README.md +103 -0
  40. package/templates/workspace/.ai/patterns/index.md +256 -0
  41. package/templates/workspace/.ai/patterns/triggers.json +1087 -0
  42. package/templates/workspace/.ai/patterns/universal/README.md +141 -0
  43. package/templates/workspace/.ai/state.template.json +8 -0
  44. package/templates/workspace/.ai/tasks/active.md +77 -0
  45. package/templates/workspace/.ai/tasks/backlog.md +95 -0
  46. package/templates/workspace/.ai/workflows/batch-task.md +356 -0
package/cli/status.js ADDED
@@ -0,0 +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
+ }
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ import { validate, getStructureReport } from '../src/core/validate.js';
3
+
4
+ export default async function validateCommand(args = []) {
5
+ const verbose = args.includes('--verbose') || args.includes('-v');
6
+
7
+ console.log('šŸ” Validating PWN workspace...\n');
8
+
9
+ const result = validate();
10
+
11
+ if (verbose) {
12
+ const report = getStructureReport();
13
+ console.log('šŸ“ Structure Report:\n');
14
+
15
+ console.log(' Directories:');
16
+ for (const [dir, exists] of Object.entries(report.directories)) {
17
+ const icon = exists ? 'āœ“' : 'āœ—';
18
+ console.log(` ${icon} .ai/${dir}/`);
19
+ }
20
+
21
+ console.log('\n Files:');
22
+ for (const [file, exists] of Object.entries(report.files)) {
23
+ const icon = exists ? 'āœ“' : 'āœ—';
24
+ console.log(` ${icon} .ai/${file}`);
25
+ }
26
+ console.log();
27
+ }
28
+
29
+ if (result.valid) {
30
+ console.log('āœ… Workspace is valid\n');
31
+
32
+ if (result.warnings.length > 0) {
33
+ console.log('āš ļø Warnings:');
34
+ for (const warning of result.warnings) {
35
+ console.log(` - ${warning}`);
36
+ }
37
+ console.log();
38
+ }
39
+
40
+ process.exit(0);
41
+ } else {
42
+ console.log('āŒ Workspace has issues:\n');
43
+
44
+ for (const issue of result.issues) {
45
+ console.log(` āœ— ${issue}`);
46
+ }
47
+
48
+ if (result.warnings.length > 0) {
49
+ console.log('\nāš ļø Warnings:');
50
+ for (const warning of result.warnings) {
51
+ console.log(` - ${warning}`);
52
+ }
53
+ }
54
+
55
+ console.log('\nšŸ’” To fix, try:');
56
+ console.log(' pwn inject --force (recreate workspace)');
57
+ console.log();
58
+
59
+ process.exit(1);
60
+ }
61
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@fermindi/pwn-cli",
3
+ "version": "0.1.0",
4
+ "description": "Professional AI Workspace - Inject structured memory and automation into any project for AI-powered development",
5
+ "type": "module",
6
+ "bin": {
7
+ "pwn": "./cli/index.js"
8
+ },
9
+ "main": "./src/index.js",
10
+ "exports": {
11
+ ".": "./src/index.js",
12
+ "./core/state": "./src/core/state.js",
13
+ "./core/inject": "./src/core/inject.js",
14
+ "./core/validate": "./src/core/validate.js",
15
+ "./core/workspace": "./src/core/workspace.js",
16
+ "./services/batch": "./src/services/batch-service.js",
17
+ "./services/notifications": "./src/services/notification-service.js",
18
+ "./patterns/registry": "./src/patterns/registry.js",
19
+ "./patterns/triggers": "./src/patterns/triggers.js",
20
+ "./knowledge/lifecycle": "./src/knowledge/lifecycle.js",
21
+ "./knowledge/references": "./src/knowledge/references.js",
22
+ "./knowledge/gc": "./src/knowledge/gc.js",
23
+ "./knowledge/promote": "./src/knowledge/promote.js"
24
+ },
25
+ "files": [
26
+ "cli/",
27
+ "src/",
28
+ "templates/",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "scripts": {
33
+ "start": "node cli/index.js",
34
+ "dev": "node --watch cli/index.js",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "test:coverage": "vitest run --coverage",
38
+ "prepublishOnly": "npm test"
39
+ },
40
+ "keywords": [
41
+ "ai",
42
+ "cli",
43
+ "workspace",
44
+ "automation",
45
+ "claude",
46
+ "patterns",
47
+ "memory",
48
+ "decisions",
49
+ "batch",
50
+ "codespaces",
51
+ "developer-tools",
52
+ "productivity"
53
+ ],
54
+ "author": "Diego Fernandes",
55
+ "license": "MIT",
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "git+https://github.com/fermindi/pwn.git"
59
+ },
60
+ "bugs": {
61
+ "url": "https://github.com/fermindi/pwn/issues"
62
+ },
63
+ "homepage": "https://github.com/fermindi/pwn#readme",
64
+ "engines": {
65
+ "node": ">=18.0.0"
66
+ },
67
+ "devDependencies": {
68
+ "vitest": "^2.0.0"
69
+ }
70
+ }
@@ -0,0 +1,128 @@
1
+ import { existsSync, cpSync, renameSync, readFileSync, appendFileSync, writeFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { randomUUID } from 'crypto';
5
+ import { initState } from './state.js';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ /**
10
+ * Get the path to the workspace template
11
+ * @returns {string} Path to template directory
12
+ */
13
+ export function getTemplatePath() {
14
+ return join(__dirname, '../../templates/workspace/.ai');
15
+ }
16
+
17
+ /**
18
+ * Inject PWN workspace into a project
19
+ * @param {object} options - Injection options
20
+ * @param {string} options.cwd - Target directory (defaults to process.cwd())
21
+ * @param {boolean} options.force - Force overwrite existing .ai/ directory
22
+ * @param {boolean} options.silent - Suppress console output
23
+ * @returns {object} Result with success status and message
24
+ */
25
+ export async function inject(options = {}) {
26
+ const {
27
+ cwd = process.cwd(),
28
+ force = false,
29
+ silent = false
30
+ } = options;
31
+
32
+ const templateDir = getTemplatePath();
33
+ const targetDir = join(cwd, '.ai');
34
+
35
+ const log = silent ? () => {} : console.log;
36
+
37
+ // Check if .ai/ already exists
38
+ if (existsSync(targetDir) && !force) {
39
+ return {
40
+ success: false,
41
+ error: 'ALREADY_EXISTS',
42
+ message: '.ai/ directory already exists. Use --force to overwrite.'
43
+ };
44
+ }
45
+
46
+ try {
47
+ // Copy workspace template
48
+ log('šŸ“¦ Copying workspace template...');
49
+ cpSync(templateDir, targetDir, { recursive: true });
50
+
51
+ // Rename state.template.json → state.json
52
+ const templateState = join(targetDir, 'state.template.json');
53
+ const stateFile = join(targetDir, 'state.json');
54
+
55
+ if (existsSync(templateState)) {
56
+ renameSync(templateState, stateFile);
57
+ }
58
+
59
+ // Rename notifications.template.json → notifications.json and generate unique topic
60
+ const templateNotify = join(targetDir, 'config', 'notifications.template.json');
61
+ const notifyFile = join(targetDir, 'config', 'notifications.json');
62
+
63
+ if (existsSync(templateNotify)) {
64
+ renameSync(templateNotify, notifyFile);
65
+ initNotifications(notifyFile);
66
+ }
67
+
68
+ // Initialize state.json with current user
69
+ initState(cwd);
70
+
71
+ // Update .gitignore
72
+ updateGitignore(cwd, silent);
73
+
74
+ return {
75
+ success: true,
76
+ message: 'PWN workspace injected successfully',
77
+ path: targetDir
78
+ };
79
+
80
+ } catch (error) {
81
+ return {
82
+ success: false,
83
+ error: 'INJECTION_FAILED',
84
+ message: error.message
85
+ };
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Initialize notifications.json with unique topic
91
+ * @param {string} notifyFile - Path to notifications.json
92
+ */
93
+ function initNotifications(notifyFile) {
94
+ try {
95
+ const content = readFileSync(notifyFile, 'utf8');
96
+ const config = JSON.parse(content);
97
+
98
+ // Generate unique topic ID
99
+ const uniqueId = randomUUID().split('-')[0]; // First segment: 8 chars
100
+ config.channels.ntfy.topic = `pwn-${uniqueId}`;
101
+
102
+ writeFileSync(notifyFile, JSON.stringify(config, null, 2));
103
+ } catch {
104
+ // Ignore errors - notifications will use defaults
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Update .gitignore to exclude PWN personal files
110
+ * @param {string} cwd - Working directory
111
+ * @param {boolean} silent - Suppress output
112
+ */
113
+ function updateGitignore(cwd, silent = false) {
114
+ const gitignorePath = join(cwd, '.gitignore');
115
+ let gitignoreContent = '';
116
+
117
+ if (existsSync(gitignorePath)) {
118
+ gitignoreContent = readFileSync(gitignorePath, 'utf8');
119
+ }
120
+
121
+ if (!gitignoreContent.includes('.ai/state.json')) {
122
+ const pwnSection = '\n# PWN\n.ai/state.json\n.ai/config/notifications.json\n';
123
+ appendFileSync(gitignorePath, pwnSection);
124
+ if (!silent) {
125
+ console.log('šŸ“ Updated .gitignore');
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,91 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+
5
+ /**
6
+ * Get the path to state.json in a workspace
7
+ * @param {string} cwd - Working directory (defaults to process.cwd())
8
+ * @returns {string} Path to state.json
9
+ */
10
+ export function getStatePath(cwd = process.cwd()) {
11
+ return join(cwd, '.ai', 'state.json');
12
+ }
13
+
14
+ /**
15
+ * Check if a PWN workspace exists in the given directory
16
+ * @param {string} cwd - Working directory
17
+ * @returns {boolean}
18
+ */
19
+ export function hasWorkspace(cwd = process.cwd()) {
20
+ return existsSync(join(cwd, '.ai'));
21
+ }
22
+
23
+ /**
24
+ * Get the current git username
25
+ * @returns {string} Git username or 'unknown'
26
+ */
27
+ export function getGitUser() {
28
+ try {
29
+ return execSync('git config user.name', { encoding: 'utf8' }).trim();
30
+ } catch {
31
+ return 'unknown';
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Read the current state from state.json
37
+ * @param {string} cwd - Working directory
38
+ * @returns {object|null} State object or null if not found
39
+ */
40
+ export function getState(cwd = process.cwd()) {
41
+ const statePath = getStatePath(cwd);
42
+
43
+ if (!existsSync(statePath)) {
44
+ return null;
45
+ }
46
+
47
+ try {
48
+ const content = readFileSync(statePath, 'utf8');
49
+ return JSON.parse(content);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Update the state.json file
57
+ * @param {object} updates - Fields to update
58
+ * @param {string} cwd - Working directory
59
+ * @returns {object} Updated state
60
+ */
61
+ export function updateState(updates, cwd = process.cwd()) {
62
+ const statePath = getStatePath(cwd);
63
+ const currentState = getState(cwd) || {};
64
+
65
+ const newState = {
66
+ ...currentState,
67
+ ...updates,
68
+ last_updated: new Date().toISOString()
69
+ };
70
+
71
+ writeFileSync(statePath, JSON.stringify(newState, null, 2));
72
+ return newState;
73
+ }
74
+
75
+ /**
76
+ * Initialize a new state.json file
77
+ * @param {string} cwd - Working directory
78
+ * @returns {object} Initial state
79
+ */
80
+ export function initState(cwd = process.cwd()) {
81
+ const state = {
82
+ developer: getGitUser(),
83
+ session_started: new Date().toISOString(),
84
+ current_task: null,
85
+ context_loaded: []
86
+ };
87
+
88
+ const statePath = getStatePath(cwd);
89
+ writeFileSync(statePath, JSON.stringify(state, null, 2));
90
+ return state;
91
+ }
@@ -0,0 +1,202 @@
1
+ import { existsSync, statSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ /**
5
+ * Expected workspace structure
6
+ */
7
+ const REQUIRED_STRUCTURE = {
8
+ files: [
9
+ 'README.md',
10
+ 'state.json'
11
+ ],
12
+ directories: [
13
+ 'memory',
14
+ 'tasks',
15
+ 'patterns',
16
+ 'workflows',
17
+ 'agents',
18
+ 'config'
19
+ ],
20
+ memoryFiles: [
21
+ 'memory/decisions.md',
22
+ 'memory/patterns.md',
23
+ 'memory/deadends.md'
24
+ ],
25
+ taskFiles: [
26
+ 'tasks/active.md',
27
+ 'tasks/backlog.md'
28
+ ],
29
+ agentFiles: [
30
+ 'agents/README.md',
31
+ 'agents/claude.md'
32
+ ],
33
+ patternFiles: [
34
+ 'patterns/index.md'
35
+ ]
36
+ };
37
+
38
+ /**
39
+ * Validate a PWN workspace structure
40
+ * @param {string} cwd - Working directory
41
+ * @returns {object} Validation result with issues array
42
+ */
43
+ export function validate(cwd = process.cwd()) {
44
+ const aiDir = join(cwd, '.ai');
45
+ const issues = [];
46
+ const warnings = [];
47
+
48
+ // Check if .ai/ exists
49
+ if (!existsSync(aiDir)) {
50
+ return {
51
+ valid: false,
52
+ issues: ['No .ai/ directory found. Run: pwn inject'],
53
+ warnings: []
54
+ };
55
+ }
56
+
57
+ // Check required files
58
+ for (const file of REQUIRED_STRUCTURE.files) {
59
+ const filePath = join(aiDir, file);
60
+ if (!existsSync(filePath)) {
61
+ issues.push(`Missing file: .ai/${file}`);
62
+ }
63
+ }
64
+
65
+ // Check required directories
66
+ for (const dir of REQUIRED_STRUCTURE.directories) {
67
+ const dirPath = join(aiDir, dir);
68
+ if (!existsSync(dirPath)) {
69
+ issues.push(`Missing directory: .ai/${dir}/`);
70
+ } else if (!statSync(dirPath).isDirectory()) {
71
+ issues.push(`.ai/${dir} exists but is not a directory`);
72
+ }
73
+ }
74
+
75
+ // Check memory files
76
+ for (const file of REQUIRED_STRUCTURE.memoryFiles) {
77
+ const filePath = join(aiDir, file);
78
+ if (!existsSync(filePath)) {
79
+ warnings.push(`Missing memory file: .ai/${file}`);
80
+ }
81
+ }
82
+
83
+ // Check task files
84
+ for (const file of REQUIRED_STRUCTURE.taskFiles) {
85
+ const filePath = join(aiDir, file);
86
+ if (!existsSync(filePath)) {
87
+ warnings.push(`Missing task file: .ai/${file}`);
88
+ }
89
+ }
90
+
91
+ // Check agent files
92
+ for (const file of REQUIRED_STRUCTURE.agentFiles) {
93
+ const filePath = join(aiDir, file);
94
+ if (!existsSync(filePath)) {
95
+ warnings.push(`Missing agent file: .ai/${file}`);
96
+ }
97
+ }
98
+
99
+ // Check pattern files
100
+ for (const file of REQUIRED_STRUCTURE.patternFiles) {
101
+ const filePath = join(aiDir, file);
102
+ if (!existsSync(filePath)) {
103
+ warnings.push(`Missing pattern file: .ai/${file}`);
104
+ }
105
+ }
106
+
107
+ // Validate state.json format
108
+ const stateValidation = validateStateJson(aiDir);
109
+ if (!stateValidation.valid) {
110
+ issues.push(...stateValidation.issues);
111
+ }
112
+
113
+ return {
114
+ valid: issues.length === 0,
115
+ issues,
116
+ warnings
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Validate state.json structure and content
122
+ * @param {string} aiDir - Path to .ai directory
123
+ * @returns {object} Validation result
124
+ */
125
+ function validateStateJson(aiDir) {
126
+ const statePath = join(aiDir, 'state.json');
127
+ const issues = [];
128
+
129
+ if (!existsSync(statePath)) {
130
+ return { valid: false, issues: ['state.json not found'] };
131
+ }
132
+
133
+ try {
134
+ const content = readFileSync(statePath, 'utf8');
135
+ const state = JSON.parse(content);
136
+
137
+ // Check required fields
138
+ const requiredFields = ['developer', 'session_started'];
139
+ for (const field of requiredFields) {
140
+ if (!(field in state)) {
141
+ issues.push(`state.json missing required field: ${field}`);
142
+ }
143
+ }
144
+
145
+ // Validate date format
146
+ if (state.session_started && isNaN(Date.parse(state.session_started))) {
147
+ issues.push('state.json: session_started is not a valid date');
148
+ }
149
+
150
+ } catch (error) {
151
+ if (error instanceof SyntaxError) {
152
+ issues.push('state.json contains invalid JSON');
153
+ } else {
154
+ issues.push(`state.json read error: ${error.message}`);
155
+ }
156
+ }
157
+
158
+ return {
159
+ valid: issues.length === 0,
160
+ issues
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Get a detailed report of workspace structure
166
+ * @param {string} cwd - Working directory
167
+ * @returns {object} Structure report
168
+ */
169
+ export function getStructureReport(cwd = process.cwd()) {
170
+ const aiDir = join(cwd, '.ai');
171
+ const report = {
172
+ exists: existsSync(aiDir),
173
+ directories: {},
174
+ files: {}
175
+ };
176
+
177
+ if (!report.exists) {
178
+ return report;
179
+ }
180
+
181
+ // Check directories
182
+ for (const dir of REQUIRED_STRUCTURE.directories) {
183
+ const dirPath = join(aiDir, dir);
184
+ report.directories[dir] = existsSync(dirPath) && statSync(dirPath).isDirectory();
185
+ }
186
+
187
+ // Check all expected files
188
+ const allFiles = [
189
+ ...REQUIRED_STRUCTURE.files,
190
+ ...REQUIRED_STRUCTURE.memoryFiles,
191
+ ...REQUIRED_STRUCTURE.taskFiles,
192
+ ...REQUIRED_STRUCTURE.agentFiles,
193
+ ...REQUIRED_STRUCTURE.patternFiles
194
+ ];
195
+
196
+ for (const file of allFiles) {
197
+ const filePath = join(aiDir, file);
198
+ report.files[file] = existsSync(filePath);
199
+ }
200
+
201
+ return report;
202
+ }