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.
Files changed (88) hide show
  1. package/.claude/rules/mandatory-delegation.md +1 -1
  2. package/.codex/delegation-matrix.json +4 -3
  3. package/.codex/delegation-parity.json +4 -3
  4. package/.codex/instructions.md +2 -2
  5. package/.sinapse-ai/constitution.md +2 -2
  6. package/.sinapse-ai/core/doctor/checks/git-hooks.js +76 -10
  7. package/.sinapse-ai/core/execution/subagent-dispatcher.js +1 -1
  8. package/.sinapse-ai/core/synapse/engine.js +15 -0
  9. package/.sinapse-ai/data/entity-registry.yaml +13 -13
  10. package/.sinapse-ai/development/agents/snps-orqx.md +4 -4
  11. package/.sinapse-ai/git-hooks/lib/secret-scanner-core.js +76 -4
  12. package/.sinapse-ai/git-hooks/pre-push +7 -1
  13. package/.sinapse-ai/install-manifest.yaml +9 -9
  14. package/AGENTS.md +2 -2
  15. package/CHANGELOG.md +1247 -0
  16. package/bin/commands/uninstall.js +2 -2
  17. package/bin/utils/secret-scanner-core.js +76 -4
  18. package/docs/agent-reference-guide.md +1 -1
  19. package/docs/framework/architecture-overview.md +4 -4
  20. package/docs/framework/guiding-principles.md +9 -9
  21. package/docs/getting-started.md +1 -1
  22. package/docs/guides/agent-reference.md +1 -1
  23. package/docs/guides/codex-config.md +4 -5
  24. package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
  25. package/package.json +8 -2
  26. package/packages/installer/src/installer/git-hooks-installer.js +3 -1
  27. package/packages/installer/src/wizard/ide-config-generator.js +9 -1
  28. package/packages/installer/src/wizard/index.js +3 -4
  29. package/scripts/regenerate-orqx-stubs.ps1 +0 -1
  30. package/scripts/sync-counts.js +10 -2
  31. package/scripts/sync-squad-yaml-components.js +108 -6
  32. package/scripts/validate-squad-orqx.js +19 -9
  33. package/sinapse/agents/sinapse-orqx.md +4 -4
  34. package/sinapse/agents/snps-orqx.md +4 -4
  35. package/sinapse/knowledge-base/routing-catalog.md +1 -1
  36. package/sinapse/tasks/diagnose-and-route.md +1 -1
  37. package/sinapse/tasks/squad-status-report.md +1 -1
  38. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
  39. package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
  40. package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
  41. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  42. package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
  43. package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
  44. package/squads/squad-animations/README.md +1 -1
  45. package/squads/squad-cloning/README.md +1 -1
  46. package/squads/squad-commercial/README.md +1 -1
  47. package/squads/squad-content/README.md +1 -1
  48. package/squads/squad-copy/README.md +1 -1
  49. package/squads/squad-council/README.md +1 -1
  50. package/squads/squad-courses/README.md +1 -1
  51. package/squads/squad-cybersecurity/README.md +1 -1
  52. package/squads/squad-design/README.md +1 -1
  53. package/squads/squad-finance/README.md +1 -1
  54. package/squads/squad-growth/README.md +1 -1
  55. package/squads/squad-paidmedia/README.md +1 -1
  56. package/squads/squad-product/README.md +1 -1
  57. package/squads/squad-research/README.md +1 -1
  58. package/squads/squad-storytelling/README.md +1 -1
  59. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
  60. package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
  61. package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
  62. package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
  63. package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
  64. package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
  65. package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
  66. package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
  67. package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
  68. package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
  69. package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
  70. package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
  71. package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
  72. package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
  73. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
  74. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
  75. package/packages/installer/tests/unit/config-validator.test.js +0 -315
  76. package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
  77. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
  78. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
  79. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
  80. package/packages/installer/tests/unit/env-template.test.js +0 -187
  81. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
  82. package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
  83. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
  84. package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
  85. package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
  86. package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
  87. package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
  88. 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
- });