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
package/bin/aios-minimal.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* AIOS-FullStack Minimal Installation
|
|
5
5
|
*
|
|
6
6
|
* DEPRECATED (since v3.11.1, scheduled for removal in v5.0.0):
|
|
7
|
-
* The --minimal mode was designed for
|
|
7
|
+
* The --minimal mode was designed for squads which have been
|
|
8
8
|
* replaced by the Squads system (OSR-8). This command now runs the
|
|
9
9
|
* standard wizard through the main router.
|
|
10
10
|
*/
|
|
@@ -14,7 +14,7 @@ const path = require('path');
|
|
|
14
14
|
|
|
15
15
|
// Show deprecation warning
|
|
16
16
|
console.log('\n⚠️ DEPRECATION WARNING: aios-minimal is deprecated.');
|
|
17
|
-
console.log(' The --minimal mode (
|
|
17
|
+
console.log(' The --minimal mode (squads) was replaced by Squads.');
|
|
18
18
|
console.log(' Running standard installation wizard instead.\n');
|
|
19
19
|
|
|
20
20
|
// Get the path to the main router (aios.js)
|
package/bin/aios.js
CHANGED
|
@@ -20,7 +20,7 @@ const command = args[0];
|
|
|
20
20
|
|
|
21
21
|
// Helper: Run initialization wizard
|
|
22
22
|
async function runWizard(options = {}) {
|
|
23
|
-
// Use the
|
|
23
|
+
// Use the v4 wizard from packages/installer/src/wizard/index.js
|
|
24
24
|
const wizardPath = path.join(__dirname, '..', 'packages', 'installer', 'src', 'wizard', 'index.js');
|
|
25
25
|
|
|
26
26
|
if (!fs.existsSync(wizardPath)) {
|
|
@@ -43,7 +43,7 @@ async function runWizard(options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
try {
|
|
46
|
-
// Run the
|
|
46
|
+
// Run the v4 wizard with options
|
|
47
47
|
const { runWizard: executeWizard } = require(wizardPath);
|
|
48
48
|
await executeWizard(options);
|
|
49
49
|
} catch (error) {
|
|
@@ -633,7 +633,7 @@ Options:
|
|
|
633
633
|
What gets removed:
|
|
634
634
|
- .aios-core/ Framework core files
|
|
635
635
|
- docs/stories/ Story files (if created by AIOS)
|
|
636
|
-
- squads/ Squad
|
|
636
|
+
- squads/ Squad definitions
|
|
637
637
|
- .gitignore AIOS-added entries only
|
|
638
638
|
|
|
639
639
|
What is preserved (with --keep-data):
|
|
@@ -702,7 +702,7 @@ async function runUninstall(options = {}) {
|
|
|
702
702
|
// Items to remove
|
|
703
703
|
const itemsToRemove = [
|
|
704
704
|
{ path: '.aios-core', description: 'Framework core' },
|
|
705
|
-
{ path: 'squads', description: 'Squad
|
|
705
|
+
{ path: 'squads', description: 'Squad definitions' },
|
|
706
706
|
];
|
|
707
707
|
|
|
708
708
|
// Optionally remove .aios
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aios-core",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aios": "bin/aios.js",
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
"sync:ide": "node .aios-core/infrastructure/scripts/ide-sync/index.js sync",
|
|
40
40
|
"sync:ide:validate": "node .aios-core/infrastructure/scripts/ide-sync/index.js validate",
|
|
41
41
|
"sync:ide:check": "node .aios-core/infrastructure/scripts/ide-sync/index.js validate --strict",
|
|
42
|
+
"sync:ide:codex": "node .aios-core/infrastructure/scripts/ide-sync/index.js sync --ide codex",
|
|
43
|
+
"sync:skills:codex": "node .aios-core/infrastructure/scripts/codex-skills-sync/index.js",
|
|
44
|
+
"sync:skills:codex:global": "node .aios-core/infrastructure/scripts/codex-skills-sync/index.js --global --global-only",
|
|
45
|
+
"validate:codex-skills": "node .aios-core/infrastructure/scripts/codex-skills-sync/validate.js --strict",
|
|
46
|
+
"validate:paths": "node .aios-core/infrastructure/scripts/validate-paths.js",
|
|
42
47
|
"sync:ide:cursor": "node .aios-core/infrastructure/scripts/ide-sync/index.js sync --ide cursor",
|
|
43
48
|
"sync:ide:windsurf": "node .aios-core/infrastructure/scripts/ide-sync/index.js sync --ide windsurf",
|
|
44
49
|
"prepublishOnly": "npm run generate:manifest && npm run validate:manifest",
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
* status Show license status
|
|
14
14
|
* features List available pro features
|
|
15
15
|
* validate Force online license revalidation
|
|
16
|
+
* recover Recover lost license key via email
|
|
16
17
|
* help Show help
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
const { execSync, spawnSync } = require('child_process');
|
|
20
21
|
const path = require('path');
|
|
21
22
|
const fs = require('fs');
|
|
23
|
+
const { recoverLicense } = require('../src/recover');
|
|
22
24
|
|
|
23
25
|
const PRO_PACKAGE = '@aios-fullstack/pro';
|
|
24
26
|
const VERSION = require('../package.json').version;
|
|
@@ -76,6 +78,51 @@ function delegateToAios(subcommand) {
|
|
|
76
78
|
process.exit(result.status ?? 0);
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Get value of a CLI argument (e.g., --key VALUE).
|
|
83
|
+
*
|
|
84
|
+
* @param {string} flag - Flag name (e.g., '--key')
|
|
85
|
+
* @returns {string|null} Value or null
|
|
86
|
+
*/
|
|
87
|
+
function getArgValue(flag) {
|
|
88
|
+
const idx = args.indexOf(flag);
|
|
89
|
+
if (idx !== -1 && idx + 1 < args.length) {
|
|
90
|
+
return args[idx + 1];
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Run the Pro Installation Wizard.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} [key] - Pre-provided license key
|
|
99
|
+
*/
|
|
100
|
+
function runProWizard(key) {
|
|
101
|
+
// Lazy import to avoid requiring installer when not needed
|
|
102
|
+
let proSetup;
|
|
103
|
+
try {
|
|
104
|
+
proSetup = require('../../installer/src/wizard/pro-setup');
|
|
105
|
+
} catch {
|
|
106
|
+
console.error('Pro wizard module not found.');
|
|
107
|
+
console.error('Ensure aios-core installer is available.\n');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const options = {};
|
|
112
|
+
if (key) {
|
|
113
|
+
options.key = key;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
proSetup.runProWizard(options).then((result) => {
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}).catch((err) => {
|
|
121
|
+
console.error(`\n Wizard failed: ${err.message}\n`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
79
126
|
// ─── Commands ───────────────────────────────────────────────────────────────
|
|
80
127
|
|
|
81
128
|
function showHelp() {
|
|
@@ -87,17 +134,23 @@ Usage:
|
|
|
87
134
|
|
|
88
135
|
Commands:
|
|
89
136
|
install Install ${PRO_PACKAGE} in the current project
|
|
137
|
+
install --wizard Install and run the setup wizard
|
|
138
|
+
setup, wizard Run Pro setup wizard (license gate + scaffold + verify)
|
|
90
139
|
activate --key KEY Activate a license key
|
|
91
140
|
deactivate Deactivate the current license
|
|
92
141
|
status Show license status
|
|
93
142
|
features List available pro features
|
|
94
143
|
validate Force online license revalidation
|
|
144
|
+
recover Recover lost license key via email
|
|
95
145
|
help Show this help message
|
|
96
146
|
|
|
97
147
|
Examples:
|
|
98
148
|
npx aios-pro install
|
|
149
|
+
npx aios-pro setup
|
|
150
|
+
npx aios-pro wizard --key PRO-XXXX-XXXX-XXXX-XXXX
|
|
99
151
|
npx aios-pro activate --key PRO-XXXX-XXXX-XXXX-XXXX
|
|
100
152
|
npx aios-pro status
|
|
153
|
+
npx aios-pro recover
|
|
101
154
|
|
|
102
155
|
Documentation: https://synkra.ai/pro/docs
|
|
103
156
|
`);
|
|
@@ -133,9 +186,29 @@ if (command === '--version' || command === '-v') {
|
|
|
133
186
|
}
|
|
134
187
|
|
|
135
188
|
switch (command) {
|
|
136
|
-
case 'install':
|
|
137
|
-
|
|
189
|
+
case 'install': {
|
|
190
|
+
// Check for --wizard flag to run wizard after install
|
|
191
|
+
const runWizardAfter = args.includes('--wizard');
|
|
138
192
|
installPro();
|
|
193
|
+
if (runWizardAfter) {
|
|
194
|
+
runProWizard();
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case 'setup':
|
|
200
|
+
case 'wizard': {
|
|
201
|
+
// Run the Pro Installation Wizard with license gate
|
|
202
|
+
const wizardKey = getArgValue('--key');
|
|
203
|
+
runProWizard(wizardKey);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case 'recover':
|
|
208
|
+
recoverLicense().catch((err) => {
|
|
209
|
+
console.error(`\n Recovery failed: ${err.message}\n`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|
|
139
212
|
break;
|
|
140
213
|
|
|
141
214
|
case 'activate':
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* License Recovery Flow
|
|
5
|
+
*
|
|
6
|
+
* Implements OWASP-compliant license key recovery via portal redirect.
|
|
7
|
+
* Decision 3 (D1 Section 4): Portal-first — CLI redirects to web portal.
|
|
8
|
+
*
|
|
9
|
+
* Security:
|
|
10
|
+
* - Anti-enumeration: identical message for any email input
|
|
11
|
+
* - No API calls — purely local CLI + browser redirect
|
|
12
|
+
* - Rate limiting is server-side (3/email/hour) — documented, not enforced here
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
|
|
17
|
+
const RECOVERY_URL = 'https://pro.synkra.ai/recover';
|
|
18
|
+
|
|
19
|
+
const RECOVERY_MESSAGE =
|
|
20
|
+
'Se este email estiver associado a uma licenca, voce recebera instrucoes de recuperacao.';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mask email for display: u***@example.com
|
|
24
|
+
* @param {string} email
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function maskEmail(email) {
|
|
28
|
+
const atIndex = email.indexOf('@');
|
|
29
|
+
if (atIndex <= 0) {
|
|
30
|
+
return '***';
|
|
31
|
+
}
|
|
32
|
+
return email[0] + '***' + email.slice(atIndex);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Prompt user for email input via readline
|
|
37
|
+
* @returns {Promise<string>}
|
|
38
|
+
*/
|
|
39
|
+
function promptEmail() {
|
|
40
|
+
const rl = readline.createInterface({
|
|
41
|
+
input: process.stdin,
|
|
42
|
+
output: process.stdout,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
rl.question(' Enter your email: ', (answer) => {
|
|
47
|
+
rl.close();
|
|
48
|
+
resolve(answer.trim());
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Open URL in default browser with offline fallback
|
|
55
|
+
* @param {string} url
|
|
56
|
+
* @param {Function} [openFn] - Optional override for testing (defaults to dynamic import of 'open')
|
|
57
|
+
* @returns {Promise<boolean>} true if browser opened, false if fallback
|
|
58
|
+
*/
|
|
59
|
+
async function openBrowser(url, openFn) {
|
|
60
|
+
try {
|
|
61
|
+
const open = openFn || (await import('open')).default;
|
|
62
|
+
await open(url);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Main recovery flow
|
|
71
|
+
* @returns {Promise<void>}
|
|
72
|
+
*/
|
|
73
|
+
async function recoverLicense() {
|
|
74
|
+
console.log('\naios-pro — License Recovery\n');
|
|
75
|
+
|
|
76
|
+
const email = await promptEmail();
|
|
77
|
+
|
|
78
|
+
if (!email) {
|
|
79
|
+
console.error('\n Error: Email is required.\n');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const masked = maskEmail(email);
|
|
84
|
+
|
|
85
|
+
// Anti-enumeration: identical message regardless of email validity
|
|
86
|
+
console.log(`\n ${RECOVERY_MESSAGE}`);
|
|
87
|
+
console.log(` Email: ${masked}\n`);
|
|
88
|
+
|
|
89
|
+
const browserOpened = await openBrowser(RECOVERY_URL);
|
|
90
|
+
|
|
91
|
+
if (browserOpened) {
|
|
92
|
+
console.log(' Recovery portal opened in your browser.');
|
|
93
|
+
} else {
|
|
94
|
+
console.log(' Could not open browser automatically.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(` You can also visit: ${RECOVERY_URL}\n`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { recoverLicense, maskEmail, promptEmail, openBrowser, RECOVERY_URL, RECOVERY_MESSAGE };
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AIOS Installer Performance Benchmark
|
|
5
|
+
* Story INS-2: Installer Performance Optimization
|
|
6
|
+
*
|
|
7
|
+
* Measures baseline performance metrics for the installer to track optimization progress.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node performance-benchmark.js [--output <file>] [--runs <n>]
|
|
11
|
+
*
|
|
12
|
+
* Output:
|
|
13
|
+
* JSON report with phase timings and statistics
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const fse = require('fs-extra');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const { performance } = require('perf_hooks');
|
|
21
|
+
|
|
22
|
+
// Configuration
|
|
23
|
+
const CONFIG = {
|
|
24
|
+
runs: 3, // Number of runs for averaging
|
|
25
|
+
outputFile: null, // Output file path (null = stdout)
|
|
26
|
+
testProjectSize: 1000, // Number of files for test project
|
|
27
|
+
verbose: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Parse CLI arguments
|
|
31
|
+
process.argv.slice(2).forEach((arg, i, arr) => {
|
|
32
|
+
if (arg === '--output' && arr[i + 1]) CONFIG.outputFile = arr[i + 1];
|
|
33
|
+
if (arg === '--runs' && arr[i + 1]) CONFIG.runs = parseInt(arr[i + 1], 10);
|
|
34
|
+
if (arg === '--verbose' || arg === '-v') CONFIG.verbose = true;
|
|
35
|
+
if (arg === '--help' || arg === '-h') {
|
|
36
|
+
console.log(`
|
|
37
|
+
AIOS Installer Performance Benchmark
|
|
38
|
+
|
|
39
|
+
Usage: node performance-benchmark.js [options]
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
--output <file> Save JSON report to file (default: stdout)
|
|
43
|
+
--runs <n> Number of benchmark runs (default: 3)
|
|
44
|
+
--verbose, -v Show detailed progress
|
|
45
|
+
--help, -h Show this help
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
node performance-benchmark.js --output baseline.json --runs 5
|
|
49
|
+
`);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Benchmark results structure
|
|
55
|
+
const results = {
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
system: {
|
|
58
|
+
platform: process.platform,
|
|
59
|
+
arch: process.arch,
|
|
60
|
+
nodeVersion: process.version,
|
|
61
|
+
cpus: require('os').cpus().length,
|
|
62
|
+
totalMemory: Math.round(require('os').totalmem() / 1024 / 1024) + ' MB',
|
|
63
|
+
},
|
|
64
|
+
config: { ...CONFIG },
|
|
65
|
+
phases: {},
|
|
66
|
+
summary: {},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Timer utility for measuring phase durations
|
|
71
|
+
*/
|
|
72
|
+
class Timer {
|
|
73
|
+
constructor(name) {
|
|
74
|
+
this.name = name;
|
|
75
|
+
this.start = null;
|
|
76
|
+
this.end = null;
|
|
77
|
+
this.runs = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
begin() {
|
|
81
|
+
this.start = performance.now();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
stop() {
|
|
85
|
+
this.end = performance.now();
|
|
86
|
+
const duration = this.end - this.start;
|
|
87
|
+
this.runs.push(duration);
|
|
88
|
+
return duration;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getStats() {
|
|
92
|
+
if (this.runs.length === 0) return null;
|
|
93
|
+
const sorted = [...this.runs].sort((a, b) => a - b);
|
|
94
|
+
return {
|
|
95
|
+
min: Math.round(sorted[0]),
|
|
96
|
+
max: Math.round(sorted[sorted.length - 1]),
|
|
97
|
+
avg: Math.round(this.runs.reduce((a, b) => a + b, 0) / this.runs.length),
|
|
98
|
+
median: Math.round(sorted[Math.floor(sorted.length / 2)]),
|
|
99
|
+
runs: this.runs.map((r) => Math.round(r)),
|
|
100
|
+
unit: 'ms',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Phase timers
|
|
106
|
+
const timers = {
|
|
107
|
+
directoryRead: new Timer('Directory Read (readdirSync)'),
|
|
108
|
+
directoryReadWithTypes: new Timer('Directory Read (withFileTypes)'),
|
|
109
|
+
statLoop: new Timer('Stat Loop (statSync per file)'),
|
|
110
|
+
realpathSingle: new Timer('Realpath (single call)'),
|
|
111
|
+
realpathDouble: new Timer('Realpath (double call - current)'),
|
|
112
|
+
hashSequential: new Timer('Hash Files (sequential)'),
|
|
113
|
+
hashParallelBatch: new Timer('Hash Files (parallel batch)'),
|
|
114
|
+
fileCopySequential: new Timer('File Copy (sequential)'),
|
|
115
|
+
fileCopyParallel: new Timer('File Copy (parallel)'),
|
|
116
|
+
totalInstallSimulation: new Timer('Total Install Simulation'),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Log if verbose mode is enabled
|
|
121
|
+
*/
|
|
122
|
+
function log(msg) {
|
|
123
|
+
if (CONFIG.verbose) console.log(`[benchmark] ${msg}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get the .aios-core directory for benchmarking
|
|
128
|
+
*/
|
|
129
|
+
function getAiosCoreDir() {
|
|
130
|
+
const projectRoot = path.resolve(__dirname, '../../../../');
|
|
131
|
+
return path.join(projectRoot, '.aios-core');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Benchmark: Directory read comparison
|
|
136
|
+
*/
|
|
137
|
+
async function benchmarkDirectoryRead(dir) {
|
|
138
|
+
const files = fs.readdirSync(dir);
|
|
139
|
+
|
|
140
|
+
// Method 1: readdirSync + statSync for each
|
|
141
|
+
timers.statLoop.begin();
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const fullPath = path.join(dir, file);
|
|
144
|
+
fs.statSync(fullPath).isDirectory();
|
|
145
|
+
}
|
|
146
|
+
timers.statLoop.stop();
|
|
147
|
+
|
|
148
|
+
// Method 2: readdirSync with withFileTypes
|
|
149
|
+
timers.directoryReadWithTypes.begin();
|
|
150
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
entry.isDirectory();
|
|
153
|
+
}
|
|
154
|
+
timers.directoryReadWithTypes.stop();
|
|
155
|
+
|
|
156
|
+
return files.length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Benchmark: Realpath comparison
|
|
161
|
+
*/
|
|
162
|
+
async function benchmarkRealpath(files) {
|
|
163
|
+
const sampleFiles = files.slice(0, 100); // Sample 100 files
|
|
164
|
+
|
|
165
|
+
// Method 1: Single realpath call
|
|
166
|
+
timers.realpathSingle.begin();
|
|
167
|
+
for (const file of sampleFiles) {
|
|
168
|
+
fs.realpathSync(file);
|
|
169
|
+
}
|
|
170
|
+
timers.realpathSingle.stop();
|
|
171
|
+
|
|
172
|
+
// Method 2: Double realpath call (current behavior)
|
|
173
|
+
timers.realpathDouble.begin();
|
|
174
|
+
for (const file of sampleFiles) {
|
|
175
|
+
fs.realpathSync(file);
|
|
176
|
+
fs.realpathSync(path.dirname(file)); // Simulates the duplicate call
|
|
177
|
+
}
|
|
178
|
+
timers.realpathDouble.stop();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Benchmark: File hashing comparison
|
|
183
|
+
*/
|
|
184
|
+
async function benchmarkHashing(files) {
|
|
185
|
+
const sampleFiles = files.slice(0, 200); // Sample 200 files for hashing
|
|
186
|
+
|
|
187
|
+
// Method 1: Sequential hashing
|
|
188
|
+
timers.hashSequential.begin();
|
|
189
|
+
for (const file of sampleFiles) {
|
|
190
|
+
try {
|
|
191
|
+
const content = fs.readFileSync(file);
|
|
192
|
+
crypto.createHash('sha256').update(content).digest('hex');
|
|
193
|
+
} catch {
|
|
194
|
+
// Skip files that can't be read
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
timers.hashSequential.stop();
|
|
198
|
+
|
|
199
|
+
// Method 2: Parallel batch hashing
|
|
200
|
+
timers.hashParallelBatch.begin();
|
|
201
|
+
const batchSize = 50;
|
|
202
|
+
for (let i = 0; i < sampleFiles.length; i += batchSize) {
|
|
203
|
+
const batch = sampleFiles.slice(i, i + batchSize);
|
|
204
|
+
await Promise.all(
|
|
205
|
+
batch.map(async (file) => {
|
|
206
|
+
try {
|
|
207
|
+
const content = await fse.readFile(file);
|
|
208
|
+
crypto.createHash('sha256').update(content).digest('hex');
|
|
209
|
+
} catch {
|
|
210
|
+
// Skip files that can't be read
|
|
211
|
+
}
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
timers.hashParallelBatch.stop();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Collect all files recursively
|
|
220
|
+
*/
|
|
221
|
+
function collectFiles(dir, maxFiles = 1000) {
|
|
222
|
+
const files = [];
|
|
223
|
+
|
|
224
|
+
function walk(currentDir) {
|
|
225
|
+
if (files.length >= maxFiles) return;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
if (files.length >= maxFiles) break;
|
|
231
|
+
|
|
232
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
233
|
+
if (entry.isDirectory()) {
|
|
234
|
+
walk(fullPath);
|
|
235
|
+
} else if (entry.isFile()) {
|
|
236
|
+
files.push(fullPath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
// Skip directories we can't read
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
walk(dir);
|
|
245
|
+
return files;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Run all benchmarks
|
|
250
|
+
*/
|
|
251
|
+
async function runBenchmarks() {
|
|
252
|
+
const aiosCoreDir = getAiosCoreDir();
|
|
253
|
+
|
|
254
|
+
if (!fs.existsSync(aiosCoreDir)) {
|
|
255
|
+
console.error(`Error: .aios-core directory not found at ${aiosCoreDir}`);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
log(`Starting benchmark with ${CONFIG.runs} runs`);
|
|
260
|
+
log(`Using .aios-core at: ${aiosCoreDir}`);
|
|
261
|
+
|
|
262
|
+
// Collect files for benchmarking
|
|
263
|
+
log('Collecting files...');
|
|
264
|
+
const allFiles = collectFiles(aiosCoreDir, CONFIG.testProjectSize);
|
|
265
|
+
log(`Collected ${allFiles.length} files`);
|
|
266
|
+
|
|
267
|
+
results.fileCount = allFiles.length;
|
|
268
|
+
|
|
269
|
+
// Run benchmarks multiple times
|
|
270
|
+
for (let run = 1; run <= CONFIG.runs; run++) {
|
|
271
|
+
log(`\n--- Run ${run}/${CONFIG.runs} ---`);
|
|
272
|
+
|
|
273
|
+
// Directory read benchmarks
|
|
274
|
+
const agentsDir = path.join(aiosCoreDir, 'development', 'agents');
|
|
275
|
+
if (fs.existsSync(agentsDir)) {
|
|
276
|
+
log('Benchmarking directory read...');
|
|
277
|
+
await benchmarkDirectoryRead(agentsDir);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Realpath benchmarks
|
|
281
|
+
log('Benchmarking realpath...');
|
|
282
|
+
await benchmarkRealpath(allFiles);
|
|
283
|
+
|
|
284
|
+
// Hashing benchmarks
|
|
285
|
+
log('Benchmarking file hashing...');
|
|
286
|
+
await benchmarkHashing(allFiles);
|
|
287
|
+
|
|
288
|
+
// Total simulation
|
|
289
|
+
log('Running total install simulation...');
|
|
290
|
+
timers.totalInstallSimulation.begin();
|
|
291
|
+
|
|
292
|
+
// Simulate full install: read dirs + hash files
|
|
293
|
+
const devDir = path.join(aiosCoreDir, 'development');
|
|
294
|
+
if (fs.existsSync(devDir)) {
|
|
295
|
+
const subdirs = fs.readdirSync(devDir, { withFileTypes: true });
|
|
296
|
+
for (const subdir of subdirs) {
|
|
297
|
+
if (subdir.isDirectory()) {
|
|
298
|
+
const fullSubdir = path.join(devDir, subdir.name);
|
|
299
|
+
fs.readdirSync(fullSubdir);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Simulate sequential file processing
|
|
305
|
+
for (const file of allFiles.slice(0, 500)) {
|
|
306
|
+
try {
|
|
307
|
+
fs.statSync(file);
|
|
308
|
+
fs.readFileSync(file);
|
|
309
|
+
} catch {
|
|
310
|
+
// Skip
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
timers.totalInstallSimulation.stop();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Compile results
|
|
318
|
+
log('\nCompiling results...');
|
|
319
|
+
|
|
320
|
+
for (const [name, timer] of Object.entries(timers)) {
|
|
321
|
+
const stats = timer.getStats();
|
|
322
|
+
if (stats) {
|
|
323
|
+
results.phases[name] = {
|
|
324
|
+
description: timer.name,
|
|
325
|
+
...stats,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Calculate summary
|
|
331
|
+
const hashSeq = results.phases.hashSequential?.avg || 0;
|
|
332
|
+
const hashPar = results.phases.hashParallelBatch?.avg || 0;
|
|
333
|
+
const realpathSingle = results.phases.realpathSingle?.avg || 0;
|
|
334
|
+
const realpathDouble = results.phases.realpathDouble?.avg || 0;
|
|
335
|
+
const statLoop = results.phases.statLoop?.avg || 0;
|
|
336
|
+
const withTypes = results.phases.directoryReadWithTypes?.avg || 0;
|
|
337
|
+
|
|
338
|
+
results.summary = {
|
|
339
|
+
totalFiles: results.fileCount,
|
|
340
|
+
hashingSpeedup: hashSeq > 0 ? `${(hashSeq / hashPar).toFixed(2)}x` : 'N/A',
|
|
341
|
+
realpathSavings: realpathDouble > 0 ? `${Math.round(((realpathDouble - realpathSingle) / realpathDouble) * 100)}%` : 'N/A',
|
|
342
|
+
statLoopSavings: statLoop > 0 ? `${Math.round(((statLoop - withTypes) / statLoop) * 100)}%` : 'N/A',
|
|
343
|
+
estimatedTotalTime: results.phases.totalInstallSimulation?.avg || 0,
|
|
344
|
+
target: '<30000ms for 1000 files',
|
|
345
|
+
baseline: `${results.phases.totalInstallSimulation?.avg || 'TBD'}ms`,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Output results
|
|
349
|
+
const output = JSON.stringify(results, null, 2);
|
|
350
|
+
|
|
351
|
+
if (CONFIG.outputFile) {
|
|
352
|
+
fs.writeFileSync(CONFIG.outputFile, output);
|
|
353
|
+
console.log(`Benchmark results saved to: ${CONFIG.outputFile}`);
|
|
354
|
+
} else {
|
|
355
|
+
console.log(output);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Print summary to stderr for visibility
|
|
359
|
+
console.error('\n' + '='.repeat(60));
|
|
360
|
+
console.error('AIOS Installer Performance Baseline');
|
|
361
|
+
console.error('='.repeat(60));
|
|
362
|
+
console.error(`Files analyzed: ${results.fileCount}`);
|
|
363
|
+
console.error(`Runs: ${CONFIG.runs}`);
|
|
364
|
+
console.error('');
|
|
365
|
+
console.error('Phase Results (avg ms):');
|
|
366
|
+
console.error(` Directory stat loop: ${statLoop}ms`);
|
|
367
|
+
console.error(` Directory withFileTypes: ${withTypes}ms (${results.summary.statLoopSavings} faster)`);
|
|
368
|
+
console.error(` Realpath single: ${realpathSingle}ms`);
|
|
369
|
+
console.error(` Realpath double: ${realpathDouble}ms (${results.summary.realpathSavings} overhead)`);
|
|
370
|
+
console.error(` Hash sequential: ${hashSeq}ms`);
|
|
371
|
+
console.error(` Hash parallel: ${hashPar}ms (${results.summary.hashingSpeedup} faster)`);
|
|
372
|
+
console.error('');
|
|
373
|
+
console.error(`Total Install Simulation: ${results.summary.baseline}`);
|
|
374
|
+
console.error(`Target: ${results.summary.target}`);
|
|
375
|
+
console.error('='.repeat(60));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Run benchmarks
|
|
379
|
+
runBenchmarks().catch((err) => {
|
|
380
|
+
console.error('Benchmark failed:', err);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
});
|