aios-core 4.0.4 → 4.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.
Files changed (107) hide show
  1. package/.aios-core/cli/commands/migrate/analyze.js +6 -6
  2. package/.aios-core/cli/commands/migrate/backup.js +2 -2
  3. package/.aios-core/cli/commands/migrate/execute.js +4 -4
  4. package/.aios-core/cli/commands/migrate/index.js +5 -5
  5. package/.aios-core/cli/commands/migrate/rollback.js +6 -6
  6. package/.aios-core/cli/commands/migrate/update-imports.js +2 -2
  7. package/.aios-core/cli/commands/migrate/validate.js +2 -2
  8. package/.aios-core/cli/commands/pro/index.js +52 -0
  9. package/.aios-core/cli/index.js +1 -1
  10. package/.aios-core/core/ids/registry-updater.js +29 -3
  11. package/.aios-core/core/migration/migration-config.yaml +2 -2
  12. package/.aios-core/core/migration/module-mapping.yaml +2 -2
  13. package/.aios-core/core/registry/README.md +2 -2
  14. package/.aios-core/core/synapse/context/context-builder.js +34 -0
  15. package/.aios-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
  16. package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
  17. package/.aios-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
  18. package/.aios-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
  19. package/.aios-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
  20. package/.aios-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
  21. package/.aios-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
  22. package/.aios-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
  23. package/.aios-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
  24. package/.aios-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
  25. package/.aios-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
  26. package/.aios-core/core/synapse/diagnostics/report-formatter.js +484 -0
  27. package/.aios-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
  28. package/.aios-core/core/synapse/engine.js +73 -20
  29. package/.aios-core/core/synapse/runtime/hook-runtime.js +60 -0
  30. package/.aios-core/core-config.yaml +6 -0
  31. package/.aios-core/data/agent-config-requirements.yaml +2 -2
  32. package/.aios-core/data/aios-kb.md +4 -4
  33. package/.aios-core/data/entity-registry.yaml +210 -10
  34. package/.aios-core/data/registry-update-log.jsonl +52 -0
  35. package/.aios-core/development/agents/architect.md +10 -10
  36. package/.aios-core/development/agents/devops.md +93 -50
  37. package/.aios-core/development/agents/qa.md +94 -40
  38. package/.aios-core/development/agents/ux-design-expert.md +25 -25
  39. package/.aios-core/development/scripts/activation-runtime.js +63 -0
  40. package/.aios-core/development/scripts/generate-greeting.js +9 -8
  41. package/.aios-core/development/scripts/unified-activation-pipeline.js +102 -2
  42. package/.aios-core/development/tasks/{db-expansion-pack-integration.md → db-squad-integration.md} +5 -5
  43. package/.aios-core/development/tasks/{integrate-expansion-pack.md → integrate-squad.md} +2 -2
  44. package/.aios-core/development/tasks/next.md +3 -3
  45. package/.aios-core/development/tasks/pr-automation.md +2 -2
  46. package/.aios-core/development/tasks/publish-npm.md +257 -0
  47. package/.aios-core/development/tasks/release-management.md +4 -4
  48. package/.aios-core/development/tasks/setup-github.md +1 -1
  49. package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
  50. package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +14 -14
  51. package/.aios-core/development/tasks/update-aios.md +1 -1
  52. package/.aios-core/development/tasks/validate-next-story.md +99 -2
  53. package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +1 -1
  54. package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +5 -5
  55. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +21 -21
  56. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +25 -25
  57. package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +4 -4
  58. package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +3 -3
  59. package/.aios-core/docs/standards/STANDARDS-INDEX.md +13 -13
  60. package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +1 -1
  61. package/.aios-core/framework-config.yaml +4 -0
  62. package/.aios-core/infrastructure/scripts/codex-skills-sync/index.js +182 -0
  63. package/.aios-core/infrastructure/scripts/codex-skills-sync/validate.js +172 -0
  64. package/.aios-core/infrastructure/scripts/ide-sync/README.md +14 -0
  65. package/.aios-core/infrastructure/scripts/ide-sync/index.js +6 -0
  66. package/.aios-core/infrastructure/scripts/tool-resolver.js +4 -4
  67. package/.aios-core/infrastructure/scripts/validate-paths.js +142 -0
  68. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +11 -11
  69. package/.aios-core/infrastructure/templates/github-workflows/README.md +1 -1
  70. package/.aios-core/install-manifest.yaml +193 -109
  71. package/.aios-core/local-config.yaml.template +2 -0
  72. package/.aios-core/manifests/agents.csv +29 -1
  73. package/.aios-core/manifests/tasks.csv +80 -3
  74. package/.aios-core/product/README.md +2 -2
  75. package/.aios-core/product/data/integration-patterns.md +1 -1
  76. package/.aios-core/product/templates/ide-rules/cline-rules.md +1 -1
  77. package/.aios-core/product/templates/ide-rules/codex-rules.md +65 -0
  78. package/.aios-core/product/templates/ide-rules/copilot-rules.md +1 -1
  79. package/.aios-core/product/templates/ide-rules/roo-rules.md +1 -1
  80. package/.aios-core/user-guide.md +15 -14
  81. package/.aios-core/workflow-intelligence/engine/output-formatter.js +1 -1
  82. package/.claude/hooks/synapse-engine.js +9 -20
  83. package/README.md +14 -7
  84. package/bin/aios-init.js +255 -184
  85. package/bin/aios-minimal.js +2 -2
  86. package/bin/aios.js +4 -4
  87. package/package.json +6 -1
  88. package/packages/aios-pro-cli/bin/aios-pro.js +75 -2
  89. package/packages/aios-pro-cli/package.json +5 -1
  90. package/packages/aios-pro-cli/src/recover.js +100 -0
  91. package/packages/installer/src/__tests__/performance-benchmark.js +382 -0
  92. package/packages/installer/src/config/ide-configs.js +12 -1
  93. package/packages/installer/src/config/templates/core-config-template.js +2 -2
  94. package/packages/installer/src/installer/aios-core-installer.js +2 -2
  95. package/packages/installer/src/installer/file-hasher.js +97 -0
  96. package/packages/installer/src/installer/post-install-validator.js +41 -1
  97. package/packages/installer/src/pro/pro-scaffolder.js +335 -0
  98. package/packages/installer/src/utils/aios-colors.js +2 -2
  99. package/packages/installer/src/wizard/feedback.js +1 -1
  100. package/packages/installer/src/wizard/ide-config-generator.js +2 -2
  101. package/packages/installer/src/wizard/index.js +58 -19
  102. package/packages/installer/src/wizard/pro-setup.js +931 -0
  103. package/packages/installer/src/wizard/questions.js +20 -14
  104. package/packages/installer/src/wizard/validators.js +1 -1
  105. package/scripts/code-intel-health-check.js +343 -0
  106. package/scripts/package-synapse.js +323 -0
  107. package/scripts/validate-package-completeness.js +317 -0
@@ -25,8 +25,9 @@ const path = require('path');
25
25
  /**
26
26
  * IDE Configuration Metadata
27
27
  *
28
- * Synkra AIOS v2.1 supports 5 main IDEs:
28
+ * Synkra AIOS v4 supports 6 main IDEs:
29
29
  * - Claude Code (Anthropic's official CLI) - Recommended
30
+ * - Codex CLI (OpenAI coding CLI)
30
31
  * - Cursor (AI-first code editor)
31
32
  * - Windsurf (AI-powered development)
32
33
  * - GitHub Copilot (GitHub's AI pair programmer)
@@ -43,6 +44,16 @@ const IDE_CONFIGS = {
43
44
  recommended: true,
44
45
  agentFolder: path.join('.claude', 'commands', 'AIOS', 'agents'),
45
46
  },
47
+ codex: {
48
+ name: 'Codex CLI',
49
+ description: '',
50
+ configFile: 'AGENTS.md',
51
+ template: 'ide-rules/codex-rules.md',
52
+ requiresDirectory: false,
53
+ format: 'text',
54
+ recommended: true,
55
+ agentFolder: path.join('.codex', 'agents'),
56
+ },
46
57
  cursor: {
47
58
  name: 'Cursor',
48
59
  description: '',
@@ -118,7 +118,7 @@ function generateCoreConfig(options = {}) {
118
118
  enabled: true,
119
119
  heavySections: [
120
120
  'pvMindContext',
121
- 'expansionPacks',
121
+ 'squads',
122
122
  'registry',
123
123
  ],
124
124
  },
@@ -146,7 +146,7 @@ function generateCoreConfig(options = {}) {
146
146
  scriptsLocation: '.aios-core/scripts',
147
147
  dataLocation: '.aios-core/data',
148
148
  elicitationLocation: '.aios-core/elicitation',
149
- expansionPacksLocation: 'expansion-packs',
149
+ squadsLocation: 'squads',
150
150
  mindsLocation: 'outputs/minds',
151
151
 
152
152
  // Project Status Configuration
@@ -24,11 +24,11 @@ function getAiosCoreSourcePath() {
24
24
 
25
25
  /**
26
26
  * Folders to copy from .aios-core
27
- * Includes both v2.1 modular structure and v2.0 legacy flat structure for compatibility
27
+ * Includes both v4 modular structure and v2.0 legacy flat structure for compatibility
28
28
  * @constant {string[]}
29
29
  */
30
30
  const FOLDERS_TO_COPY = [
31
- // v2.1 Modular Structure (Story 2.15)
31
+ // v4.0.4 Modular Structure (Story 2.15)
32
32
  'core', // Framework utilities, config, registry, migration
33
33
  'development', // Agents, tasks, workflows, scripts, personas
34
34
  'product', // Templates, checklists, cli, api
@@ -87,6 +87,100 @@ function hashFile(filePath) {
87
87
  return crypto.createHash('sha256').update(content).digest('hex');
88
88
  }
89
89
 
90
+ /**
91
+ * Async version of hashFile for parallel processing.
92
+ * INS-2 Performance: Enables parallel hashing with Promise.all
93
+ *
94
+ * @param {string} filePath - Absolute path to the file
95
+ * @returns {Promise<string>} - SHA256 hash as hex string
96
+ * @throws {Error} - If file cannot be read
97
+ */
98
+ async function hashFileAsync(filePath) {
99
+ const exists = await fs.pathExists(filePath);
100
+ if (!exists) {
101
+ throw new Error(`File not found: ${filePath}`);
102
+ }
103
+
104
+ const stats = await fs.stat(filePath);
105
+ if (stats.isDirectory()) {
106
+ throw new Error(`Cannot hash directory: ${filePath}`);
107
+ }
108
+
109
+ let content;
110
+
111
+ if (isBinaryFile(filePath)) {
112
+ // Binary files: hash raw bytes
113
+ content = await fs.readFile(filePath);
114
+ } else {
115
+ // Text files: normalize line endings and remove BOM
116
+ const rawContent = await fs.readFile(filePath, 'utf8');
117
+ const withoutBOM = removeBOM(rawContent);
118
+ const normalized = normalizeLineEndings(withoutBOM);
119
+ content = Buffer.from(normalized, 'utf8');
120
+ }
121
+
122
+ return crypto.createHash('sha256').update(content).digest('hex');
123
+ }
124
+
125
+ /**
126
+ * Async version of hashesMatch using hashFileAsync.
127
+ *
128
+ * @param {string} filePath1 - First file path
129
+ * @param {string} filePath2 - Second file path
130
+ * @returns {Promise<boolean>} - True if file hashes match
131
+ */
132
+ async function hashFilesMatchAsync(filePath1, filePath2) {
133
+ try {
134
+ const [hash1, hash2] = await Promise.all([
135
+ hashFileAsync(filePath1),
136
+ hashFileAsync(filePath2),
137
+ ]);
138
+ return hashesMatch(hash1, hash2);
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Hash multiple files in parallel with batch processing
146
+ * INS-2 Performance: Process files in batches to prevent memory exhaustion
147
+ *
148
+ * @param {string[]} filePaths - Array of absolute file paths
149
+ * @param {number} batchSize - Number of files to hash concurrently (default: 50)
150
+ * @param {Function} onProgress - Optional progress callback (current, total)
151
+ * @returns {Promise<Map<string, string>>} - Map of filePath -> hash
152
+ */
153
+ async function hashFilesParallel(filePaths, batchSize = 50, onProgress = null) {
154
+ const results = new Map();
155
+ const total = filePaths.length;
156
+
157
+ for (let i = 0; i < total; i += batchSize) {
158
+ const batch = filePaths.slice(i, i + batchSize);
159
+ const batchResults = await Promise.all(
160
+ batch.map(async (filePath) => {
161
+ try {
162
+ const hash = await hashFileAsync(filePath);
163
+ return { filePath, hash, error: null };
164
+ } catch (error) {
165
+ return { filePath, hash: null, error: error.message };
166
+ }
167
+ }),
168
+ );
169
+
170
+ for (const result of batchResults) {
171
+ if (result.hash) {
172
+ results.set(result.filePath, result.hash);
173
+ }
174
+ }
175
+
176
+ if (onProgress) {
177
+ onProgress(Math.min(i + batchSize, total), total);
178
+ }
179
+ }
180
+
181
+ return results;
182
+ }
183
+
90
184
  /**
91
185
  * Compute SHA256 hash of a string (for manifest integrity)
92
186
  * @param {string} content - String content to hash
@@ -127,8 +221,11 @@ function getFileMetadata(filePath, basePath) {
127
221
 
128
222
  module.exports = {
129
223
  hashFile,
224
+ hashFileAsync,
225
+ hashFilesParallel,
130
226
  hashString,
131
227
  hashesMatch,
228
+ hashFilesMatchAsync,
132
229
  getFileMetadata,
133
230
  isBinaryFile,
134
231
  normalizeLineEndings,
@@ -359,6 +359,27 @@ class PostInstallValidator {
359
359
  extraFiles: 0,
360
360
  skippedFiles: 0,
361
361
  };
362
+
363
+ // INS-2 Performance: Cache realpath of target directory (computed once, used for all files)
364
+ // This eliminates redundant fs.realpathSync() calls - 50% reduction in syscalls
365
+ this._realTargetDirCache = null;
366
+ }
367
+
368
+ /**
369
+ * Get the real path of the target directory (cached)
370
+ * INS-2 Performance Optimization: Reduces syscalls from 2 to 1 per file validation
371
+ * @returns {string|null} - Real path or null if resolution fails
372
+ */
373
+ _getRealTargetDir() {
374
+ if (this._realTargetDirCache === null) {
375
+ try {
376
+ this._realTargetDirCache = fs.realpathSync(this.aiosCoreTarget);
377
+ } catch {
378
+ // Will be handled by caller
379
+ return null;
380
+ }
381
+ }
382
+ return this._realTargetDirCache;
362
383
  }
363
384
 
364
385
  /**
@@ -681,9 +702,28 @@ class PostInstallValidator {
681
702
  // both the file path AND the target directory to their real paths, then
682
703
  // comparing containment. The key security check is: does the real path of the
683
704
  // file stay within the real path of the target directory?
705
+ //
706
+ // INS-2 Performance: realTargetDir is now cached via _getRealTargetDir()
707
+ // This reduces syscalls from 2 to 1 per file (50% reduction)
684
708
  try {
685
709
  const realPath = fs.realpathSync(absolutePath);
686
- const realTargetDir = fs.realpathSync(this.aiosCoreTarget);
710
+ const realTargetDir = this._getRealTargetDir();
711
+
712
+ // Handle case where target dir resolution failed
713
+ if (realTargetDir === null) {
714
+ this.log('SECURITY: Cannot resolve realpath for target directory');
715
+ result.issue = {
716
+ type: IssueType.PERMISSION_ERROR,
717
+ severity: Severity.CRITICAL,
718
+ message: 'Cannot resolve real path for target directory',
719
+ details: 'Target directory realpath resolution failed',
720
+ category,
721
+ remediation: 'Check directory permissions',
722
+ relativePath,
723
+ };
724
+ this.stats.skippedFiles++;
725
+ return result;
726
+ }
687
727
 
688
728
  // SECURITY: Verify realpath is still contained within REAL target directory
689
729
  // This handles system symlinks like /tmp -> /private/tmp correctly
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Pro Content Scaffolder
3
+ *
4
+ * Copies premium content (squads, configs, feature registry) from
5
+ * node_modules/@aios-fullstack/pro/ into the user's project after
6
+ * license activation.
7
+ *
8
+ * @module packages/installer/src/pro/pro-scaffolder
9
+ * @story INS-3.1 — Implement Pro Content Scaffolder
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs-extra');
15
+ const path = require('path');
16
+ const yaml = require('js-yaml');
17
+ const { hashFileAsync, hashFilesMatchAsync } = require('../installer/file-hasher');
18
+
19
+ /**
20
+ * Items to scaffold from pro package into user project.
21
+ * Each entry defines source (relative to proSourceDir) and dest (relative to targetDir).
22
+ */
23
+ const SCAFFOLD_ITEMS = [
24
+ {
25
+ type: 'directory',
26
+ source: 'squads',
27
+ dest: 'squads',
28
+ description: 'Pro squads',
29
+ required: true,
30
+ },
31
+ {
32
+ type: 'file',
33
+ source: 'pro-config.yaml',
34
+ dest: path.join('.aios-core', 'pro-config.yaml'),
35
+ description: 'Pro configuration',
36
+ required: true,
37
+ },
38
+ {
39
+ type: 'file',
40
+ source: 'feature-registry.yaml',
41
+ dest: path.join('.aios-core', 'feature-registry.yaml'),
42
+ description: 'Feature registry',
43
+ required: false,
44
+ },
45
+ ];
46
+
47
+ /**
48
+ * Scaffold pro content into user project.
49
+ *
50
+ * @param {string} targetDir - Project root directory
51
+ * @param {string} proSourceDir - Path to pro package content (node_modules/@aios-fullstack/pro)
52
+ * @param {Object} [options={}] - Scaffold options
53
+ * @param {Function} [options.onProgress] - Progress callback ({item, status, message})
54
+ * @param {boolean} [options.force=false] - Force overwrite even if content exists
55
+ * @returns {Promise<Object>} Scaffold result with copiedFiles, warnings, manifest
56
+ */
57
+ async function scaffoldProContent(targetDir, proSourceDir, options = {}) {
58
+ const { onProgress = null, force = false } = options;
59
+
60
+ const result = {
61
+ success: false,
62
+ copiedFiles: [],
63
+ skippedFiles: [],
64
+ warnings: [],
65
+ errors: [],
66
+ manifest: null,
67
+ versionInfo: null,
68
+ };
69
+
70
+ // Track files for rollback on partial failure
71
+ const rollbackFiles = [];
72
+
73
+ // Validate pro source exists
74
+ if (!await fs.pathExists(proSourceDir)) {
75
+ result.errors.push(
76
+ `Pro package not found at ${proSourceDir}. Run "npm install @aios-fullstack/pro" first.`
77
+ );
78
+ return result;
79
+ }
80
+
81
+ try {
82
+ for (const item of SCAFFOLD_ITEMS) {
83
+ const sourcePath = path.join(proSourceDir, item.source);
84
+ const destPath = path.join(targetDir, item.dest);
85
+
86
+ // Check source exists
87
+ if (!await fs.pathExists(sourcePath)) {
88
+ if (item.required) {
89
+ throw new Error(`Required pro content not found: ${item.source}`);
90
+ }
91
+ const warning = `${item.description} (${item.source}) not found in pro package — skipping`;
92
+ result.warnings.push(warning);
93
+ if (onProgress) {
94
+ onProgress({ item: item.source, status: 'warning', message: warning });
95
+ }
96
+ continue;
97
+ }
98
+
99
+ if (item.type === 'directory') {
100
+ const copied = await scaffoldDirectory(sourcePath, destPath, { force, rollbackFiles, baseDir: targetDir });
101
+ result.copiedFiles.push(...copied.copiedFiles);
102
+ result.skippedFiles.push(...copied.skippedFiles);
103
+ } else {
104
+ const copied = await scaffoldFile(sourcePath, destPath, { force, rollbackFiles, baseDir: targetDir });
105
+ if (copied.skipped) {
106
+ result.skippedFiles.push(copied.relativePath);
107
+ } else {
108
+ result.copiedFiles.push(copied.relativePath);
109
+ }
110
+ }
111
+
112
+ if (onProgress) {
113
+ onProgress({ item: item.source, status: 'done', message: `${item.description} scaffolded` });
114
+ }
115
+ }
116
+
117
+ // Generate pro-version.json (AC4)
118
+ const versionInfo = await generateProVersionJson(targetDir, proSourceDir, result.copiedFiles);
119
+ result.versionInfo = versionInfo;
120
+ result.copiedFiles.push('pro-version.json');
121
+ rollbackFiles.push(path.join(targetDir, 'pro-version.json'));
122
+
123
+ // Generate pro-installed-manifest.yaml (AC8)
124
+ const manifest = await generateInstalledManifest(targetDir, result.copiedFiles);
125
+ result.manifest = manifest;
126
+ result.copiedFiles.push('pro-installed-manifest.yaml');
127
+ rollbackFiles.push(path.join(targetDir, 'pro-installed-manifest.yaml'));
128
+
129
+ result.success = true;
130
+
131
+ } catch (error) {
132
+ result.errors.push(error.message);
133
+
134
+ // Rollback partially copied files (AC6)
135
+ const rollbackResult = await rollbackScaffold(rollbackFiles);
136
+ if (rollbackResult.errors.length > 0) {
137
+ result.errors.push(`Rollback errors: ${rollbackResult.errors.join(', ')}`);
138
+ }
139
+ result.warnings.push(
140
+ `Scaffolding failed: ${error.message}. ${rollbackResult.removed} files cleaned up.`
141
+ );
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Scaffold a directory recursively with idempotency checks.
149
+ *
150
+ * @param {string} sourceDir - Source directory
151
+ * @param {string} destDir - Destination directory
152
+ * @param {Object} options - Options
153
+ * @returns {Promise<Object>} Result with copiedFiles and skippedFiles
154
+ */
155
+ async function scaffoldDirectory(sourceDir, destDir, options = {}) {
156
+ const { force = false, rollbackFiles = [], baseDir } = options;
157
+ const copiedFiles = [];
158
+ const skippedFiles = [];
159
+
160
+ await fs.ensureDir(destDir);
161
+
162
+ const items = await fs.readdir(sourceDir, { withFileTypes: true });
163
+
164
+ for (const item of items) {
165
+ const sourcePath = path.join(sourceDir, item.name);
166
+ const destPath = path.join(destDir, item.name);
167
+
168
+ if (item.isDirectory()) {
169
+ const sub = await scaffoldDirectory(sourcePath, destPath, options);
170
+ copiedFiles.push(...sub.copiedFiles);
171
+ skippedFiles.push(...sub.skippedFiles);
172
+ } else {
173
+ const result = await scaffoldFile(sourcePath, destPath, { force, rollbackFiles, baseDir });
174
+ if (result.skipped) {
175
+ skippedFiles.push(result.relativePath);
176
+ } else {
177
+ copiedFiles.push(result.relativePath);
178
+ }
179
+ }
180
+ }
181
+
182
+ return { copiedFiles, skippedFiles };
183
+ }
184
+
185
+ /**
186
+ * Scaffold a single file with idempotency (AC5).
187
+ * If dest exists and has identical hash, skip. If user modified, skip (preserve).
188
+ *
189
+ * @param {string} sourcePath - Source file path
190
+ * @param {string} destPath - Destination file path
191
+ * @param {Object} options - Options
192
+ * @returns {Promise<Object>} Result with relativePath and skipped flag
193
+ */
194
+ async function scaffoldFile(sourcePath, destPath, options = {}) {
195
+ const { force = false, rollbackFiles = [], baseDir } = options;
196
+ const base = baseDir || path.resolve(destPath, '..', '..');
197
+ const relativePath = path.relative(base, destPath).replace(/\\/g, '/');
198
+
199
+ // Idempotency check (AC5) — async to avoid blocking event loop
200
+ if (!force && await fs.pathExists(destPath)) {
201
+ try {
202
+ if (await hashFilesMatchAsync(sourcePath, destPath)) {
203
+ // Identical — skip
204
+ return { relativePath, skipped: true };
205
+ }
206
+ } catch {
207
+ // Hash comparison failed — overwrite to be safe
208
+ }
209
+ }
210
+
211
+ await fs.ensureDir(path.dirname(destPath));
212
+ await fs.copy(sourcePath, destPath);
213
+ rollbackFiles.push(destPath);
214
+
215
+ return { relativePath, skipped: false };
216
+ }
217
+
218
+ /**
219
+ * Generate pro-version.json with SHA256 hashes for version tracking (AC4).
220
+ *
221
+ * @param {string} targetDir - Project root
222
+ * @param {string} proSourceDir - Pro package directory
223
+ * @param {string[]} copiedFiles - List of copied file relative paths
224
+ * @returns {Promise<Object>} Version info object
225
+ */
226
+ async function generateProVersionJson(targetDir, proSourceDir, copiedFiles) {
227
+ // Read pro package version
228
+ let proVersion = 'unknown';
229
+ const proPkgPath = path.join(proSourceDir, 'package.json');
230
+ if (await fs.pathExists(proPkgPath)) {
231
+ try {
232
+ const proPkg = await fs.readJson(proPkgPath);
233
+ proVersion = proPkg.version || 'unknown';
234
+ } catch {
235
+ // Keep 'unknown'
236
+ }
237
+ }
238
+
239
+ // Generate hashes for all copied files
240
+ const fileHashes = {};
241
+ for (const relativePath of copiedFiles) {
242
+ const absolutePath = path.join(targetDir, relativePath);
243
+ try {
244
+ if (await fs.pathExists(absolutePath)) {
245
+ const stats = await fs.stat(absolutePath);
246
+ if (stats.isFile()) {
247
+ fileHashes[relativePath] = `sha256:${await hashFileAsync(absolutePath)}`;
248
+ }
249
+ }
250
+ } catch {
251
+ // Skip unhashable files
252
+ }
253
+ }
254
+
255
+ const versionInfo = {
256
+ proVersion,
257
+ installedAt: new Date().toISOString(),
258
+ fileCount: copiedFiles.length,
259
+ fileHashes,
260
+ };
261
+
262
+ const versionPath = path.join(targetDir, 'pro-version.json');
263
+ await fs.writeJson(versionPath, versionInfo, { spaces: 2 });
264
+
265
+ return versionInfo;
266
+ }
267
+
268
+ /**
269
+ * Generate pro-installed-manifest.yaml listing all scaffolded files (AC8).
270
+ *
271
+ * @param {string} targetDir - Project root
272
+ * @param {string[]} copiedFiles - List of copied file relative paths
273
+ * @returns {Promise<Object>} Manifest object
274
+ */
275
+ async function generateInstalledManifest(targetDir, copiedFiles) {
276
+ const files = [];
277
+ for (const relativePath of copiedFiles) {
278
+ const absolutePath = path.join(targetDir, relativePath);
279
+ let timestamp = new Date().toISOString();
280
+ try {
281
+ if (await fs.pathExists(absolutePath)) {
282
+ const stats = await fs.stat(absolutePath);
283
+ timestamp = stats.mtime.toISOString();
284
+ }
285
+ } catch {
286
+ // Use current time
287
+ }
288
+ files.push({ path: relativePath, timestamp });
289
+ }
290
+
291
+ const manifest = {
292
+ generatedAt: new Date().toISOString(),
293
+ totalFiles: files.length,
294
+ files,
295
+ };
296
+
297
+ const manifestPath = path.join(targetDir, 'pro-installed-manifest.yaml');
298
+ await fs.writeFile(manifestPath, yaml.dump(manifest), 'utf8');
299
+
300
+ return manifest;
301
+ }
302
+
303
+ /**
304
+ * Rollback partially scaffolded files on error (AC6).
305
+ *
306
+ * @param {string[]} rollbackFiles - Absolute paths to remove
307
+ * @returns {Promise<Object>} Rollback result with removed count and errors
308
+ */
309
+ async function rollbackScaffold(rollbackFiles) {
310
+ let removed = 0;
311
+ const errors = [];
312
+
313
+ for (const filePath of rollbackFiles) {
314
+ try {
315
+ if (await fs.pathExists(filePath)) {
316
+ await fs.remove(filePath);
317
+ removed++;
318
+ }
319
+ } catch (error) {
320
+ errors.push(`Failed to remove ${filePath}: ${error.message}`);
321
+ }
322
+ }
323
+
324
+ return { removed, errors };
325
+ }
326
+
327
+ module.exports = {
328
+ scaffoldProContent,
329
+ scaffoldDirectory,
330
+ scaffoldFile,
331
+ generateProVersionJson,
332
+ generateInstalledManifest,
333
+ rollbackScaffold,
334
+ SCAFFOLD_ITEMS,
335
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AIOS Color Palette v2.1
2
+ * AIOS Color Palette v4.0.4
3
3
  *
4
4
  * Brand-inspired color system for CLI tools and terminal output.
5
5
  * Colors derived from AIOS logo gradient (magenta → orange → purple → blue)
@@ -189,7 +189,7 @@ const lists = {
189
189
  */
190
190
  const examples = {
191
191
  welcome: () => {
192
- console.log(headings.h1('🎉 Welcome to AIOS v2.1 Installer!'));
192
+ console.log(headings.h1('🎉 Welcome to AIOS v4 Installer!'));
193
193
  console.log(colors.info('Let\'s configure your project in just a few steps...\n'));
194
194
  },
195
195
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Visual Feedback Helpers
3
3
  *
4
- * Spinners, progress bars, and status indicators using AIOS Color System v2.1
4
+ * Spinners, progress bars, and status indicators using AIOS Color System v4.0.4
5
5
  *
6
6
  * @module wizard/feedback
7
7
  */
@@ -206,14 +206,14 @@ function generateTemplateVariables(wizardState) {
206
206
 
207
207
  /**
208
208
  * Copy agent files from .aios-core/development/agents to IDE-specific agent folder
209
- * v2.1 modular structure: agents are now in development/ module
209
+ * v4 modular structure: agents are now in development/ module
210
210
  * @param {string} projectRoot - Project root directory
211
211
  * @param {string} agentFolder - Target folder for agent files (IDE-specific)
212
212
  * @param {Object} ideConfig - IDE configuration object (optional, for special handling)
213
213
  * @returns {Promise<string[]>} List of copied files
214
214
  */
215
215
  async function copyAgentFiles(projectRoot, agentFolder, ideConfig = null) {
216
- // v2.1: Agents are in development/agents/ (not root agents/)
216
+ // v4: Agents are in development/agents/ (not root agents/)
217
217
  const sourceDir = path.join(__dirname, '..', '..', '..', '..', '.aios-core', 'development', 'agents');
218
218
  const targetDir = path.join(projectRoot, agentFolder);
219
219
  const copiedFiles = [];