aios-core 3.1.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.
package/bin/aios-init.js CHANGED
@@ -60,6 +60,15 @@ const { detectRepositoryContext } = resolveAiosCoreModule('scripts/repository-de
60
60
  // const { GitHubProjectsAdapter } = resolveAiosCoreModule('utils/pm-adapters/github-adapter');
61
61
  // const { JiraAdapter } = resolveAiosCoreModule('utils/pm-adapters/jira-adapter');
62
62
 
63
+ // Brownfield upgrade module (Story 6.18)
64
+ let brownfieldUpgrader;
65
+ try {
66
+ brownfieldUpgrader = require('../src/installer/brownfield-upgrader');
67
+ } catch (_err) {
68
+ // Module may not be available in older installations
69
+ brownfieldUpgrader = null;
70
+ }
71
+
63
72
  async function main() {
64
73
  console.clear();
65
74
 
@@ -142,6 +151,108 @@ async function main() {
142
151
  console.log(chalk.cyan('šŸ“¦ Package:') + ` ${context.packageName}`);
143
152
  console.log('');
144
153
 
154
+ // Check for existing installation (Story 6.18 - Brownfield Upgrade)
155
+ const installedManifestPath = path.join(projectRoot, '.aios-core', '.installed-manifest.yaml');
156
+ const hasExistingInstall = fs.existsSync(installedManifestPath);
157
+
158
+ if (hasExistingInstall && brownfieldUpgrader) {
159
+ console.log(chalk.yellow('šŸ”„ Existing AIOS installation detected!'));
160
+ console.log('');
161
+
162
+ const sourceDir = path.join(context.frameworkLocation, '.aios-core');
163
+ const upgradeCheck = brownfieldUpgrader.checkUpgradeAvailable(sourceDir, projectRoot);
164
+
165
+ if (upgradeCheck.available) {
166
+ console.log(chalk.green(` Upgrade available: ${upgradeCheck.from} → ${upgradeCheck.to}`));
167
+ console.log('');
168
+
169
+ // Generate upgrade report for display
170
+ const sourceManifest = brownfieldUpgrader.loadSourceManifest(sourceDir);
171
+ const installedManifest = brownfieldUpgrader.loadInstalledManifest(projectRoot);
172
+ const report = brownfieldUpgrader.generateUpgradeReport(sourceManifest, installedManifest, projectRoot);
173
+
174
+ console.log(chalk.gray('─'.repeat(80)));
175
+ const { upgradeChoice } = await inquirer.prompt([
176
+ {
177
+ type: 'list',
178
+ name: 'upgradeChoice',
179
+ message: chalk.white('What would you like to do?'),
180
+ choices: [
181
+ {
182
+ name: ` Upgrade to ${upgradeCheck.to} ` + chalk.gray(`(${report.newFiles.length} new, ${report.modifiedFiles.length} updated files)`),
183
+ value: 'upgrade',
184
+ },
185
+ {
186
+ name: ' Dry Run ' + chalk.gray('(Show what would be changed without applying)'),
187
+ value: 'dry-run',
188
+ },
189
+ {
190
+ name: ' Fresh Install ' + chalk.gray('(Reinstall everything, overwrite all files)'),
191
+ value: 'fresh',
192
+ },
193
+ {
194
+ name: ' Cancel ' + chalk.gray('(Exit without changes)'),
195
+ value: 'cancel',
196
+ },
197
+ ],
198
+ },
199
+ ]);
200
+
201
+ if (upgradeChoice === 'cancel') {
202
+ console.log(chalk.yellow('\nInstallation cancelled.'));
203
+ process.exit(0);
204
+ }
205
+
206
+ if (upgradeChoice === 'dry-run') {
207
+ console.log('');
208
+ console.log(brownfieldUpgrader.formatUpgradeReport(report));
209
+ console.log('');
210
+ console.log(chalk.yellow('This was a dry run. No files were changed.'));
211
+ console.log(chalk.gray('Run again and select "Upgrade" to apply changes.'));
212
+ process.exit(0);
213
+ }
214
+
215
+ if (upgradeChoice === 'upgrade') {
216
+ console.log('');
217
+ console.log(chalk.blue('šŸ“¦ Applying upgrade...'));
218
+
219
+ const result = await brownfieldUpgrader.applyUpgrade(report, sourceDir, projectRoot, { dryRun: false });
220
+
221
+ if (result.success) {
222
+ // Update installed manifest
223
+ const packageJson = require(path.join(context.frameworkLocation, 'package.json'));
224
+ brownfieldUpgrader.updateInstalledManifest(projectRoot, sourceManifest, `aios-core@${packageJson.version}`);
225
+
226
+ console.log(chalk.green('āœ“') + ` Upgraded ${result.filesInstalled.length} files`);
227
+ if (result.filesSkipped.length > 0) {
228
+ console.log(chalk.yellow('⚠') + ` Preserved ${result.filesSkipped.length} user-modified files`);
229
+ }
230
+ console.log('');
231
+ console.log(chalk.green('āœ… Upgrade complete!'));
232
+ console.log(chalk.gray(` From: ${upgradeCheck.from}`));
233
+ console.log(chalk.gray(` To: ${upgradeCheck.to}`));
234
+ process.exit(0);
235
+ } else {
236
+ console.error(chalk.red('āœ—') + ' Upgrade failed with errors:');
237
+ for (const err of result.errors) {
238
+ console.error(chalk.red(` - ${err.path}: ${err.error}`));
239
+ }
240
+ process.exit(1);
241
+ }
242
+ }
243
+
244
+ // If 'fresh' was selected, continue with normal installation flow below
245
+ if (upgradeChoice === 'fresh') {
246
+ console.log(chalk.yellow('\nProceeding with fresh installation...'));
247
+ console.log('');
248
+ }
249
+ } else {
250
+ console.log(chalk.green(` Current version: ${upgradeCheck.from || 'unknown'}`));
251
+ console.log(chalk.gray(' No upgrade available. You can proceed with fresh install if needed.'));
252
+ console.log('');
253
+ }
254
+ }
255
+
145
256
  // Step 1: Installation Mode
146
257
  console.log(chalk.gray('─'.repeat(80)));
147
258
  const { installMode } = await inquirer.prompt([
@@ -251,6 +362,21 @@ async function main() {
251
362
  if (fs.existsSync(sourceCoreDir)) {
252
363
  await fse.copy(sourceCoreDir, targetCoreDir);
253
364
  console.log(chalk.green('āœ“') + ' AIOS Core files installed ' + chalk.gray('(11 agents, 68 tasks, 23 templates)'));
365
+
366
+ // Create installed manifest for brownfield upgrades (Story 6.18)
367
+ if (brownfieldUpgrader) {
368
+ try {
369
+ const sourceManifest = brownfieldUpgrader.loadSourceManifest(sourceCoreDir);
370
+ if (sourceManifest) {
371
+ const packageJson = require(path.join(context.frameworkLocation, 'package.json'));
372
+ brownfieldUpgrader.updateInstalledManifest(context.projectRoot, sourceManifest, `aios-core@${packageJson.version}`);
373
+ console.log(chalk.green('āœ“') + ' Installation manifest created ' + chalk.gray('(enables future upgrades)'));
374
+ }
375
+ } catch (manifestErr) {
376
+ // Non-critical - just log warning
377
+ console.log(chalk.yellow('⚠') + ' Could not create installation manifest ' + chalk.gray('(brownfield upgrades may not work)'));
378
+ }
379
+ }
254
380
  } else {
255
381
  console.error(chalk.red('āœ—') + ' AIOS Core files not found');
256
382
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aios-core",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "main": "index.js",
6
6
  "module": "index.esm.js",
@@ -78,6 +78,9 @@
78
78
  "version:expansion:all:patch": "node tools/bump-all-versions.js patch",
79
79
  "release": "semantic-release",
80
80
  "release:test": "semantic-release --dry-run --no-ci || echo 'Config test complete - authentication errors are expected locally'",
81
+ "generate:manifest": "node scripts/generate-install-manifest.js",
82
+ "validate:manifest": "node scripts/validate-manifest.js",
83
+ "prepublishOnly": "npm run generate:manifest && npm run validate:manifest",
81
84
  "prepare": "echo 'Skipping husky - not needed for NPM publish'"
82
85
  },
83
86
  "dependencies": {
@@ -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
+ };