aios-core 3.0.0 → 3.2.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.
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate Install Manifest
4
+ * Dynamically generates install-manifest.yaml with file hashes for brownfield upgrades
5
+ *
6
+ * @script scripts/generate-install-manifest.js
7
+ * @story 6.18 - Dynamic Manifest & Brownfield Upgrade System
8
+ *
9
+ * Usage:
10
+ * node scripts/generate-install-manifest.js
11
+ * npm run generate:manifest
12
+ */
13
+
14
+ const fs = require('fs-extra');
15
+ const path = require('path');
16
+ const yaml = require('js-yaml');
17
+ const { hashFile, hashString } = require('../src/installer/file-hasher');
18
+
19
+ // Import FOLDERS_TO_COPY from installer (same source of truth)
20
+ const FOLDERS_TO_COPY = [
21
+ // v2.1 Modular Structure
22
+ 'core',
23
+ 'development',
24
+ 'product',
25
+ 'infrastructure',
26
+ // v2.0 Legacy Flat Structure (backwards compatibility)
27
+ 'agents',
28
+ 'agent-teams',
29
+ 'checklists',
30
+ 'data',
31
+ 'docs',
32
+ 'elicitation',
33
+ 'scripts',
34
+ 'tasks',
35
+ 'templates',
36
+ 'tools',
37
+ 'workflows',
38
+ // Additional directories
39
+ 'cli',
40
+ 'manifests',
41
+ ];
42
+
43
+ const ROOT_FILES_TO_COPY = [
44
+ 'index.js',
45
+ 'index.esm.js',
46
+ 'index.d.ts',
47
+ 'core-config.yaml',
48
+ 'package.json',
49
+ 'user-guide.md',
50
+ 'working-in-the-brownfield.md',
51
+ ];
52
+
53
+ // Files/folders to exclude from manifest
54
+ const EXCLUDE_PATTERNS = [
55
+ /node_modules/,
56
+ /\.git/,
57
+ /\.DS_Store/,
58
+ /Thumbs\.db/,
59
+ /\.installed-manifest\.yaml$/, // Don't include installed manifest
60
+ /\.bak$/,
61
+ /\.tmp$/,
62
+ /~$/,
63
+ ];
64
+
65
+ /**
66
+ * Check if a path should be excluded
67
+ * @param {string} filePath - Path to check
68
+ * @returns {boolean} - True if should be excluded
69
+ */
70
+ function shouldExclude(filePath) {
71
+ return EXCLUDE_PATTERNS.some(pattern => pattern.test(filePath));
72
+ }
73
+
74
+ /**
75
+ * Determine file type based on path
76
+ * @param {string} relativePath - Relative file path
77
+ * @returns {string} - File type identifier
78
+ */
79
+ function getFileType(relativePath) {
80
+ const normalized = relativePath.replace(/\\/g, '/');
81
+
82
+ if (normalized.includes('/agents/') || normalized.startsWith('agents/')) {
83
+ return 'agent';
84
+ }
85
+ if (normalized.includes('/tasks/') || normalized.startsWith('tasks/')) {
86
+ return 'task';
87
+ }
88
+ if (normalized.includes('/workflows/') || normalized.startsWith('workflows/')) {
89
+ return 'workflow';
90
+ }
91
+ if (normalized.includes('/templates/') || normalized.startsWith('templates/')) {
92
+ return 'template';
93
+ }
94
+ if (normalized.includes('/checklists/') || normalized.startsWith('checklists/')) {
95
+ return 'checklist';
96
+ }
97
+ if (normalized.includes('/scripts/') || normalized.startsWith('scripts/')) {
98
+ return 'script';
99
+ }
100
+ if (normalized.includes('/tools/') || normalized.startsWith('tools/')) {
101
+ return 'tool';
102
+ }
103
+ if (normalized.includes('/data/') || normalized.startsWith('data/')) {
104
+ return 'data';
105
+ }
106
+ if (normalized.includes('/docs/') || normalized.startsWith('docs/')) {
107
+ return 'documentation';
108
+ }
109
+ if (normalized.includes('/elicitation/') || normalized.startsWith('elicitation/')) {
110
+ return 'elicitation';
111
+ }
112
+ if (normalized.includes('/manifests/') || normalized.startsWith('manifests/')) {
113
+ return 'manifest';
114
+ }
115
+ if (normalized.includes('/cli/') || normalized.startsWith('cli/')) {
116
+ return 'cli';
117
+ }
118
+ if (normalized.includes('/core/') || normalized.startsWith('core/')) {
119
+ return 'core';
120
+ }
121
+ if (normalized.includes('/infrastructure/') || normalized.startsWith('infrastructure/')) {
122
+ return 'infrastructure';
123
+ }
124
+ if (normalized.includes('/product/') || normalized.startsWith('product/')) {
125
+ return 'product';
126
+ }
127
+ if (normalized.includes('/development/') || normalized.startsWith('development/')) {
128
+ return 'development';
129
+ }
130
+
131
+ // Root files
132
+ if (normalized.endsWith('.js') || normalized.endsWith('.ts')) {
133
+ return 'code';
134
+ }
135
+ if (normalized.endsWith('.yaml') || normalized.endsWith('.yml')) {
136
+ return 'config';
137
+ }
138
+ if (normalized.endsWith('.md')) {
139
+ return 'documentation';
140
+ }
141
+
142
+ return 'other';
143
+ }
144
+
145
+ /**
146
+ * Recursively scan directory and collect file metadata
147
+ * @param {string} dirPath - Directory to scan
148
+ * @param {string} basePath - Base path for relative paths
149
+ * @param {string[]} files - Array to collect files
150
+ */
151
+ function scanDirectory(dirPath, basePath, files = []) {
152
+ if (!fs.existsSync(dirPath)) {
153
+ return files;
154
+ }
155
+
156
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
157
+
158
+ for (const entry of entries) {
159
+ const fullPath = path.join(dirPath, entry.name);
160
+ const relativePath = path.relative(basePath, fullPath).replace(/\\/g, '/');
161
+
162
+ if (shouldExclude(relativePath)) {
163
+ continue;
164
+ }
165
+
166
+ if (entry.isDirectory()) {
167
+ scanDirectory(fullPath, basePath, files);
168
+ } else if (entry.isFile()) {
169
+ files.push(fullPath);
170
+ }
171
+ }
172
+
173
+ return files;
174
+ }
175
+
176
+ /**
177
+ * Generate the install manifest
178
+ * @returns {Object} - Manifest object
179
+ */
180
+ async function generateManifest() {
181
+ const aiosCoreDir = path.join(__dirname, '..', '.aios-core');
182
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
183
+
184
+ if (!fs.existsSync(aiosCoreDir)) {
185
+ throw new Error(`.aios-core directory not found at ${aiosCoreDir}`);
186
+ }
187
+
188
+ // Get version from package.json
189
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
190
+ const version = packageJson.version;
191
+
192
+ console.log(`Generating manifest for aios-core v${version}...`);
193
+
194
+ const allFiles = [];
195
+
196
+ // Scan folders
197
+ for (const folder of FOLDERS_TO_COPY) {
198
+ const folderPath = path.join(aiosCoreDir, folder);
199
+ if (fs.existsSync(folderPath)) {
200
+ scanDirectory(folderPath, aiosCoreDir, allFiles);
201
+ }
202
+ }
203
+
204
+ // Add root files
205
+ for (const file of ROOT_FILES_TO_COPY) {
206
+ const filePath = path.join(aiosCoreDir, file);
207
+ if (fs.existsSync(filePath)) {
208
+ allFiles.push(filePath);
209
+ }
210
+ }
211
+
212
+ // Note: install-manifest.yaml itself is not included in the manifest
213
+ // as it would create a circular dependency during validation
214
+
215
+ console.log(`Found ${allFiles.length} files to include in manifest`);
216
+
217
+ // Generate file entries with metadata
218
+ const fileEntries = [];
219
+ let processedCount = 0;
220
+
221
+ for (const fullPath of allFiles) {
222
+ try {
223
+ const relativePath = path.relative(aiosCoreDir, fullPath).replace(/\\/g, '/');
224
+ const stats = fs.statSync(fullPath);
225
+ const hash = hashFile(fullPath);
226
+ const fileType = getFileType(relativePath);
227
+
228
+ fileEntries.push({
229
+ path: relativePath,
230
+ hash: `sha256:${hash}`,
231
+ type: fileType,
232
+ size: stats.size,
233
+ });
234
+
235
+ processedCount++;
236
+ if (processedCount % 50 === 0) {
237
+ console.log(` Processed ${processedCount}/${allFiles.length} files...`);
238
+ }
239
+ } catch (error) {
240
+ console.warn(` Warning: Could not process ${fullPath}: ${error.message}`);
241
+ }
242
+ }
243
+
244
+ // Sort files by path for consistent output
245
+ fileEntries.sort((a, b) => a.path.localeCompare(b.path));
246
+
247
+ // Build manifest object
248
+ const manifest = {
249
+ version: version,
250
+ generated_at: new Date().toISOString(),
251
+ generator: 'scripts/generate-install-manifest.js',
252
+ file_count: fileEntries.length,
253
+ files: fileEntries,
254
+ };
255
+
256
+ return manifest;
257
+ }
258
+
259
+ /**
260
+ * Write manifest to file
261
+ * @param {Object} manifest - Manifest object
262
+ */
263
+ async function writeManifest(manifest) {
264
+ const manifestPath = path.join(__dirname, '..', '.aios-core', 'install-manifest.yaml');
265
+
266
+ // Generate YAML with custom options for readability
267
+ const yamlContent = yaml.dump(manifest, {
268
+ indent: 2,
269
+ lineWidth: 120,
270
+ noRefs: true,
271
+ sortKeys: false,
272
+ quotingType: '"',
273
+ });
274
+
275
+ // Add header comment
276
+ const header = `# AIOS-Core Install Manifest
277
+ # Auto-generated by scripts/generate-install-manifest.js
278
+ # DO NOT EDIT MANUALLY - regenerate with: npm run generate:manifest
279
+ #
280
+ # This manifest is used for brownfield upgrades to track:
281
+ # - Which files are part of the framework
282
+ # - SHA256 hashes for change detection
283
+ # - File types for categorization
284
+ #
285
+ `;
286
+
287
+ fs.writeFileSync(manifestPath, header + yamlContent, 'utf8');
288
+
289
+ console.log(`\nManifest written to: ${manifestPath}`);
290
+ console.log(` Version: ${manifest.version}`);
291
+ console.log(` Files: ${manifest.file_count}`);
292
+ console.log(` Generated: ${manifest.generated_at}`);
293
+
294
+ // Also compute and display manifest hash for integrity verification
295
+ const manifestHash = hashString(yamlContent);
296
+ console.log(` Manifest hash: sha256:${manifestHash.substring(0, 16)}...`);
297
+
298
+ return manifestPath;
299
+ }
300
+
301
+ /**
302
+ * Main execution
303
+ */
304
+ async function main() {
305
+ try {
306
+ console.log('='.repeat(60));
307
+ console.log('AIOS-Core Install Manifest Generator');
308
+ console.log('='.repeat(60));
309
+ console.log('');
310
+
311
+ const manifest = await generateManifest();
312
+ await writeManifest(manifest);
313
+
314
+ console.log('\n✅ Manifest generated successfully!');
315
+ process.exit(0);
316
+ } catch (error) {
317
+ console.error('\n❌ Error generating manifest:', error.message);
318
+ if (process.env.DEBUG) {
319
+ console.error(error.stack);
320
+ }
321
+ process.exit(1);
322
+ }
323
+ }
324
+
325
+ // Run if called directly
326
+ if (require.main === module) {
327
+ main();
328
+ }
329
+
330
+ module.exports = {
331
+ generateManifest,
332
+ writeManifest,
333
+ getFileType,
334
+ scanDirectory,
335
+ FOLDERS_TO_COPY,
336
+ ROOT_FILES_TO_COPY,
337
+ };
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Validate Install Manifest
4
+ * Ensures install-manifest.yaml is up-to-date with actual files
5
+ *
6
+ * @script scripts/validate-manifest.js
7
+ * @story 6.18 - Dynamic Manifest & Brownfield Upgrade System
8
+ *
9
+ * Usage:
10
+ * node scripts/validate-manifest.js
11
+ * npm run validate:manifest
12
+ *
13
+ * Exit codes:
14
+ * 0 - Manifest is valid and up-to-date
15
+ * 1 - Manifest is outdated or has issues
16
+ */
17
+
18
+ const fs = require('fs-extra');
19
+ const path = require('path');
20
+ const yaml = require('js-yaml');
21
+ const { hashFile } = require('../src/installer/file-hasher');
22
+ const {
23
+ scanDirectory,
24
+ FOLDERS_TO_COPY,
25
+ ROOT_FILES_TO_COPY,
26
+ getFileType,
27
+ } = require('./generate-install-manifest');
28
+
29
+ /**
30
+ * Validation result object
31
+ * @typedef {Object} ValidationResult
32
+ * @property {boolean} valid - Whether manifest is valid
33
+ * @property {string[]} newFiles - Files in filesystem but not in manifest
34
+ * @property {string[]} removedFiles - Files in manifest but not in filesystem
35
+ * @property {string[]} modifiedFiles - Files with different hashes
36
+ * @property {string[]} errors - Error messages
37
+ */
38
+
39
+ /**
40
+ * Load and parse the install manifest
41
+ * @returns {Object|null} - Parsed manifest or null if not found
42
+ */
43
+ function loadManifest() {
44
+ const manifestPath = path.join(__dirname, '..', '.aios-core', 'install-manifest.yaml');
45
+
46
+ if (!fs.existsSync(manifestPath)) {
47
+ return null;
48
+ }
49
+
50
+ const content = fs.readFileSync(manifestPath, 'utf8');
51
+ return yaml.load(content);
52
+ }
53
+
54
+ /**
55
+ * Get current files from filesystem
56
+ * @returns {Map<string, Object>} - Map of relativePath -> file metadata
57
+ */
58
+ function getCurrentFiles() {
59
+ const aiosCoreDir = path.join(__dirname, '..', '.aios-core');
60
+ const filesMap = new Map();
61
+
62
+ // Scan folders
63
+ const allFiles = [];
64
+ for (const folder of FOLDERS_TO_COPY) {
65
+ const folderPath = path.join(aiosCoreDir, folder);
66
+ if (fs.existsSync(folderPath)) {
67
+ scanDirectory(folderPath, aiosCoreDir, allFiles);
68
+ }
69
+ }
70
+
71
+ // Add root files
72
+ for (const file of ROOT_FILES_TO_COPY) {
73
+ const filePath = path.join(aiosCoreDir, file);
74
+ if (fs.existsSync(filePath)) {
75
+ allFiles.push(filePath);
76
+ }
77
+ }
78
+
79
+ // Build map
80
+ for (const fullPath of allFiles) {
81
+ const relativePath = path.relative(aiosCoreDir, fullPath).replace(/\\/g, '/');
82
+ try {
83
+ const hash = hashFile(fullPath);
84
+ filesMap.set(relativePath, {
85
+ path: relativePath,
86
+ hash: `sha256:${hash}`,
87
+ type: getFileType(relativePath),
88
+ });
89
+ } catch (error) {
90
+ console.warn(`Warning: Could not hash ${relativePath}: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ return filesMap;
95
+ }
96
+
97
+ /**
98
+ * Validate manifest against current filesystem
99
+ * @returns {ValidationResult} - Validation results
100
+ */
101
+ function validateManifest() {
102
+ const result = {
103
+ valid: true,
104
+ newFiles: [],
105
+ removedFiles: [],
106
+ modifiedFiles: [],
107
+ errors: [],
108
+ };
109
+
110
+ // Load manifest
111
+ const manifest = loadManifest();
112
+ if (!manifest) {
113
+ result.valid = false;
114
+ result.errors.push('install-manifest.yaml not found');
115
+ return result;
116
+ }
117
+
118
+ if (!manifest.files || !Array.isArray(manifest.files)) {
119
+ result.valid = false;
120
+ result.errors.push('Manifest has no files array');
121
+ return result;
122
+ }
123
+
124
+ // Get current files
125
+ const currentFiles = getCurrentFiles();
126
+
127
+ // Build set of manifest paths
128
+ const manifestPaths = new Set();
129
+ const manifestMap = new Map();
130
+
131
+ for (const entry of manifest.files) {
132
+ const normalizedPath = entry.path.replace(/\\/g, '/');
133
+ manifestPaths.add(normalizedPath);
134
+ manifestMap.set(normalizedPath, entry);
135
+ }
136
+
137
+ // Check for new files (in filesystem but not in manifest)
138
+ for (const [filePath, _fileData] of currentFiles) {
139
+ if (!manifestPaths.has(filePath)) {
140
+ result.newFiles.push(filePath);
141
+ result.valid = false;
142
+ }
143
+ }
144
+
145
+ // Check for removed files and hash mismatches
146
+ for (const [manifestPath, manifestEntry] of manifestMap) {
147
+ const currentFile = currentFiles.get(manifestPath);
148
+
149
+ if (!currentFile) {
150
+ // File in manifest but not in filesystem
151
+ result.removedFiles.push(manifestPath);
152
+ result.valid = false;
153
+ } else if (currentFile.hash !== manifestEntry.hash) {
154
+ // Hash mismatch
155
+ result.modifiedFiles.push({
156
+ path: manifestPath,
157
+ manifestHash: manifestEntry.hash,
158
+ currentHash: currentFile.hash,
159
+ });
160
+ result.valid = false;
161
+ }
162
+ }
163
+
164
+ return result;
165
+ }
166
+
167
+ /**
168
+ * Print validation report
169
+ * @param {ValidationResult} result - Validation results
170
+ */
171
+ function printReport(result) {
172
+ console.log('='.repeat(60));
173
+ console.log('AIOS-Core Manifest Validation Report');
174
+ console.log('='.repeat(60));
175
+ console.log('');
176
+
177
+ if (result.errors.length > 0) {
178
+ console.log('❌ ERRORS:');
179
+ for (const error of result.errors) {
180
+ console.log(` - ${error}`);
181
+ }
182
+ console.log('');
183
+ }
184
+
185
+ if (result.newFiles.length > 0) {
186
+ console.log(`📁 NEW FILES (${result.newFiles.length}) - not in manifest:`);
187
+ for (const file of result.newFiles.slice(0, 20)) {
188
+ console.log(` + ${file}`);
189
+ }
190
+ if (result.newFiles.length > 20) {
191
+ console.log(` ... and ${result.newFiles.length - 20} more`);
192
+ }
193
+ console.log('');
194
+ }
195
+
196
+ if (result.removedFiles.length > 0) {
197
+ console.log(`🗑️ REMOVED FILES (${result.removedFiles.length}) - in manifest but missing:`);
198
+ for (const file of result.removedFiles.slice(0, 20)) {
199
+ console.log(` - ${file}`);
200
+ }
201
+ if (result.removedFiles.length > 20) {
202
+ console.log(` ... and ${result.removedFiles.length - 20} more`);
203
+ }
204
+ console.log('');
205
+ }
206
+
207
+ if (result.modifiedFiles.length > 0) {
208
+ console.log(`📝 MODIFIED FILES (${result.modifiedFiles.length}) - hash mismatch:`);
209
+ for (const file of result.modifiedFiles.slice(0, 20)) {
210
+ console.log(` ~ ${file.path}`);
211
+ if (process.env.VERBOSE) {
212
+ console.log(` manifest: ${file.manifestHash}`);
213
+ console.log(` current: ${file.currentHash}`);
214
+ }
215
+ }
216
+ if (result.modifiedFiles.length > 20) {
217
+ console.log(` ... and ${result.modifiedFiles.length - 20} more`);
218
+ }
219
+ console.log('');
220
+ }
221
+
222
+ // Summary
223
+ console.log('-'.repeat(60));
224
+ if (result.valid) {
225
+ console.log('✅ Manifest is VALID and up-to-date');
226
+ } else {
227
+ console.log('❌ Manifest is OUTDATED');
228
+ console.log('');
229
+ console.log('To fix, run: npm run generate:manifest');
230
+ }
231
+ console.log('-'.repeat(60));
232
+ }
233
+
234
+ /**
235
+ * Main execution
236
+ */
237
+ async function main() {
238
+ try {
239
+ const result = validateManifest();
240
+ printReport(result);
241
+
242
+ if (!result.valid) {
243
+ process.exit(1);
244
+ }
245
+ process.exit(0);
246
+ } catch (error) {
247
+ console.error('\n❌ Error validating manifest:', error.message);
248
+ if (process.env.DEBUG) {
249
+ console.error(error.stack);
250
+ }
251
+ process.exit(1);
252
+ }
253
+ }
254
+
255
+ // Run if called directly
256
+ if (require.main === module) {
257
+ main();
258
+ }
259
+
260
+ module.exports = {
261
+ validateManifest,
262
+ loadManifest,
263
+ getCurrentFiles,
264
+ printReport,
265
+ };
@@ -0,0 +1,23 @@
1
+ # Squad Design Blueprint
2
+ # Generated by *design-squad
3
+ # Source: Interactive input
4
+ # Created: 2025-12-18T23:01:55.322Z
5
+
6
+ squad:
7
+ name: duplicate-test
8
+ domain: test
9
+ analysis:
10
+ entities: []
11
+ workflows: []
12
+ integrations: []
13
+ stakeholders: []
14
+ recommendations:
15
+ agents: []
16
+ tasks: []
17
+ template: basic
18
+ config_mode: extend
19
+ metadata:
20
+ created_at: '2025-12-18T23:01:55.322Z'
21
+ source_docs: []
22
+ user_adjustments: 0
23
+ overall_confidence: 0.5
@@ -0,0 +1,23 @@
1
+ # Squad Design Blueprint
2
+ # Generated by *design-squad
3
+ # Source: Interactive input
4
+ # Created: 2025-12-18T23:01:55.323Z
5
+
6
+ squad:
7
+ name: force-test
8
+ domain: test-v1
9
+ analysis:
10
+ entities: []
11
+ workflows: []
12
+ integrations: []
13
+ stakeholders: []
14
+ recommendations:
15
+ agents: []
16
+ tasks: []
17
+ template: basic
18
+ config_mode: extend
19
+ metadata:
20
+ created_at: '2025-12-18T23:01:55.323Z'
21
+ source_docs: []
22
+ user_adjustments: 0
23
+ overall_confidence: 0.5
@@ -0,0 +1,23 @@
1
+ # Squad Design Blueprint
2
+ # Generated by *design-squad
3
+ # Source: Interactive input
4
+ # Created: 2025-12-18T23:01:55.321Z
5
+
6
+ squad:
7
+ name: nested-test
8
+ domain: test
9
+ analysis:
10
+ entities: []
11
+ workflows: []
12
+ integrations: []
13
+ stakeholders: []
14
+ recommendations:
15
+ agents: []
16
+ tasks: []
17
+ template: basic
18
+ config_mode: extend
19
+ metadata:
20
+ created_at: '2025-12-18T23:01:55.321Z'
21
+ source_docs: []
22
+ user_adjustments: 0
23
+ overall_confidence: 0.5
@@ -0,0 +1,23 @@
1
+ # Squad Design Blueprint
2
+ # Generated by *design-squad
3
+ # Source: Interactive input
4
+ # Created: 2025-12-18T23:01:55.317Z
5
+
6
+ squad:
7
+ name: test-squad
8
+ domain: test-domain
9
+ analysis:
10
+ entities: []
11
+ workflows: []
12
+ integrations: []
13
+ stakeholders: []
14
+ recommendations:
15
+ agents: []
16
+ tasks: []
17
+ template: basic
18
+ config_mode: extend
19
+ metadata:
20
+ created_at: '2025-12-18T23:01:55.317Z'
21
+ source_docs: []
22
+ user_adjustments: 0
23
+ overall_confidence: 0.5