sinapse-ai 1.9.0 → 1.9.1
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/.claude/rules/mandatory-delegation.md +1 -1
- package/.codex/delegation-matrix.json +4 -3
- package/.codex/delegation-parity.json +4 -3
- package/.codex/instructions.md +2 -2
- package/.sinapse-ai/constitution.md +2 -2
- package/.sinapse-ai/core/doctor/checks/git-hooks.js +76 -10
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +1 -1
- package/.sinapse-ai/core/synapse/engine.js +15 -0
- package/.sinapse-ai/data/entity-registry.yaml +13 -13
- package/.sinapse-ai/development/agents/snps-orqx.md +4 -4
- package/.sinapse-ai/git-hooks/lib/secret-scanner-core.js +76 -4
- package/.sinapse-ai/git-hooks/pre-push +7 -1
- package/.sinapse-ai/install-manifest.yaml +9 -9
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +1247 -0
- package/bin/commands/uninstall.js +2 -2
- package/bin/utils/secret-scanner-core.js +76 -4
- package/docs/agent-reference-guide.md +1 -1
- package/docs/framework/architecture-overview.md +4 -4
- package/docs/framework/guiding-principles.md +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/guides/agent-reference.md +1 -1
- package/docs/guides/codex-config.md +4 -5
- package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
- package/package.json +8 -2
- package/packages/installer/src/installer/git-hooks-installer.js +3 -1
- package/packages/installer/src/wizard/ide-config-generator.js +9 -1
- package/packages/installer/src/wizard/index.js +3 -4
- package/scripts/regenerate-orqx-stubs.ps1 +0 -1
- package/scripts/sync-counts.js +10 -2
- package/scripts/sync-squad-yaml-components.js +108 -6
- package/scripts/validate-squad-orqx.js +19 -9
- package/sinapse/agents/sinapse-orqx.md +4 -4
- package/sinapse/agents/snps-orqx.md +4 -4
- package/sinapse/knowledge-base/routing-catalog.md +1 -1
- package/sinapse/tasks/diagnose-and-route.md +1 -1
- package/sinapse/tasks/squad-status-report.md +1 -1
- package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
- package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
- package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
- package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
- package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
- package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
- package/squads/squad-animations/README.md +1 -1
- package/squads/squad-cloning/README.md +1 -1
- package/squads/squad-commercial/README.md +1 -1
- package/squads/squad-content/README.md +1 -1
- package/squads/squad-copy/README.md +1 -1
- package/squads/squad-council/README.md +1 -1
- package/squads/squad-courses/README.md +1 -1
- package/squads/squad-cybersecurity/README.md +1 -1
- package/squads/squad-design/README.md +1 -1
- package/squads/squad-finance/README.md +1 -1
- package/squads/squad-growth/README.md +1 -1
- package/squads/squad-paidmedia/README.md +1 -1
- package/squads/squad-product/README.md +1 -1
- package/squads/squad-research/README.md +1 -1
- package/squads/squad-storytelling/README.md +1 -1
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
- package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
- package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
- package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
- package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
- package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
- package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
- package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
- package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
- package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
- package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
- package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
- package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
- package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
- package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
- package/packages/installer/tests/unit/config-validator.test.js +0 -315
- package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
- package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
- package/packages/installer/tests/unit/env-template.test.js +0 -187
- package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
- package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
- package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
- package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
- package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
- package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
- package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
- package/packages/sinapse-install/tests/unit/chrome-brain.smoke.test.js +0 -66
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Unit tests for WorkflowRegistry
|
|
3
|
-
* @story WIS-2 - Workflow Registry Enhancement
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const {
|
|
10
|
-
WorkflowRegistry,
|
|
11
|
-
createWorkflowRegistry,
|
|
12
|
-
DEFAULT_CACHE_TTL,
|
|
13
|
-
} = require('../registry/workflow-registry');
|
|
14
|
-
|
|
15
|
-
describe('WorkflowRegistry', () => {
|
|
16
|
-
let registry;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
registry = createWorkflowRegistry();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
registry.invalidateCache();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('constructor', () => {
|
|
27
|
-
it('should create registry with default options', () => {
|
|
28
|
-
expect(registry.cacheTTL).toBe(DEFAULT_CACHE_TTL);
|
|
29
|
-
expect(registry.cache).toBeNull();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should accept custom cache TTL', () => {
|
|
33
|
-
const customRegistry = createWorkflowRegistry({ cacheTTL: 1000 });
|
|
34
|
-
expect(customRegistry.cacheTTL).toBe(1000);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should accept custom patterns path', () => {
|
|
38
|
-
const customPath = '/custom/path.yaml';
|
|
39
|
-
const customRegistry = createWorkflowRegistry({ patternsPath: customPath });
|
|
40
|
-
expect(customRegistry.patternsPath).toBe(customPath);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('loadWorkflows()', () => {
|
|
45
|
-
it('should load workflow patterns from file', () => {
|
|
46
|
-
const workflows = registry.loadWorkflows();
|
|
47
|
-
expect(workflows).toBeDefined();
|
|
48
|
-
expect(typeof workflows).toBe('object');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should return 10 workflows', () => {
|
|
52
|
-
const workflows = registry.loadWorkflows();
|
|
53
|
-
const names = Object.keys(workflows);
|
|
54
|
-
expect(names.length).toBe(10);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should include story_development workflow', () => {
|
|
58
|
-
const workflows = registry.loadWorkflows();
|
|
59
|
-
expect(workflows.story_development).toBeDefined();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should include epic_creation workflow', () => {
|
|
63
|
-
const workflows = registry.loadWorkflows();
|
|
64
|
-
expect(workflows.epic_creation).toBeDefined();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should cache loaded workflows', () => {
|
|
68
|
-
const workflows1 = registry.loadWorkflows();
|
|
69
|
-
const workflows2 = registry.loadWorkflows();
|
|
70
|
-
expect(workflows1).toBe(workflows2);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should throw error for invalid file path', () => {
|
|
74
|
-
const badRegistry = createWorkflowRegistry({
|
|
75
|
-
patternsPath: '/nonexistent/path.yaml',
|
|
76
|
-
});
|
|
77
|
-
expect(() => badRegistry.loadWorkflows()).toThrow('Workflow patterns file not found');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('isCacheValid()', () => {
|
|
82
|
-
it('should return false when cache is null', () => {
|
|
83
|
-
expect(registry.isCacheValid()).toBe(false);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should return true after loading workflows', () => {
|
|
87
|
-
registry.loadWorkflows();
|
|
88
|
-
expect(registry.isCacheValid()).toBe(true);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should return false after invalidation', () => {
|
|
92
|
-
registry.loadWorkflows();
|
|
93
|
-
registry.invalidateCache();
|
|
94
|
-
expect(registry.isCacheValid()).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('getWorkflowNames()', () => {
|
|
99
|
-
it('should return array of workflow names', () => {
|
|
100
|
-
const names = registry.getWorkflowNames();
|
|
101
|
-
expect(Array.isArray(names)).toBe(true);
|
|
102
|
-
expect(names.length).toBe(10);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should include expected workflows', () => {
|
|
106
|
-
const names = registry.getWorkflowNames();
|
|
107
|
-
expect(names).toContain('story_development');
|
|
108
|
-
expect(names).toContain('epic_creation');
|
|
109
|
-
expect(names).toContain('backlog_management');
|
|
110
|
-
expect(names).toContain('architecture_review');
|
|
111
|
-
expect(names).toContain('git_workflow');
|
|
112
|
-
expect(names).toContain('database_workflow');
|
|
113
|
-
expect(names).toContain('code_quality_workflow');
|
|
114
|
-
expect(names).toContain('documentation_workflow');
|
|
115
|
-
expect(names).toContain('ux_workflow');
|
|
116
|
-
expect(names).toContain('research_workflow');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('getWorkflow()', () => {
|
|
121
|
-
it('should return workflow by name', () => {
|
|
122
|
-
const workflow = registry.getWorkflow('epic_creation');
|
|
123
|
-
expect(workflow).toBeDefined();
|
|
124
|
-
expect(workflow.description).toBeDefined();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should return null for unknown workflow', () => {
|
|
128
|
-
const workflow = registry.getWorkflow('nonexistent');
|
|
129
|
-
expect(workflow).toBeNull();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should include transitions in workflow', () => {
|
|
133
|
-
const workflow = registry.getWorkflow('epic_creation');
|
|
134
|
-
expect(workflow.transitions).toBeDefined();
|
|
135
|
-
expect(typeof workflow.transitions).toBe('object');
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe('matchWorkflow()', () => {
|
|
140
|
-
it('should match workflow from command history', () => {
|
|
141
|
-
const commands = ['create-epic', 'create-story'];
|
|
142
|
-
const match = registry.matchWorkflow(commands);
|
|
143
|
-
|
|
144
|
-
expect(match).not.toBeNull();
|
|
145
|
-
expect(match.name).toBe('epic_creation');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should return best matching workflow', () => {
|
|
149
|
-
const commands = ['validate-story-draft', 'develop', 'review-qa'];
|
|
150
|
-
const match = registry.matchWorkflow(commands);
|
|
151
|
-
|
|
152
|
-
expect(match).not.toBeNull();
|
|
153
|
-
expect(match.name).toBe('story_development');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should return null for empty commands', () => {
|
|
157
|
-
const match = registry.matchWorkflow([]);
|
|
158
|
-
expect(match).toBeNull();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should return null for unmatched commands', () => {
|
|
162
|
-
const match = registry.matchWorkflow(['random-cmd', 'another-random']);
|
|
163
|
-
expect(match).toBeNull();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should include score in match result', () => {
|
|
167
|
-
const match = registry.matchWorkflow(['create-epic', 'create-story']);
|
|
168
|
-
expect(match.score).toBeGreaterThanOrEqual(2);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should include matched commands', () => {
|
|
172
|
-
const match = registry.matchWorkflow(['create-epic', 'create-story']);
|
|
173
|
-
expect(match.matchedCommands).toBeDefined();
|
|
174
|
-
expect(Array.isArray(match.matchedCommands)).toBe(true);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe('getTransitions()', () => {
|
|
179
|
-
it('should return transitions for valid state', () => {
|
|
180
|
-
const transition = registry.getTransitions('epic_creation', 'epic_drafted');
|
|
181
|
-
|
|
182
|
-
expect(transition).not.toBeNull();
|
|
183
|
-
expect(transition.trigger).toBeDefined();
|
|
184
|
-
expect(transition.confidence).toBeDefined();
|
|
185
|
-
expect(transition.next_steps).toBeDefined();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should return null for unknown state', () => {
|
|
189
|
-
const transition = registry.getTransitions('epic_creation', 'unknown_state');
|
|
190
|
-
expect(transition).toBeNull();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('should return null for unknown workflow', () => {
|
|
194
|
-
const transition = registry.getTransitions('unknown_workflow', 'any_state');
|
|
195
|
-
expect(transition).toBeNull();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should return empty array for workflow without transitions', () => {
|
|
199
|
-
const result = registry.getAllTransitions('nonexistent');
|
|
200
|
-
expect(result).toEqual({});
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe('getNextSteps()', () => {
|
|
205
|
-
it('should return sorted next steps', () => {
|
|
206
|
-
const steps = registry.getNextSteps('epic_creation', 'epic_drafted');
|
|
207
|
-
|
|
208
|
-
expect(Array.isArray(steps)).toBe(true);
|
|
209
|
-
expect(steps.length).toBeGreaterThan(0);
|
|
210
|
-
expect(steps[0].priority).toBeLessThanOrEqual(steps[1]?.priority || 99);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should include command and description', () => {
|
|
214
|
-
const steps = registry.getNextSteps('epic_creation', 'epic_drafted');
|
|
215
|
-
|
|
216
|
-
expect(steps[0].command).toBeDefined();
|
|
217
|
-
expect(steps[0].description).toBeDefined();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('should return empty array for unknown state', () => {
|
|
221
|
-
const steps = registry.getNextSteps('epic_creation', 'unknown_state');
|
|
222
|
-
expect(steps).toEqual([]);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('findCurrentState()', () => {
|
|
227
|
-
it('should find state from command', () => {
|
|
228
|
-
const state = registry.findCurrentState('epic_creation', 'create-epic completed');
|
|
229
|
-
expect(state).toBe('epic_drafted');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should find state without "completed" suffix', () => {
|
|
233
|
-
const state = registry.findCurrentState('epic_creation', 'create-epic');
|
|
234
|
-
expect(state).toBe('epic_drafted');
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('should return null for unknown command', () => {
|
|
238
|
-
const state = registry.findCurrentState('epic_creation', 'random-command');
|
|
239
|
-
expect(state).toBeNull();
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should return null for unknown workflow', () => {
|
|
243
|
-
const state = registry.findCurrentState('unknown', 'create-epic');
|
|
244
|
-
expect(state).toBeNull();
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe('getWorkflowsByAgent()', () => {
|
|
249
|
-
it('should return workflows for agent', () => {
|
|
250
|
-
const workflows = registry.getWorkflowsByAgent('@po');
|
|
251
|
-
|
|
252
|
-
expect(Array.isArray(workflows)).toBe(true);
|
|
253
|
-
expect(workflows.length).toBeGreaterThan(0);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should handle agent without @ prefix', () => {
|
|
257
|
-
const workflows = registry.getWorkflowsByAgent('po');
|
|
258
|
-
expect(workflows.length).toBeGreaterThan(0);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('should return empty array for unknown agent', () => {
|
|
262
|
-
const workflows = registry.getWorkflowsByAgent('@unknown');
|
|
263
|
-
expect(workflows).toEqual([]);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe('getStats()', () => {
|
|
268
|
-
it('should return registry statistics', () => {
|
|
269
|
-
const stats = registry.getStats();
|
|
270
|
-
|
|
271
|
-
expect(stats.totalWorkflows).toBe(10);
|
|
272
|
-
expect(stats.workflowsWithTransitions).toBe(10);
|
|
273
|
-
expect(stats.totalTransitions).toBeGreaterThan(0);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('should report cache status', () => {
|
|
277
|
-
registry.loadWorkflows();
|
|
278
|
-
const stats = registry.getStats();
|
|
279
|
-
|
|
280
|
-
expect(stats.cacheValid).toBe(true);
|
|
281
|
-
expect(stats.cacheAge).toBeGreaterThanOrEqual(0);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
describe('normalizeCommand()', () => {
|
|
286
|
-
it('should normalize command strings', () => {
|
|
287
|
-
expect(registry.normalizeCommand('CREATE-EPIC')).toBe('create-epic');
|
|
288
|
-
expect(registry.normalizeCommand('*create-epic')).toBe('create-epic');
|
|
289
|
-
expect(registry.normalizeCommand('create-epic completed')).toBe('create-epic');
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('should handle null input', () => {
|
|
293
|
-
expect(registry.normalizeCommand(null)).toBe('');
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
describe('DEFAULT_CACHE_TTL', () => {
|
|
298
|
-
it('should be 5 minutes', () => {
|
|
299
|
-
expect(DEFAULT_CACHE_TTL).toBe(5 * 60 * 1000);
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SINAPSE 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
|
-
SINAPSE 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 .sinapse-ai directory for benchmarking
|
|
128
|
-
*/
|
|
129
|
-
function getSinapseCoreDir() {
|
|
130
|
-
const projectRoot = path.resolve(__dirname, '../../../../');
|
|
131
|
-
return path.join(projectRoot, '.sinapse-ai');
|
|
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 sinapseCoreDir = getSinapseCoreDir();
|
|
253
|
-
|
|
254
|
-
if (!fs.existsSync(sinapseCoreDir)) {
|
|
255
|
-
console.error(`Error: .sinapse-ai directory not found at ${sinapseCoreDir}`);
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
log(`Starting benchmark with ${CONFIG.runs} runs`);
|
|
260
|
-
log(`Using .sinapse-ai at: ${sinapseCoreDir}`);
|
|
261
|
-
|
|
262
|
-
// Collect files for benchmarking
|
|
263
|
-
log('Collecting files...');
|
|
264
|
-
const allFiles = collectFiles(sinapseCoreDir, 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(sinapseCoreDir, '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(sinapseCoreDir, '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('SINAPSE 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
|
-
});
|
|
383
|
-
|