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.
- package/.aios-core/cli/commands/migrate/analyze.js +6 -6
- package/.aios-core/cli/commands/migrate/backup.js +2 -2
- package/.aios-core/cli/commands/migrate/execute.js +4 -4
- package/.aios-core/cli/commands/migrate/index.js +5 -5
- package/.aios-core/cli/commands/migrate/rollback.js +6 -6
- package/.aios-core/cli/commands/migrate/update-imports.js +2 -2
- package/.aios-core/cli/commands/migrate/validate.js +2 -2
- package/.aios-core/cli/commands/pro/index.js +52 -0
- package/.aios-core/cli/index.js +1 -1
- package/.aios-core/core/ids/registry-updater.js +29 -3
- package/.aios-core/core/migration/migration-config.yaml +2 -2
- package/.aios-core/core/migration/module-mapping.yaml +2 -2
- package/.aios-core/core/registry/README.md +2 -2
- package/.aios-core/core/synapse/context/context-builder.js +34 -0
- package/.aios-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
- package/.aios-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
- package/.aios-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
- package/.aios-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
- package/.aios-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
- package/.aios-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
- package/.aios-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
- package/.aios-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
- package/.aios-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
- package/.aios-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
- package/.aios-core/core/synapse/diagnostics/report-formatter.js +484 -0
- package/.aios-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
- package/.aios-core/core/synapse/engine.js +73 -20
- package/.aios-core/core/synapse/runtime/hook-runtime.js +60 -0
- package/.aios-core/core-config.yaml +6 -0
- package/.aios-core/data/agent-config-requirements.yaml +2 -2
- package/.aios-core/data/aios-kb.md +4 -4
- package/.aios-core/data/entity-registry.yaml +210 -10
- package/.aios-core/data/registry-update-log.jsonl +52 -0
- package/.aios-core/development/agents/architect.md +10 -10
- package/.aios-core/development/agents/devops.md +93 -50
- package/.aios-core/development/agents/qa.md +94 -40
- package/.aios-core/development/agents/ux-design-expert.md +25 -25
- package/.aios-core/development/scripts/activation-runtime.js +63 -0
- package/.aios-core/development/scripts/generate-greeting.js +9 -8
- package/.aios-core/development/scripts/unified-activation-pipeline.js +102 -2
- package/.aios-core/development/tasks/{db-expansion-pack-integration.md → db-squad-integration.md} +5 -5
- package/.aios-core/development/tasks/{integrate-expansion-pack.md → integrate-squad.md} +2 -2
- package/.aios-core/development/tasks/next.md +3 -3
- package/.aios-core/development/tasks/pr-automation.md +2 -2
- package/.aios-core/development/tasks/publish-npm.md +257 -0
- package/.aios-core/development/tasks/release-management.md +4 -4
- package/.aios-core/development/tasks/setup-github.md +1 -1
- package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
- package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +14 -14
- package/.aios-core/development/tasks/update-aios.md +1 -1
- package/.aios-core/development/tasks/validate-next-story.md +99 -2
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +1 -1
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +5 -5
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +21 -21
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +25 -25
- package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +4 -4
- package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +3 -3
- package/.aios-core/docs/standards/STANDARDS-INDEX.md +13 -13
- package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +1 -1
- package/.aios-core/framework-config.yaml +4 -0
- package/.aios-core/infrastructure/scripts/codex-skills-sync/index.js +182 -0
- package/.aios-core/infrastructure/scripts/codex-skills-sync/validate.js +172 -0
- package/.aios-core/infrastructure/scripts/ide-sync/README.md +14 -0
- package/.aios-core/infrastructure/scripts/ide-sync/index.js +6 -0
- package/.aios-core/infrastructure/scripts/tool-resolver.js +4 -4
- package/.aios-core/infrastructure/scripts/validate-paths.js +142 -0
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +11 -11
- package/.aios-core/infrastructure/templates/github-workflows/README.md +1 -1
- package/.aios-core/install-manifest.yaml +193 -109
- package/.aios-core/local-config.yaml.template +2 -0
- package/.aios-core/manifests/agents.csv +29 -1
- package/.aios-core/manifests/tasks.csv +80 -3
- package/.aios-core/product/README.md +2 -2
- package/.aios-core/product/data/integration-patterns.md +1 -1
- package/.aios-core/product/templates/ide-rules/cline-rules.md +1 -1
- package/.aios-core/product/templates/ide-rules/codex-rules.md +65 -0
- package/.aios-core/product/templates/ide-rules/copilot-rules.md +1 -1
- package/.aios-core/product/templates/ide-rules/roo-rules.md +1 -1
- package/.aios-core/user-guide.md +15 -14
- package/.aios-core/workflow-intelligence/engine/output-formatter.js +1 -1
- package/.claude/hooks/synapse-engine.js +9 -20
- package/README.md +14 -7
- package/bin/aios-init.js +255 -184
- package/bin/aios-minimal.js +2 -2
- package/bin/aios.js +4 -4
- package/package.json +6 -1
- package/packages/aios-pro-cli/bin/aios-pro.js +75 -2
- package/packages/aios-pro-cli/package.json +5 -1
- package/packages/aios-pro-cli/src/recover.js +100 -0
- package/packages/installer/src/__tests__/performance-benchmark.js +382 -0
- package/packages/installer/src/config/ide-configs.js +12 -1
- package/packages/installer/src/config/templates/core-config-template.js +2 -2
- package/packages/installer/src/installer/aios-core-installer.js +2 -2
- package/packages/installer/src/installer/file-hasher.js +97 -0
- package/packages/installer/src/installer/post-install-validator.js +41 -1
- package/packages/installer/src/pro/pro-scaffolder.js +335 -0
- package/packages/installer/src/utils/aios-colors.js +2 -2
- package/packages/installer/src/wizard/feedback.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +2 -2
- package/packages/installer/src/wizard/index.js +58 -19
- package/packages/installer/src/wizard/pro-setup.js +931 -0
- package/packages/installer/src/wizard/questions.js +20 -14
- package/packages/installer/src/wizard/validators.js +1 -1
- package/scripts/code-intel-health-check.js +343 -0
- package/scripts/package-synapse.js +323 -0
- 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
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
-
*
|
|
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
|
-
//
|
|
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 = [];
|