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,231 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * IDE Sync Integration Tests (Story INS-4.5)
5
- *
6
- * Verifies that the installer calls commandSync and commandValidate
7
- * via the adapter pattern (save cwd, chdir, finally restore).
8
- */
9
-
10
- const path = require('path');
11
-
12
- // Mock the ide-sync module before requiring wizard
13
- const mockCommandSync = jest.fn();
14
- const mockCommandValidate = jest.fn();
15
-
16
- jest.mock('../../../../../.sinapse-ai/infrastructure/scripts/ide-sync/index', () => ({
17
- commandSync: mockCommandSync,
18
- commandValidate: mockCommandValidate,
19
- }));
20
-
21
- // We need to verify that the wizard source code has the correct integration
22
- const fs = require('fs');
23
- const WIZARD_PATH = path.join(
24
- __dirname, '..', '..', '..', 'src', 'wizard', 'index.js'
25
- );
26
-
27
- describe('IDE Sync Integration (Story INS-4.5)', () => {
28
- let wizardSource;
29
-
30
- beforeAll(() => {
31
- wizardSource = fs.readFileSync(WIZARD_PATH, 'utf8');
32
- });
33
-
34
- describe('AC1: IDE sync called via adapter pattern', () => {
35
- test('wizard imports commandSync and commandValidate from ide-sync', () => {
36
- expect(wizardSource).toContain(
37
- "const { commandSync, commandValidate } = require('../../../../.sinapse-ai/infrastructure/scripts/ide-sync/index')"
38
- );
39
- });
40
-
41
- test('wizard uses programmatic API (not child_process.exec)', () => {
42
- // Should NOT shell out to ide-sync
43
- expect(wizardSource).not.toMatch(/child_process.*ide-sync/);
44
- expect(wizardSource).not.toMatch(/exec\(.*ide-sync/);
45
- expect(wizardSource).not.toMatch(/spawn\(.*ide-sync/);
46
- });
47
-
48
- test('adapter pattern: saves cwd before calling commandSync', () => {
49
- expect(wizardSource).toContain('const savedCwd = process.cwd()');
50
- });
51
-
52
- test('adapter pattern: uses explicit targetProjectRoot variable (not bare process.cwd())', () => {
53
- expect(wizardSource).toContain('const targetProjectRoot = process.cwd()');
54
- expect(wizardSource).toContain('process.chdir(targetProjectRoot)');
55
- });
56
-
57
- test('adapter pattern: restores cwd in finally block', () => {
58
- // The finally block should restore savedCwd
59
- expect(wizardSource).toContain('process.chdir(savedCwd)');
60
- expect(wizardSource).toMatch(/finally\s*\{[^}]*process\.chdir\(savedCwd\)/s);
61
- });
62
-
63
- test('commandSync called with { quiet: true }', () => {
64
- expect(wizardSource).toContain("await commandSync({ quiet: true })");
65
- });
66
-
67
- test('does NOT pass projectRoot or ides as parameters to commandSync', () => {
68
- // commandSync uses process.cwd() internally — no projectRoot param
69
- expect(wizardSource).not.toMatch(/commandSync\(\{[^}]*projectRoot/);
70
- expect(wizardSource).not.toMatch(/commandSync\(\{[^}]*ides/);
71
- });
72
- });
73
-
74
- describe('AC3: Graceful failure', () => {
75
- test('sync failure is caught and does not propagate', () => {
76
- // The try/catch should set ideSyncStatus to failed, not throw
77
- expect(wizardSource).toContain("answers.ideSyncStatus = 'failed'");
78
- });
79
-
80
- test('failure message suggests sinapse doctor --fix', () => {
81
- expect(wizardSource).toContain("sinapse doctor --fix");
82
- });
83
-
84
- test('install summary includes sync status on success', () => {
85
- expect(wizardSource).toContain("answers.ideSyncStatus = 'synced'");
86
- });
87
-
88
- test('install summary includes sync status on failure', () => {
89
- expect(wizardSource).toContain("answers.ideSyncStatus = 'failed'");
90
- });
91
- });
92
-
93
- describe('AC4: Validate sync output', () => {
94
- test('commandValidate called after commandSync', () => {
95
- // commandValidate should appear after commandSync in the source
96
- const syncIndex = wizardSource.indexOf('await commandSync({ quiet: true })');
97
- const validateIndex = wizardSource.indexOf('await commandValidate(');
98
- expect(syncIndex).toBeGreaterThan(-1);
99
- expect(validateIndex).toBeGreaterThan(-1);
100
- expect(validateIndex).toBeGreaterThan(syncIndex);
101
- });
102
-
103
- test('commandValidate uses same adapter pattern (within same finally block)', () => {
104
- // Both commandSync and commandValidate should be within the same
105
- // saved cwd / finally block
106
- const savedCwdIndex = wizardSource.indexOf('const savedCwd = process.cwd()');
107
- const syncIndex = wizardSource.indexOf('await commandSync({ quiet: true })');
108
- const validateIndex = wizardSource.indexOf('await commandValidate(');
109
- const finallyIndex = wizardSource.indexOf('process.chdir(savedCwd)');
110
-
111
- // All should be in order: savedCwd < sync < validate < finally restore
112
- expect(savedCwdIndex).toBeLessThan(syncIndex);
113
- expect(syncIndex).toBeLessThan(validateIndex);
114
- expect(validateIndex).toBeLessThan(finallyIndex);
115
- });
116
-
117
- test('validation drift logged as WARN not ERROR', () => {
118
- expect(wizardSource).toContain("answers.ideSyncValidation = 'drift'");
119
- // Should use console.warn, not console.error for drift
120
- expect(wizardSource).toMatch(/console\.warn\(.*drift/i);
121
- });
122
-
123
- test('commandValidate console output suppressed (quiet workaround)', () => {
124
- // commandValidate does not support quiet — wizard suppresses console.log
125
- expect(wizardSource).toContain('const _origLog = console.log');
126
- expect(wizardSource).toContain('console.log = () => {}');
127
- // console.log must be restored in a finally block
128
- expect(wizardSource).toContain('console.log = _origLog');
129
- });
130
- });
131
-
132
- describe('AC5: Behavioral tests with mocks', () => {
133
- beforeEach(() => {
134
- mockCommandSync.mockReset();
135
- mockCommandValidate.mockReset();
136
- });
137
-
138
- test('commandSync is called with { quiet: true } when invoked', async () => {
139
- mockCommandSync.mockResolvedValue(undefined);
140
- mockCommandValidate.mockResolvedValue(undefined);
141
-
142
- // Simulate the adapter pattern call sequence
143
- const savedCwd = process.cwd();
144
- try {
145
- await mockCommandSync({ quiet: true });
146
- await mockCommandValidate({ quiet: true });
147
- } finally {
148
- process.chdir(savedCwd);
149
- }
150
-
151
- expect(mockCommandSync).toHaveBeenCalledWith({ quiet: true });
152
- expect(mockCommandValidate).toHaveBeenCalledWith({ quiet: true });
153
- });
154
-
155
- test('commandSync failure does not throw — install continues', async () => {
156
- mockCommandSync.mockRejectedValue(new Error('sync failed'));
157
-
158
- let ideSyncStatus = 'unknown';
159
- const savedCwd = process.cwd();
160
- try {
161
- process.chdir(process.cwd());
162
- await mockCommandSync({ quiet: true });
163
- ideSyncStatus = 'synced';
164
- } catch (syncError) {
165
- ideSyncStatus = 'failed';
166
- } finally {
167
- process.chdir(savedCwd);
168
- }
169
-
170
- // Install should continue — status is failed, no uncaught throw
171
- expect(ideSyncStatus).toBe('failed');
172
- expect(process.cwd()).toBe(savedCwd);
173
- });
174
-
175
- test('cwd is restored even when commandSync throws', async () => {
176
- mockCommandSync.mockRejectedValue(new Error('sync failed'));
177
- const originalCwd = process.cwd();
178
-
179
- const savedCwd = process.cwd();
180
- try {
181
- process.chdir(process.cwd());
182
- await mockCommandSync({ quiet: true });
183
- } catch {
184
- // Expected — sync failed
185
- } finally {
186
- process.chdir(savedCwd);
187
- }
188
-
189
- expect(process.cwd()).toBe(originalCwd);
190
- });
191
-
192
- test('commandValidate called after successful sync', async () => {
193
- mockCommandSync.mockResolvedValue(undefined);
194
- mockCommandValidate.mockResolvedValue(undefined);
195
-
196
- const savedCwd = process.cwd();
197
- let validateCalled = false;
198
- try {
199
- process.chdir(process.cwd());
200
- await mockCommandSync({ quiet: true });
201
- await mockCommandValidate({ quiet: true });
202
- validateCalled = true;
203
- } finally {
204
- process.chdir(savedCwd);
205
- }
206
-
207
- expect(validateCalled).toBe(true);
208
- expect(mockCommandValidate).toHaveBeenCalled();
209
- });
210
-
211
- test('commandValidate NOT called when commandSync fails', async () => {
212
- mockCommandSync.mockRejectedValue(new Error('sync failed'));
213
-
214
- const savedCwd = process.cwd();
215
- try {
216
- process.chdir(process.cwd());
217
- await mockCommandSync({ quiet: true });
218
- // This line should NOT be reached
219
- await mockCommandValidate({ quiet: true });
220
- } catch {
221
- // Expected — sync failed, validate not called
222
- } finally {
223
- process.chdir(savedCwd);
224
- }
225
-
226
- expect(mockCommandSync).toHaveBeenCalled();
227
- expect(mockCommandValidate).not.toHaveBeenCalled();
228
- });
229
- });
230
- });
231
-
@@ -1,191 +0,0 @@
1
- /**
2
- * @fileoverview Tests for EnvMerger
3
- * Story 9.2: .env File Merge Implementation
4
- */
5
-
6
- const { EnvMerger } = require('../../../src/merger/strategies/env-merger.js');
7
- const { parseEnvFile } = require('../../../src/merger/parsers/env-parser.js');
8
-
9
- describe('EnvMerger', () => {
10
- let merger;
11
-
12
- beforeEach(() => {
13
- merger = new EnvMerger();
14
- });
15
-
16
- describe('canMerge', () => {
17
- it('should always return true for .env files', () => {
18
- expect(merger.canMerge('', '')).toBe(true);
19
- expect(merger.canMerge('KEY=value', 'OTHER=value')).toBe(true);
20
- });
21
- });
22
-
23
- describe('merge', () => {
24
- it('should add new variables from new content', async () => {
25
- const existing = 'EXISTING_VAR=existing_value';
26
- const newContent = 'NEW_VAR=new_value';
27
-
28
- const result = await merger.merge(existing, newContent);
29
-
30
- expect(result.content).toContain('EXISTING_VAR=existing_value');
31
- expect(result.content).toContain('NEW_VAR=new_value');
32
- expect(result.stats.preserved).toBe(1);
33
- expect(result.stats.added).toBe(1);
34
- });
35
-
36
- it('should preserve existing values over new values', async () => {
37
- const existing = 'API_KEY=my_secret_key';
38
- const newContent = 'API_KEY=placeholder_key';
39
-
40
- const result = await merger.merge(existing, newContent);
41
-
42
- // The original value is preserved at the start
43
- expect(result.content.startsWith('API_KEY=my_secret_key')).toBe(true);
44
- // The suggested value appears in SINAPSE_SUGGESTED comment
45
- expect(result.content).toContain('SINAPSE_SUGGESTED: API_KEY=placeholder_key');
46
- expect(result.stats.preserved).toBe(1);
47
- });
48
-
49
- it('should add SINAPSE_SUGGESTED comment for differing values', async () => {
50
- const existing = 'PORT=3000';
51
- const newContent = 'PORT=8080';
52
-
53
- const result = await merger.merge(existing, newContent);
54
-
55
- expect(result.content).toContain('PORT=3000');
56
- expect(result.content).toContain('# SINAPSE_SUGGESTED: PORT=8080');
57
- expect(result.stats.conflicts).toBe(1);
58
- });
59
-
60
- it('should preserve comments from existing file', async () => {
61
- const existing = `# Database configuration
62
- DB_HOST=localhost
63
- DB_PORT=5432`;
64
- const newContent = 'NEW_VAR=value';
65
-
66
- const result = await merger.merge(existing, newContent);
67
-
68
- expect(result.content).toContain('# Database configuration');
69
- expect(result.content).toContain('DB_HOST=localhost');
70
- });
71
-
72
- it('should add new variables in SINAPSE Variables section', async () => {
73
- const existing = 'EXISTING=value';
74
- const newContent = `SINAPSE_VAR1=value1
75
- SINAPSE_VAR2=value2`;
76
-
77
- const result = await merger.merge(existing, newContent);
78
-
79
- // Header includes date: "# === SINAPSE Variables (added YYYY-MM-DD) ==="
80
- expect(result.content).toMatch(/# === SINAPSE Variables \(added \d{4}-\d{2}-\d{2}\) ===/);
81
- expect(result.content).toContain('SINAPSE_VAR1=value1');
82
- expect(result.content).toContain('SINAPSE_VAR2=value2');
83
- });
84
-
85
- it('should handle empty existing content', async () => {
86
- const existing = '';
87
- const newContent = 'NEW_VAR=value';
88
-
89
- const result = await merger.merge(existing, newContent);
90
-
91
- expect(result.content).toContain('NEW_VAR=value');
92
- expect(result.stats.added).toBe(1);
93
- });
94
-
95
- it('should handle empty new content', async () => {
96
- const existing = 'EXISTING=value';
97
- const newContent = '';
98
-
99
- const result = await merger.merge(existing, newContent);
100
-
101
- expect(result.content).toContain('EXISTING=value');
102
- expect(result.stats.preserved).toBe(1);
103
- });
104
-
105
- it('should handle multiline values', async () => {
106
- const existing = `PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
107
- MIIEpQIBAAKCAQEA
108
- -----END RSA PRIVATE KEY-----"`;
109
- const newContent = 'OTHER=value';
110
-
111
- const result = await merger.merge(existing, newContent);
112
-
113
- expect(result.content).toContain('PRIVATE_KEY=');
114
- expect(result.content).toContain('OTHER=value');
115
- });
116
- });
117
-
118
- describe('preview', () => {
119
- it('should return same result as merge with preview option', async () => {
120
- const existing = 'A=1';
121
- const newContent = 'B=2';
122
-
123
- const previewResult = await merger.preview(existing, newContent);
124
- const mergeResult = await merger.merge(existing, newContent, { preview: true });
125
-
126
- expect(previewResult.content).toBe(mergeResult.content);
127
- expect(previewResult.stats).toEqual(mergeResult.stats);
128
- });
129
- });
130
- });
131
-
132
- describe('parseEnvFile', () => {
133
- it('should parse simple key-value pairs', () => {
134
- const content = `KEY1=value1
135
- KEY2=value2`;
136
-
137
- const result = parseEnvFile(content);
138
-
139
- expect(result.variables.get('KEY1').value).toBe('value1');
140
- expect(result.variables.get('KEY2').value).toBe('value2');
141
- });
142
-
143
- it('should handle quoted values', () => {
144
- const content = `QUOTED="value with spaces"
145
- SINGLE='single quoted'`;
146
-
147
- const result = parseEnvFile(content);
148
-
149
- expect(result.variables.get('QUOTED').value).toBe('"value with spaces"');
150
- expect(result.variables.get('SINGLE').value).toBe("'single quoted'");
151
- });
152
-
153
- it('should preserve comments', () => {
154
- const content = `# This is a comment
155
- KEY=value
156
- # Another comment`;
157
-
158
- const result = parseEnvFile(content);
159
-
160
- expect(result.comments.length).toBeGreaterThan(0);
161
- expect(result.variables.get('KEY').value).toBe('value');
162
- });
163
-
164
- it('should handle empty lines', () => {
165
- const content = `KEY1=value1
166
-
167
- KEY2=value2`;
168
-
169
- const result = parseEnvFile(content);
170
-
171
- expect(result.variables.get('KEY1').value).toBe('value1');
172
- expect(result.variables.get('KEY2').value).toBe('value2');
173
- });
174
-
175
- it('should handle values with equals signs', () => {
176
- const content = 'URL=https://example.com?param=value';
177
-
178
- const result = parseEnvFile(content);
179
-
180
- expect(result.variables.get('URL').value).toBe('https://example.com?param=value');
181
- });
182
-
183
- it('should handle empty values', () => {
184
- const content = 'EMPTY_VAR=';
185
-
186
- const result = parseEnvFile(content);
187
-
188
- expect(result.variables.has('EMPTY_VAR')).toBe(true);
189
- expect(result.variables.get('EMPTY_VAR').value).toBe('');
190
- });
191
- });
@@ -1,262 +0,0 @@
1
- /**
2
- * @fileoverview Tests for MarkdownMerger
3
- * Story 9.3: Markdown Merge Implementation
4
- */
5
-
6
- const { MarkdownMerger } = require('../../../src/merger/strategies/markdown-merger.js');
7
- const {
8
- parseMarkdownSections,
9
- slugify,
10
- hasSinapseMarkers,
11
- } = require('../../../src/merger/parsers/markdown-section-parser.js');
12
-
13
- describe('MarkdownMerger', () => {
14
- let merger;
15
-
16
- beforeEach(() => {
17
- merger = new MarkdownMerger();
18
- });
19
-
20
- describe('canMerge', () => {
21
- it('should return true for content with SINAPSE markers', () => {
22
- const content = `# Title
23
- <!-- SINAPSE-MANAGED-START: section1 -->
24
- Content
25
- <!-- SINAPSE-MANAGED-END: section1 -->`;
26
-
27
- expect(merger.canMerge(content, '')).toBe(true);
28
- });
29
-
30
- it('should return false for content without SINAPSE markers', () => {
31
- const content = `# Title
32
- Some content without markers`;
33
-
34
- expect(merger.canMerge(content, '')).toBe(false);
35
- });
36
- });
37
-
38
- describe('merge', () => {
39
- it('should update SINAPSE-managed sections', async () => {
40
- const existing = `# My Rules
41
-
42
- <!-- SINAPSE-MANAGED-START: agent-system -->
43
- ## Old Agent System
44
- Old content
45
- <!-- SINAPSE-MANAGED-END: agent-system -->
46
-
47
- ## My Custom Section
48
- Custom content`;
49
-
50
- const newContent = `# Template
51
-
52
- <!-- SINAPSE-MANAGED-START: agent-system -->
53
- ## Agent System
54
- New updated content
55
- <!-- SINAPSE-MANAGED-END: agent-system -->`;
56
-
57
- const result = await merger.merge(existing, newContent);
58
-
59
- expect(result.content).toContain('## Agent System');
60
- expect(result.content).toContain('New updated content');
61
- expect(result.content).toContain('## My Custom Section');
62
- expect(result.content).toContain('Custom content');
63
- expect(result.stats.updated).toBe(1);
64
- });
65
-
66
- it('should preserve user sections', async () => {
67
- const existing = `# Rules
68
-
69
- <!-- SINAPSE-MANAGED-START: core -->
70
- Core content
71
- <!-- SINAPSE-MANAGED-END: core -->
72
-
73
- ## My Custom Rules
74
- 1. Rule one
75
- 2. Rule two`;
76
-
77
- const newContent = `# Template
78
-
79
- <!-- SINAPSE-MANAGED-START: core -->
80
- Updated core
81
- <!-- SINAPSE-MANAGED-END: core -->`;
82
-
83
- const result = await merger.merge(existing, newContent);
84
-
85
- expect(result.content).toContain('## My Custom Rules');
86
- expect(result.content).toContain('1. Rule one');
87
- expect(result.content).toContain('2. Rule two');
88
- expect(result.stats.preserved).toBeGreaterThan(0);
89
- });
90
-
91
- it('should add new SINAPSE sections that do not exist', async () => {
92
- const existing = `# Rules
93
-
94
- <!-- SINAPSE-MANAGED-START: core -->
95
- Core content
96
- <!-- SINAPSE-MANAGED-END: core -->`;
97
-
98
- const newContent = `# Template
99
-
100
- <!-- SINAPSE-MANAGED-START: core -->
101
- Updated core
102
- <!-- SINAPSE-MANAGED-END: core -->
103
-
104
- <!-- SINAPSE-MANAGED-START: new-section -->
105
- New section content
106
- <!-- SINAPSE-MANAGED-END: new-section -->`;
107
-
108
- const result = await merger.merge(existing, newContent);
109
-
110
- expect(result.content).toContain('<!-- SINAPSE-MANAGED-START: new-section -->');
111
- expect(result.content).toContain('New section content');
112
- expect(result.stats.added).toBe(1);
113
- });
114
-
115
- it('should handle files with no SINAPSE sections', async () => {
116
- const existing = `# My Custom Rules
117
-
118
- ## Section 1
119
- Content 1
120
-
121
- ## Section 2
122
- Content 2`;
123
-
124
- const newContent = `# Template
125
-
126
- <!-- SINAPSE-MANAGED-START: core -->
127
- Core content
128
- <!-- SINAPSE-MANAGED-END: core -->`;
129
-
130
- const result = await merger.merge(existing, newContent);
131
-
132
- // Should preserve existing content and add SINAPSE section
133
- expect(result.content).toContain('## Section 1');
134
- expect(result.content).toContain('Content 1');
135
- });
136
- });
137
-
138
- describe('migrateLegacy', () => {
139
- it('should append SINAPSE sections to legacy file', async () => {
140
- const existing = `# My Old Rules
141
- Custom content here`;
142
-
143
- const template = `# Template
144
-
145
- <!-- SINAPSE-MANAGED-START: core -->
146
- Core content
147
- <!-- SINAPSE-MANAGED-END: core -->`;
148
-
149
- // migrateLegacy expects a parsed template object, so use merge which handles that
150
- const result = await merger.merge(existing, template);
151
-
152
- expect(result.content).toContain('# My Old Rules');
153
- expect(result.content).toContain('Custom content here');
154
- expect(result.content).toContain('<!-- SINAPSE-MANAGED-START: core -->');
155
- expect(result.isLegacyMigration).toBe(true);
156
- });
157
- });
158
- });
159
-
160
- describe('parseMarkdownSections', () => {
161
- it('should identify SINAPSE-managed sections', () => {
162
- const content = `# Title
163
-
164
- <!-- SINAPSE-MANAGED-START: section1 -->
165
- Managed content
166
- <!-- SINAPSE-MANAGED-END: section1 -->
167
-
168
- ## User Section
169
- User content`;
170
-
171
- const result = parseMarkdownSections(content);
172
-
173
- const managedSection = result.sections.find((s) => s.id === 'section1');
174
- expect(managedSection).toBeDefined();
175
- expect(managedSection.managed).toBe(true);
176
- expect(managedSection.lines.join('\n')).toContain('Managed content');
177
- });
178
-
179
- it('should identify user sections', () => {
180
- const content = `# Title
181
-
182
- <!-- SINAPSE-MANAGED-START: managed -->
183
- Managed
184
- <!-- SINAPSE-MANAGED-END: managed -->
185
-
186
- ## User Section
187
- User content`;
188
-
189
- const result = parseMarkdownSections(content);
190
-
191
- const userSections = result.sections.filter((s) => !s.managed);
192
- expect(userSections.length).toBeGreaterThan(0);
193
- });
194
-
195
- it('should handle nested content in managed sections', () => {
196
- const content = `<!-- SINAPSE-MANAGED-START: test -->
197
- ## Heading
198
- - List item 1
199
- - List item 2
200
-
201
- \`\`\`javascript
202
- const code = true;
203
- \`\`\`
204
- <!-- SINAPSE-MANAGED-END: test -->`;
205
-
206
- const result = parseMarkdownSections(content);
207
-
208
- const section = result.sections.find((s) => s.id === 'test');
209
- const sectionContent = section.lines.join('\n');
210
- expect(sectionContent).toContain('## Heading');
211
- expect(sectionContent).toContain('- List item 1');
212
- expect(sectionContent).toContain('const code = true');
213
- });
214
- });
215
-
216
- describe('slugify', () => {
217
- it('should convert text to lowercase slug', () => {
218
- expect(slugify('Hello World')).toBe('hello-world');
219
- });
220
-
221
- it('should handle special characters', () => {
222
- expect(slugify('Test & Example!')).toBe('test-example');
223
- });
224
-
225
- it('should collapse multiple dashes', () => {
226
- expect(slugify('Multiple Spaces')).toBe('multiple-spaces');
227
- });
228
-
229
- it('should trim leading and trailing dashes', () => {
230
- expect(slugify(' Trimmed ')).toBe('trimmed');
231
- });
232
-
233
- it('should handle numbers', () => {
234
- // Dots are removed, so numbers become consecutive
235
- expect(slugify('Section 1.2.3')).toBe('section-123');
236
- // With spaces between numbers
237
- expect(slugify('Section 1 2 3')).toBe('section-1-2-3');
238
- });
239
- });
240
-
241
- describe('hasSinapseMarkers', () => {
242
- it('should return true for content with SINAPSE markers', () => {
243
- const content = `<!-- SINAPSE-MANAGED-START: test -->
244
- Content
245
- <!-- SINAPSE-MANAGED-END: test -->`;
246
-
247
- expect(hasSinapseMarkers(content)).toBe(true);
248
- });
249
-
250
- it('should return false for content without markers', () => {
251
- const content = '# Just a heading\nSome content';
252
-
253
- expect(hasSinapseMarkers(content)).toBe(false);
254
- });
255
-
256
- it('should return false for incomplete markers', () => {
257
- const content = '<!-- SINAPSE-MANAGED-START: test -->';
258
-
259
- expect(hasSinapseMarkers(content)).toBe(false);
260
- });
261
- });
262
-