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,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests: Doctor Orchestrator
|
|
3
|
-
* Story INS-4.1: sinapse doctor rewrite
|
|
4
|
-
*
|
|
5
|
-
* Tests for options forwarding, output format, and fix/dry-run behavior.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// Use real modules for orchestrator testing (checks will hit real filesystem)
|
|
13
|
-
const { runDoctorChecks, DOCTOR_VERSION, resolveExitCode } = require('../../../../../.sinapse-ai/core/doctor');
|
|
14
|
-
|
|
15
|
-
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..', '..');
|
|
16
|
-
|
|
17
|
-
describe('Doctor Orchestrator', () => {
|
|
18
|
-
describe('version', () => {
|
|
19
|
-
it('should export DOCTOR_VERSION as semver string (Story A.3 bumped to 2.1.0)', () => {
|
|
20
|
-
expect(DOCTOR_VERSION).toBe('2.1.0');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('options forwarding (AC1)', () => {
|
|
25
|
-
it('should accept and use options object', async () => {
|
|
26
|
-
const result = await runDoctorChecks({ projectRoot });
|
|
27
|
-
expect(result).toHaveProperty('formatted');
|
|
28
|
-
expect(result).toHaveProperty('data');
|
|
29
|
-
expect(result.data).toHaveProperty('version', '2.1.0');
|
|
30
|
-
expect(result.data).toHaveProperty('summary');
|
|
31
|
-
expect(result.data).toHaveProperty('checks');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should produce JSON when json option is true', async () => {
|
|
35
|
-
const result = await runDoctorChecks({ json: true, projectRoot });
|
|
36
|
-
expect(result.formatted).toBeTruthy();
|
|
37
|
-
|
|
38
|
-
// Should be valid JSON
|
|
39
|
-
const parsed = JSON.parse(result.formatted);
|
|
40
|
-
expect(parsed).toHaveProperty('version');
|
|
41
|
-
expect(parsed).toHaveProperty('summary');
|
|
42
|
-
expect(parsed).toHaveProperty('checks');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should produce text when json option is false', async () => {
|
|
46
|
-
const result = await runDoctorChecks({ json: false, projectRoot });
|
|
47
|
-
expect(result.formatted).toContain('SINAPSE Doctor');
|
|
48
|
-
expect(result.formatted).toContain('Summary:');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('15 checks (AC2 + INS-4.8)', () => {
|
|
53
|
-
it('should run exactly 15 checks', async () => {
|
|
54
|
-
const result = await runDoctorChecks({ projectRoot });
|
|
55
|
-
expect(result.data.checks).toHaveLength(16);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should return valid status for each check', async () => {
|
|
59
|
-
const result = await runDoctorChecks({ projectRoot });
|
|
60
|
-
const validStatuses = ['PASS', 'WARN', 'FAIL', 'INFO'];
|
|
61
|
-
|
|
62
|
-
for (const check of result.data.checks) {
|
|
63
|
-
expect(validStatuses).toContain(check.status);
|
|
64
|
-
expect(check).toHaveProperty('check');
|
|
65
|
-
expect(check).toHaveProperty('message');
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should include all expected check names', async () => {
|
|
70
|
-
const result = await runDoctorChecks({ projectRoot });
|
|
71
|
-
const checkNames = result.data.checks.map((c) => c.check);
|
|
72
|
-
|
|
73
|
-
expect(checkNames).toContain('settings-json');
|
|
74
|
-
expect(checkNames).toContain('rules-files');
|
|
75
|
-
expect(checkNames).toContain('agent-memory');
|
|
76
|
-
expect(checkNames).toContain('entity-registry');
|
|
77
|
-
expect(checkNames).toContain('git-hooks');
|
|
78
|
-
expect(checkNames).toContain('core-config');
|
|
79
|
-
expect(checkNames).toContain('claude-md');
|
|
80
|
-
expect(checkNames).toContain('ide-sync');
|
|
81
|
-
expect(checkNames).toContain('graph-dashboard');
|
|
82
|
-
expect(checkNames).toContain('code-intel');
|
|
83
|
-
expect(checkNames).toContain('node-version');
|
|
84
|
-
expect(checkNames).toContain('npm-packages');
|
|
85
|
-
expect(checkNames).toContain('skills-count');
|
|
86
|
-
expect(checkNames).toContain('commands-count');
|
|
87
|
-
expect(checkNames).toContain('hooks-claude-count');
|
|
88
|
-
expect(checkNames).toContain('manifest-version-parity');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('summary (AC3)', () => {
|
|
93
|
-
it('should have pass/warn/fail/info counts that sum to 15', async () => {
|
|
94
|
-
const result = await runDoctorChecks({ projectRoot });
|
|
95
|
-
const { pass, warn, fail, info } = result.data.summary;
|
|
96
|
-
expect(pass + warn + fail + info).toBe(16);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should include Summary line in text output', async () => {
|
|
100
|
-
const result = await runDoctorChecks({ projectRoot });
|
|
101
|
-
expect(result.formatted).toMatch(/Summary: \d+ PASS \| \d+ WARN \| \d+ FAIL \| \d+ INFO/);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('--dry-run (AC4)', () => {
|
|
106
|
-
it('should include fixResults when dryRun is true', async () => {
|
|
107
|
-
const result = await runDoctorChecks({ dryRun: true, projectRoot });
|
|
108
|
-
expect(result.data).toHaveProperty('fixResults');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should not produce file changes with dryRun', async () => {
|
|
112
|
-
const result = await runDoctorChecks({ dryRun: true, projectRoot });
|
|
113
|
-
if (result.data.fixResults) {
|
|
114
|
-
for (const fr of result.data.fixResults) {
|
|
115
|
-
expect(fr.applied).toBe(false);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('NOT_INSTALLED short-circuit (Story 10.42)', () => {
|
|
122
|
-
let tempDir;
|
|
123
|
-
let fakeHome;
|
|
124
|
-
|
|
125
|
-
beforeEach(() => {
|
|
126
|
-
// Isolated projectRoot + fake HOME so the doctor cannot see the
|
|
127
|
-
// developer's real ~/.sinapse or ~/.claude. Passing homeDir via
|
|
128
|
-
// options is the reliable way across POSIX and Windows (os.homedir
|
|
129
|
-
// sometimes ignores env changes mid-process).
|
|
130
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sinapse-doctor-fresh-'));
|
|
131
|
-
fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sinapse-doctor-home-'));
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
afterEach(() => {
|
|
135
|
-
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {}
|
|
136
|
-
try { fs.rmSync(fakeHome, { recursive: true, force: true }); } catch {}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should return notInstalled=true when no SINAPSE markers exist', async () => {
|
|
140
|
-
const result = await runDoctorChecks({ projectRoot: tempDir, homeDir: fakeHome });
|
|
141
|
-
expect(result.data.notInstalled).toBe(true);
|
|
142
|
-
expect(result.data.installCommand).toBe('npx sinapse-ai install');
|
|
143
|
-
expect(result.data.checks).toHaveLength(0);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should produce friendly NOT_INSTALLED text (no FAIL lines)', async () => {
|
|
147
|
-
const result = await runDoctorChecks({ projectRoot: tempDir, homeDir: fakeHome });
|
|
148
|
-
expect(result.formatted).toContain('SINAPSE is not installed');
|
|
149
|
-
expect(result.formatted).toContain('npx sinapse-ai install');
|
|
150
|
-
expect(result.formatted).not.toContain('[FAIL]');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should include notInstalled flag in JSON output', async () => {
|
|
154
|
-
const result = await runDoctorChecks({ json: true, projectRoot: tempDir, homeDir: fakeHome });
|
|
155
|
-
const parsed = JSON.parse(result.formatted);
|
|
156
|
-
expect(parsed.notInstalled).toBe(true);
|
|
157
|
-
expect(parsed.installCommand).toBe('npx sinapse-ai install');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should map to exit code 4', async () => {
|
|
161
|
-
const result = await runDoctorChecks({ projectRoot: tempDir, homeDir: fakeHome });
|
|
162
|
-
expect(resolveExitCode(result)).toBe(4);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should fall back to full check suite when a project marker exists', async () => {
|
|
166
|
-
// Drop a .sinapse-ai/ dir into the temp project. That alone should
|
|
167
|
-
// flip detection to installed=true and run the full check path.
|
|
168
|
-
fs.mkdirSync(path.join(tempDir, '.sinapse-ai'), { recursive: true });
|
|
169
|
-
const result = await runDoctorChecks({ projectRoot: tempDir, homeDir: fakeHome });
|
|
170
|
-
expect(result.data.notInstalled).toBeUndefined();
|
|
171
|
-
expect(result.data.checks.length).toBeGreaterThan(0);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe('JSON output schema (AC3)', () => {
|
|
176
|
-
it('should match expected JSON schema', async () => {
|
|
177
|
-
const result = await runDoctorChecks({ json: true, projectRoot });
|
|
178
|
-
const parsed = JSON.parse(result.formatted);
|
|
179
|
-
|
|
180
|
-
expect(parsed).toHaveProperty('version');
|
|
181
|
-
expect(parsed).toHaveProperty('timestamp');
|
|
182
|
-
expect(parsed).toHaveProperty('summary');
|
|
183
|
-
expect(parsed.summary).toHaveProperty('pass');
|
|
184
|
-
expect(parsed.summary).toHaveProperty('warn');
|
|
185
|
-
expect(parsed.summary).toHaveProperty('fail');
|
|
186
|
-
expect(parsed.summary).toHaveProperty('info');
|
|
187
|
-
expect(parsed).toHaveProperty('checks');
|
|
188
|
-
expect(Array.isArray(parsed.checks)).toBe(true);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Entity Registry Bootstrap Tests (Story INS-4.6)
|
|
5
|
-
*
|
|
6
|
-
* Validates that the installer calls populate-entity-registry.js during install,
|
|
7
|
-
* handles failures gracefully, and that sinapse doctor can verify the registry.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
const WIZARD_PATH = path.join(__dirname, '..', '..', 'src', 'wizard', 'index.js');
|
|
14
|
-
const POPULATE_SCRIPT = path.join(
|
|
15
|
-
__dirname, '..', '..', '..', '..', '.sinapse-ai', 'development', 'scripts', 'populate-entity-registry.js'
|
|
16
|
-
);
|
|
17
|
-
const DOCTOR_CHECK = path.join(
|
|
18
|
-
__dirname, '..', '..', '..', '..', '.sinapse-ai', 'core', 'doctor', 'checks', 'entity-registry.js'
|
|
19
|
-
);
|
|
20
|
-
const REGISTRY_PATH = path.join(
|
|
21
|
-
__dirname, '..', '..', '..', '..', '.sinapse-ai', 'data', 'entity-registry.yaml'
|
|
22
|
-
);
|
|
23
|
-
const PRE_PUSH_HOOK = path.join(__dirname, '..', '..', '..', '..', '.husky', 'pre-push');
|
|
24
|
-
const IDS_PRE_PUSH = path.join(
|
|
25
|
-
__dirname, '..', '..', '..', '..', '.sinapse-ai', 'hooks', 'ids-pre-push.js'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
describe('Entity Registry Bootstrap (Story INS-4.6)', () => {
|
|
29
|
-
let wizardSource;
|
|
30
|
-
|
|
31
|
-
beforeAll(() => {
|
|
32
|
-
wizardSource = fs.readFileSync(WIZARD_PATH, 'utf8');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('AC1: Bootstrap called during install', () => {
|
|
36
|
-
test('wizard calls populate-entity-registry.js', () => {
|
|
37
|
-
expect(wizardSource).toContain('populate-entity-registry.js');
|
|
38
|
-
expect(wizardSource).toContain('Bootstrapping entity registry');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('bootstrap runs after .sinapse-ai/ copy (injection order)', () => {
|
|
42
|
-
const sinapseCoreIdx = wizardSource.indexOf('SINAPSE core installed');
|
|
43
|
-
const bootstrapIdx = wizardSource.indexOf('Bootstrapping entity registry');
|
|
44
|
-
const envConfigIdx = wizardSource.indexOf('Configuring environment');
|
|
45
|
-
|
|
46
|
-
// Bootstrap must come after sinapse-ai install
|
|
47
|
-
expect(bootstrapIdx).toBeGreaterThan(sinapseCoreIdx);
|
|
48
|
-
// Bootstrap must come before environment configuration
|
|
49
|
-
expect(bootstrapIdx).toBeLessThan(envConfigIdx);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('bootstrap has try/catch — failure does not abort install', () => {
|
|
53
|
-
// Verify the bootstrap block is wrapped in try/catch
|
|
54
|
-
expect(wizardSource).toContain("answers.entityRegistryStatus = 'failed'");
|
|
55
|
-
expect(wizardSource).toContain('Entity registry bootstrap failed');
|
|
56
|
-
// Should warn, not throw
|
|
57
|
-
expect(wizardSource).toContain("run 'sinapse doctor' post-install");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('bootstrap uses 30s timeout guard', () => {
|
|
61
|
-
expect(wizardSource).toContain('timeout: 30000');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('bootstrap reports entity count on success', () => {
|
|
65
|
-
expect(wizardSource).toContain("answers.entityRegistryStatus = 'populated'");
|
|
66
|
-
expect(wizardSource).toContain('answers.entityRegistryCount');
|
|
67
|
-
expect(wizardSource).toContain('answers.entityRegistryMs');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('bootstrap handles missing script gracefully', () => {
|
|
71
|
-
expect(wizardSource).toContain("answers.entityRegistryStatus = 'skipped'");
|
|
72
|
-
expect(wizardSource).toContain('Entity registry script not found');
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('AC2: Sanity check (relative threshold)', () => {
|
|
77
|
-
test('sinapse doctor entity-registry check exists', () => {
|
|
78
|
-
expect(fs.existsSync(DOCTOR_CHECK)).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('doctor check uses relative validation (file exists + non-empty), not fixed threshold', () => {
|
|
82
|
-
const checkSource = fs.readFileSync(DOCTOR_CHECK, 'utf8');
|
|
83
|
-
|
|
84
|
-
// Should check file existence
|
|
85
|
-
expect(checkSource).toContain('existsSync');
|
|
86
|
-
// Should NOT have hardcoded threshold like >= 500
|
|
87
|
-
expect(checkSource).not.toMatch(/>= ?\d{3}/);
|
|
88
|
-
// Should report line count (relative measure)
|
|
89
|
-
expect(checkSource).toContain('lineCount');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('doctor check validates recency (mtime)', () => {
|
|
93
|
-
const checkSource = fs.readFileSync(DOCTOR_CHECK, 'utf8');
|
|
94
|
-
expect(checkSource).toContain('mtimeMs');
|
|
95
|
-
expect(checkSource).toContain('MAX_AGE_MS');
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('AC3: No duplication with pre-push hook', () => {
|
|
100
|
-
test('pre-push hook calls pre-push-safety.js, not populate script', () => {
|
|
101
|
-
const hookContent = fs.readFileSync(PRE_PUSH_HOOK, 'utf8');
|
|
102
|
-
expect(hookContent).toContain('pre-push-safety.js');
|
|
103
|
-
expect(hookContent).not.toContain('populate-entity-registry.js');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('ids-pre-push.js regenerates via the canonical populate() (not the incremental updater)', () => {
|
|
107
|
-
const idsSource = fs.readFileSync(IDS_PRE_PUSH, 'utf8');
|
|
108
|
-
// Root-cause fix (f003c4e): the incremental RegistryUpdater emitted a
|
|
109
|
-
// divergent registry schema (categories-first, dropping the
|
|
110
|
-
// metadata/resolutionRate block) that flipped the whole ~30k-line
|
|
111
|
-
// registry on every push. Both IDS hooks now regenerate with the
|
|
112
|
-
// canonical deterministic populate-entity-registry.js — a fixed point
|
|
113
|
-
// that produces a byte-identical file when content is unchanged.
|
|
114
|
-
expect(idsSource).toContain('populate-entity-registry');
|
|
115
|
-
expect(idsSource).not.toContain('processChanges');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('bootstrap uses populate-entity-registry.js (full scan) and does not invoke the IDS hook directly', () => {
|
|
119
|
-
// Wizard calls populate-entity-registry.js
|
|
120
|
-
expect(wizardSource).toContain('populate-entity-registry.js');
|
|
121
|
-
// Wizard does NOT call ids-pre-push.js
|
|
122
|
-
expect(wizardSource).not.toContain('ids-pre-push.js');
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('AC4: Performance guard', () => {
|
|
127
|
-
test('populate-entity-registry.js exists and is executable', () => {
|
|
128
|
-
expect(fs.existsSync(POPULATE_SCRIPT)).toBe(true);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('populate script has programmatic API (module.exports)', () => {
|
|
132
|
-
const scriptSource = fs.readFileSync(POPULATE_SCRIPT, 'utf8');
|
|
133
|
-
expect(scriptSource).toContain('module.exports');
|
|
134
|
-
expect(scriptSource).toContain('populate');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('measured runtime is well under 15s threshold', () => {
|
|
138
|
-
// Run the actual script and measure time
|
|
139
|
-
const { execSync } = require('child_process');
|
|
140
|
-
const projectRoot = path.join(__dirname, '..', '..', '..', '..');
|
|
141
|
-
const start = Date.now();
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
execSync(`node "${POPULATE_SCRIPT}"`, {
|
|
145
|
-
cwd: projectRoot,
|
|
146
|
-
encoding: 'utf8',
|
|
147
|
-
timeout: 30000,
|
|
148
|
-
stdio: 'pipe',
|
|
149
|
-
});
|
|
150
|
-
} catch {
|
|
151
|
-
// Script may fail in some environments — that's ok for timing test
|
|
152
|
-
console.log('SKIP: populate script execution failed — timing not measured');
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const elapsed = Date.now() - start;
|
|
157
|
-
// Must be under 15s (AC4 threshold)
|
|
158
|
-
expect(elapsed).toBeLessThan(15000);
|
|
159
|
-
console.log(`Measured runtime: ${(elapsed / 1000).toFixed(2)}s`);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe('AC5: Post-bootstrap verification', () => {
|
|
164
|
-
test('entity-registry.yaml exists after bootstrap', () => {
|
|
165
|
-
expect(fs.existsSync(REGISTRY_PATH)).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('entity-registry.yaml has at least 1 entity (non-empty)', () => {
|
|
169
|
-
const content = fs.readFileSync(REGISTRY_PATH, 'utf8');
|
|
170
|
-
const match = content.match(/entityCount:\s*(\d+)/);
|
|
171
|
-
expect(match).not.toBeNull();
|
|
172
|
-
const count = parseInt(match[1], 10);
|
|
173
|
-
expect(count).toBeGreaterThan(0);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// NOTE: This test depends on AC4 "measured runtime" test running first,
|
|
177
|
-
// which executes populate-entity-registry.js and refreshes the file mtime.
|
|
178
|
-
test('entity-registry.yaml updatedAt is recent (within 5 minutes)', () => {
|
|
179
|
-
const stat = fs.statSync(REGISTRY_PATH);
|
|
180
|
-
const ageMs = Date.now() - stat.mtimeMs;
|
|
181
|
-
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
182
|
-
expect(ageMs).toBeLessThan(FIVE_MINUTES);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('sinapse doctor entity-registry check passes on current registry', async () => {
|
|
186
|
-
const { run } = require(DOCTOR_CHECK);
|
|
187
|
-
const projectRoot = path.join(__dirname, '..', '..', '..', '..');
|
|
188
|
-
const result = await run({ projectRoot });
|
|
189
|
-
expect(result.status).toBe('PASS');
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
});
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests: .env Template Generator
|
|
3
|
-
* Story 1.6: Environment Configuration
|
|
4
|
-
*
|
|
5
|
-
* Tests for env-template.js
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { generateEnvContent, generateEnvExample } = require('../../src/config/templates/env-template');
|
|
9
|
-
|
|
10
|
-
describe('.env Template Generator', () => {
|
|
11
|
-
describe('generateEnvContent', () => {
|
|
12
|
-
it('should generate .env with empty API keys when none provided', () => {
|
|
13
|
-
const content = generateEnvContent();
|
|
14
|
-
|
|
15
|
-
expect(content).toContain('NODE_ENV=development');
|
|
16
|
-
// Version-agnostic: check SINAPSE_VERSION exists with valid semver format
|
|
17
|
-
expect(content).toMatch(/SINAPSE_VERSION=\d+\.\d+\.\d+/);
|
|
18
|
-
expect(content).toContain('OPENAI_API_KEY=');
|
|
19
|
-
expect(content).toContain('ANTHROPIC_API_KEY=');
|
|
20
|
-
expect(content).toContain('# SINAPSE Environment Configuration');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should generate .env with provided API keys', () => {
|
|
24
|
-
const apiKeys = {
|
|
25
|
-
openai: 'sk-test-openai-key',
|
|
26
|
-
anthropic: 'sk-ant-test-anthropic-key',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const content = generateEnvContent(apiKeys);
|
|
30
|
-
|
|
31
|
-
expect(content).toContain('OPENAI_API_KEY=sk-test-openai-key');
|
|
32
|
-
expect(content).toContain('ANTHROPIC_API_KEY=sk-ant-test-anthropic-key');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should generate .env with optional service API keys', () => {
|
|
36
|
-
const apiKeys = {
|
|
37
|
-
clickup: 'clickup-test-key',
|
|
38
|
-
github: 'ghp-test-token',
|
|
39
|
-
exa: 'exa-test-key',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const content = generateEnvContent(apiKeys);
|
|
43
|
-
|
|
44
|
-
expect(content).toContain('CLICKUP_API_KEY=clickup-test-key');
|
|
45
|
-
expect(content).toContain('GITHUB_TOKEN=ghp-test-token');
|
|
46
|
-
expect(content).toContain('EXA_API_KEY=exa-test-key');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should include helpful comments', () => {
|
|
50
|
-
const content = generateEnvContent();
|
|
51
|
-
|
|
52
|
-
expect(content).toContain('# LLM Providers');
|
|
53
|
-
expect(content).toContain('# Search & Research Tools');
|
|
54
|
-
expect(content).toContain('Get your key at:');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should not contain sensitive data in comments', () => {
|
|
58
|
-
const content = generateEnvContent();
|
|
59
|
-
|
|
60
|
-
// Should not have example API keys or credentials in comments
|
|
61
|
-
expect(content).not.toMatch(/sk-[a-zA-Z0-9]{20,}/);
|
|
62
|
-
expect(content).not.toMatch(/example.*key.*[a-zA-Z0-9]{20,}/i);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should use KEY=value format without spaces', () => {
|
|
66
|
-
const apiKeys = { openai: 'test-key' };
|
|
67
|
-
const content = generateEnvContent(apiKeys);
|
|
68
|
-
|
|
69
|
-
// Check no spaces around =
|
|
70
|
-
const lines = content.split('\n').filter(line => !line.startsWith('#') && line.trim());
|
|
71
|
-
lines.forEach(line => {
|
|
72
|
-
if (line.includes('=')) {
|
|
73
|
-
expect(line).toMatch(/^[A-Z0-9_]+=.*/);
|
|
74
|
-
// Check there are NO spaces before or after the = sign
|
|
75
|
-
expect(line).not.toMatch(/\s=/); // No space before =
|
|
76
|
-
expect(line).not.toMatch(/=\s[^=]*$/); // No space after = (except in comments)
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('generateEnvExample', () => {
|
|
83
|
-
it('should generate .env.example with empty values', () => {
|
|
84
|
-
const content = generateEnvExample();
|
|
85
|
-
|
|
86
|
-
expect(content).toContain('NODE_ENV=development');
|
|
87
|
-
// Version-agnostic: check SINAPSE_VERSION exists with valid semver format
|
|
88
|
-
expect(content).toMatch(/SINAPSE_VERSION=\d+\.\d+\.\d+/);
|
|
89
|
-
expect(content).toContain('OPENAI_API_KEY=');
|
|
90
|
-
expect(content).toContain('ANTHROPIC_API_KEY=');
|
|
91
|
-
expect(content).toContain('CLICKUP_API_KEY=');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should not contain any actual API keys', () => {
|
|
95
|
-
const content = generateEnvExample();
|
|
96
|
-
|
|
97
|
-
// All API key fields should be empty
|
|
98
|
-
const apiKeyLines = content.split('\n').filter(line =>
|
|
99
|
-
line.includes('_API_KEY=') || line.includes('_TOKEN='),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
apiKeyLines.forEach(line => {
|
|
103
|
-
const [, value] = line.split('=');
|
|
104
|
-
expect(value.trim()).toBe('');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should include helpful comments with links', () => {
|
|
109
|
-
const content = generateEnvExample();
|
|
110
|
-
|
|
111
|
-
expect(content).toContain('https://platform.openai.com');
|
|
112
|
-
expect(content).toContain('https://console.anthropic.com');
|
|
113
|
-
expect(content).toContain('ClickUp Settings');
|
|
114
|
-
expect(content).toContain('https://github.com/settings/tokens');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should be safe to commit', () => {
|
|
118
|
-
const content = generateEnvExample();
|
|
119
|
-
|
|
120
|
-
// Should not contain any of these sensitive patterns
|
|
121
|
-
const sensitivePatterns = [
|
|
122
|
-
/sk-[a-zA-Z0-9]{20,}/, // OpenAI keys
|
|
123
|
-
/sk-ant-[a-zA-Z0-9]{30,}/, // Anthropic keys
|
|
124
|
-
/ghp_[a-zA-Z0-9]+/, // GitHub tokens
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
sensitivePatterns.forEach(pattern => {
|
|
128
|
-
expect(content).not.toMatch(pattern);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Check that no API key fields have actual values (only empty or commented)
|
|
132
|
-
const lines = content.split('\n').filter(line => !line.trim().startsWith('#'));
|
|
133
|
-
lines.forEach(line => {
|
|
134
|
-
if (line.includes('PASSWORD') || line.includes('SECRET') || line.includes('KEY') || line.includes('TOKEN')) {
|
|
135
|
-
const [key, value] = line.split('=');
|
|
136
|
-
if (value !== undefined) {
|
|
137
|
-
expect(value.trim()).toBe('');
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should match .env structure but with empty values', () => {
|
|
144
|
-
const envContent = generateEnvContent({ openai: 'test', anthropic: 'test' });
|
|
145
|
-
const exampleContent = generateEnvExample();
|
|
146
|
-
|
|
147
|
-
// Extract non-comment, non-empty lines
|
|
148
|
-
const envKeys = envContent.split('\n')
|
|
149
|
-
.filter(line => line.trim() && !line.startsWith('#'))
|
|
150
|
-
.map(line => line.split('=')[0]);
|
|
151
|
-
|
|
152
|
-
const exampleKeys = exampleContent.split('\n')
|
|
153
|
-
.filter(line => line.trim() && !line.startsWith('#'))
|
|
154
|
-
.map(line => line.split('=')[0]);
|
|
155
|
-
|
|
156
|
-
// Both should have the same keys
|
|
157
|
-
expect(exampleKeys.sort()).toEqual(envKeys.sort());
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('Content Quality', () => {
|
|
162
|
-
it('should have proper line endings', () => {
|
|
163
|
-
const content = generateEnvContent();
|
|
164
|
-
|
|
165
|
-
// Should use \n for line endings
|
|
166
|
-
expect(content).not.toContain('\r\n');
|
|
167
|
-
expect(content.split('\n').length).toBeGreaterThan(10);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should organize content in sections', () => {
|
|
171
|
-
const content = generateEnvContent();
|
|
172
|
-
|
|
173
|
-
// Should have clear section headers
|
|
174
|
-
expect(content).toContain('# SINAPSE Core Configuration');
|
|
175
|
-
expect(content).toContain('# LLM Providers');
|
|
176
|
-
expect(content).toContain('# Search & Research Tools');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should include usage instructions', () => {
|
|
180
|
-
const content = generateEnvContent();
|
|
181
|
-
|
|
182
|
-
// Should help users understand what to do
|
|
183
|
-
expect(content).toContain('DO NOT commit');
|
|
184
|
-
expect(content).toContain('Get yours at:');
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
});
|