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,675 +0,0 @@
1
- /**
2
- * Unit Tests: Doctor Check Modules
3
- * Story INS-4.1: sinapse doctor rewrite
4
- * Story INS-4.8: 3 new checks (skills-count, commands-count, hooks-claude-count)
5
- *
6
- * Tests all 15 check modules individually with mocked filesystem.
7
- */
8
-
9
- const path = require('path');
10
- const fs = require('fs');
11
-
12
- // Mock fs for controlled test scenarios
13
- jest.mock('fs');
14
-
15
- // Mock child_process — the git-hooks check reads core.hooksPath via execFileSync('git', ...)
16
- jest.mock('child_process');
17
- const { execFileSync } = require('child_process');
18
-
19
- const nodeVersionCheck = require('../../../../../.sinapse-ai/core/doctor/checks/node-version');
20
- const npmPackagesCheck = require('../../../../../.sinapse-ai/core/doctor/checks/npm-packages');
21
- const settingsJsonCheck = require('../../../../../.sinapse-ai/core/doctor/checks/settings-json');
22
- const rulesFilesCheck = require('../../../../../.sinapse-ai/core/doctor/checks/rules-files');
23
- const agentMemoryCheck = require('../../../../../.sinapse-ai/core/doctor/checks/agent-memory');
24
- const entityRegistryCheck = require('../../../../../.sinapse-ai/core/doctor/checks/entity-registry');
25
- const gitHooksCheck = require('../../../../../.sinapse-ai/core/doctor/checks/git-hooks');
26
- const coreConfigCheck = require('../../../../../.sinapse-ai/core/doctor/checks/core-config');
27
- const claudeMdCheck = require('../../../../../.sinapse-ai/core/doctor/checks/claude-md');
28
- const graphDashboardCheck = require('../../../../../.sinapse-ai/core/doctor/checks/graph-dashboard');
29
- const codeIntelCheck = require('../../../../../.sinapse-ai/core/doctor/checks/code-intel');
30
- const ideSyncCheck = require('../../../../../.sinapse-ai/core/doctor/checks/ide-sync');
31
- const skillsCountCheck = require('../../../../../.sinapse-ai/core/doctor/checks/skills-count');
32
- const commandsCountCheck = require('../../../../../.sinapse-ai/core/doctor/checks/commands-count');
33
- const hooksClaudeCountCheck = require('../../../../../.sinapse-ai/core/doctor/checks/hooks-claude-count');
34
- const { loadChecks } = require('../../../../../.sinapse-ai/core/doctor/checks');
35
-
36
- const mockContext = {
37
- projectRoot: '/mock/project',
38
- frameworkRoot: '/mock/framework',
39
- options: { fix: false, json: false, dryRun: false, quiet: false },
40
- };
41
-
42
- beforeEach(() => {
43
- jest.clearAllMocks();
44
- });
45
-
46
- describe('node-version check', () => {
47
- it('should PASS for current Node.js version (>=18)', async () => {
48
- const result = await nodeVersionCheck.run(mockContext);
49
- expect(result.check).toBe('node-version');
50
- expect(result.status).toBe('PASS');
51
- expect(result.message).toContain('Node.js');
52
- });
53
- });
54
-
55
- describe('npm-packages check', () => {
56
- // Story 10.48 + v1.4.2: this check no longer treats a missing project-level
57
- // node_modules/ as a blocker. It validates that the deps DECLARED in
58
- // .sinapse-ai/package.json are *resolvable* via Node's resolver (which walks
59
- // parent + global node_modules). PASS unless a declared dep is unresolvable.
60
-
61
- it('should PASS when .sinapse-ai package has zero declared deps', async () => {
62
- // existsSync true everywhere → .sinapse-ai/package.json + node_modules present.
63
- // readFileSync auto-mock returns undefined → JSON.parse throws → caught →
64
- // falls through to PASS with totalDeps=0 and node_modules present.
65
- fs.existsSync.mockReturnValue(true);
66
- const result = await npmPackagesCheck.run(mockContext);
67
- expect(result.status).toBe('PASS');
68
- expect(result.message).toContain('.sinapse-ai deps complete');
69
- });
70
-
71
- it('should PASS (not FAIL) when there is no .sinapse-ai/package.json', async () => {
72
- fs.existsSync.mockReturnValue(false);
73
- const result = await npmPackagesCheck.run(mockContext);
74
- expect(result.status).toBe('PASS');
75
- expect(result.message).toContain('no .sinapse-ai/package.json');
76
- expect(result.fixCommand).toBeNull();
77
- });
78
-
79
- it('should FAIL when a declared .sinapse-ai dep is unresolvable', async () => {
80
- // .sinapse-ai/package.json present, declaring a dep that cannot resolve.
81
- fs.existsSync.mockImplementation((p) =>
82
- String(p).includes('package.json'),
83
- );
84
- fs.readFileSync.mockReturnValue(
85
- JSON.stringify({ dependencies: { '@sinapse/definitely-not-installed-xyz': '^1.0.0' } }),
86
- );
87
- const result = await npmPackagesCheck.run(mockContext);
88
- expect(result.status).toBe('FAIL');
89
- expect(result.message).toContain('unresolvable');
90
- expect(result.fixCommand).toBe('cd .sinapse-ai && npm install --production');
91
- });
92
- });
93
-
94
- describe('settings-json check', () => {
95
- it('should PASS with valid settings and sufficient deny rules', async () => {
96
- fs.existsSync.mockReturnValue(true);
97
- const mockSettings = {
98
- permissions: {
99
- deny: new Array(50).fill('Edit(.sinapse-ai/core/)'),
100
- allow: ['Edit(docs/)'],
101
- },
102
- };
103
- const coreConfig = 'boundary:\n protected:\n - .sinapse-ai/core/**\n exceptions:\n - agents/MEMORY.md';
104
- fs.readFileSync.mockImplementation((p) => {
105
- if (p.includes('settings.json')) return JSON.stringify(mockSettings);
106
- if (p.includes('core-config')) return coreConfig;
107
- return '';
108
- });
109
-
110
- const result = await settingsJsonCheck.run(mockContext);
111
- expect(result.status).toBe('PASS');
112
- expect(result.message).toContain('50 rules');
113
- });
114
-
115
- it('should FAIL when settings.json not found', async () => {
116
- fs.existsSync.mockReturnValue(false);
117
- const result = await settingsJsonCheck.run(mockContext);
118
- expect(result.status).toBe('FAIL');
119
- });
120
-
121
- it('should WARN when deny rules below threshold', async () => {
122
- fs.existsSync.mockReturnValue(true);
123
- const mockSettings = { permissions: { deny: ['one'], allow: [] } };
124
- fs.readFileSync.mockReturnValue(JSON.stringify(mockSettings));
125
-
126
- const result = await settingsJsonCheck.run(mockContext);
127
- expect(result.status).toBe('WARN');
128
- });
129
-
130
- it('should WARN when boundary paths not covered by deny rules', async () => {
131
- fs.existsSync.mockReturnValue(true);
132
- const mockSettings = {
133
- permissions: {
134
- deny: new Array(50).fill('Edit(docs/)'),
135
- allow: [],
136
- },
137
- };
138
- const coreConfig = 'boundary:\n protected:\n - .sinapse-ai/core/**\n - bin/sinapse.js\n exceptions:\n - test';
139
- fs.readFileSync.mockImplementation((p) => {
140
- if (p.includes('settings.json')) return JSON.stringify(mockSettings);
141
- if (p.includes('core-config')) return coreConfig;
142
- return '';
143
- });
144
-
145
- const result = await settingsJsonCheck.run(mockContext);
146
- expect(result.status).toBe('WARN');
147
- expect(result.message).toContain('boundary coverage');
148
- });
149
- });
150
-
151
- describe('rules-files check', () => {
152
- it('should PASS when all 7 rules files exist', async () => {
153
- fs.existsSync.mockReturnValue(true);
154
- const result = await rulesFilesCheck.run(mockContext);
155
- expect(result.status).toBe('PASS');
156
- expect(result.message).toContain('7');
157
- });
158
-
159
- it('should FAIL when rules directory missing', async () => {
160
- fs.existsSync.mockReturnValue(false);
161
- const result = await rulesFilesCheck.run(mockContext);
162
- expect(result.status).toBe('FAIL');
163
- });
164
-
165
- it('should WARN when some rules missing', async () => {
166
- fs.existsSync.mockImplementation((p) => {
167
- // Directory exists
168
- if (p.endsWith('rules')) return true;
169
- // Most files exist except 2
170
- if (p.includes('agent-authority') || p.includes('workflow-execution')) return false;
171
- return true;
172
- });
173
-
174
- const result = await rulesFilesCheck.run(mockContext);
175
- expect(result.status).toBe('WARN');
176
- expect(result.message).toContain('Missing 2');
177
- });
178
- });
179
-
180
- describe('agent-memory check', () => {
181
- it('should PASS when all 10 MEMORY.md files exist', async () => {
182
- fs.existsSync.mockReturnValue(true);
183
- const result = await agentMemoryCheck.run(mockContext);
184
- expect(result.status).toBe('PASS');
185
- expect(result.message).toContain('10/10');
186
- });
187
-
188
- it('should WARN when some MEMORY.md files missing', async () => {
189
- fs.existsSync.mockImplementation((p) => {
190
- if (p.endsWith('agents')) return true;
191
- if (p.includes('analyst') || p.includes('ux')) return false;
192
- return true;
193
- });
194
-
195
- const result = await agentMemoryCheck.run(mockContext);
196
- expect(result.status).toBe('WARN');
197
- expect(result.message).toContain('8/10');
198
- });
199
- });
200
-
201
- describe('entity-registry check', () => {
202
- it('should PASS when registry exists and is fresh', async () => {
203
- fs.existsSync.mockReturnValue(true);
204
- fs.statSync.mockReturnValue({ mtimeMs: Date.now() - 1000 });
205
- fs.readFileSync.mockReturnValue('line1\nline2\nline3');
206
-
207
- const result = await entityRegistryCheck.run(mockContext);
208
- expect(result.status).toBe('PASS');
209
- });
210
-
211
- it('should FAIL when registry not found', async () => {
212
- fs.existsSync.mockReturnValue(false);
213
- const result = await entityRegistryCheck.run(mockContext);
214
- expect(result.status).toBe('FAIL');
215
- });
216
-
217
- it('should WARN when registry is stale (>48h)', async () => {
218
- fs.existsSync.mockReturnValue(true);
219
- fs.statSync.mockReturnValue({ mtimeMs: Date.now() - 72 * 60 * 60 * 1000 });
220
- fs.readFileSync.mockReturnValue('line1\nline2');
221
-
222
- const result = await entityRegistryCheck.run(mockContext);
223
- expect(result.status).toBe('WARN');
224
- expect(result.message).toContain('72h');
225
- });
226
- });
227
-
228
- describe('git-hooks check', () => {
229
- // E8-SECURITY: the check now reads core.hooksPath via git and validates the
230
- // managed `.sinapse-ai/git-hooks/` dir is populated (catches the inert-trap).
231
- it('should PASS when managed hooksPath is populated (pre-commit + pre-push)', async () => {
232
- execFileSync.mockReturnValue('.sinapse-ai/git-hooks\n');
233
- fs.existsSync.mockReturnValue(true); // dir + pre-commit + pre-push all present
234
- const result = await gitHooksCheck.run(mockContext);
235
- expect(result.status).toBe('PASS');
236
- expect(result.message).toContain('managed git guards active');
237
- });
238
-
239
- it('should FAIL when core.hooksPath is set but the dir is MISSING (inert trap)', async () => {
240
- execFileSync.mockReturnValue('.sinapse-ai/git-hooks\n');
241
- fs.existsSync.mockReturnValue(false); // dir missing -> guards inert
242
- const result = await gitHooksCheck.run(mockContext);
243
- expect(result.status).toBe('FAIL');
244
- expect(result.message).toContain('MISSING');
245
- });
246
-
247
- it('should FAIL when hooksPath dir exists but has no pre-commit guard', async () => {
248
- execFileSync.mockReturnValue('.sinapse-ai/git-hooks\n');
249
- fs.existsSync.mockImplementation((p) => !String(p).endsWith('pre-commit')); // dir yes, pre-commit no
250
- const result = await gitHooksCheck.run(mockContext);
251
- expect(result.status).toBe('FAIL');
252
- expect(result.message).toContain('NO pre-commit');
253
- });
254
-
255
- it('should WARN when pre-commit present but pre-push missing', async () => {
256
- execFileSync.mockReturnValue('.sinapse-ai/git-hooks\n');
257
- fs.existsSync.mockImplementation((p) => !String(p).endsWith('pre-push')); // dir + pre-commit yes
258
- const result = await gitHooksCheck.run(mockContext);
259
- expect(result.status).toBe('WARN');
260
- });
261
-
262
- it('should FAIL when no core.hooksPath and no .husky fallback', async () => {
263
- execFileSync.mockImplementation(() => { throw new Error('not a git repo'); });
264
- fs.existsSync.mockReturnValue(false);
265
- const result = await gitHooksCheck.run(mockContext);
266
- expect(result.status).toBe('FAIL');
267
- });
268
-
269
- it('should WARN when husky present but core.hooksPath not consolidated', async () => {
270
- execFileSync.mockImplementation(() => { throw new Error('hooksPath unset'); });
271
- fs.existsSync.mockReturnValue(true); // .husky + expected hooks present
272
- const result = await gitHooksCheck.run(mockContext);
273
- expect(result.status).toBe('WARN');
274
- });
275
- });
276
-
277
- describe('core-config check', () => {
278
- it('should PASS when config has all required sections', async () => {
279
- fs.existsSync.mockReturnValue(true);
280
- fs.readFileSync.mockReturnValue('boundary:\n test: true\nproject:\n name: test\nide:\n sync: true');
281
-
282
- const result = await coreConfigCheck.run(mockContext);
283
- expect(result.status).toBe('PASS');
284
- });
285
-
286
- it('should FAIL when config missing', async () => {
287
- fs.existsSync.mockReturnValue(false);
288
- const result = await coreConfigCheck.run(mockContext);
289
- expect(result.status).toBe('FAIL');
290
- });
291
-
292
- it('should FAIL when missing required sections', async () => {
293
- fs.existsSync.mockReturnValue(true);
294
- fs.readFileSync.mockReturnValue('project:\n name: test');
295
-
296
- const result = await coreConfigCheck.run(mockContext);
297
- expect(result.status).toBe('FAIL');
298
- expect(result.message).toContain('boundary');
299
- });
300
- });
301
-
302
- describe('claude-md check', () => {
303
- it('should PASS when all sections present', async () => {
304
- fs.existsSync.mockReturnValue(true);
305
- fs.readFileSync.mockReturnValue(
306
- '## Constitution\n## Framework vs Project Boundary\n## Sistema de Agentes',
307
- );
308
-
309
- const result = await claudeMdCheck.run(mockContext);
310
- expect(result.status).toBe('PASS');
311
- });
312
-
313
- it('should WARN when sections missing', async () => {
314
- fs.existsSync.mockReturnValue(true);
315
- fs.readFileSync.mockReturnValue('## Constitution\nSome content');
316
-
317
- const result = await claudeMdCheck.run(mockContext);
318
- expect(result.status).toBe('WARN');
319
- expect(result.message).toContain('Missing sections');
320
- });
321
- });
322
-
323
- describe('graph-dashboard check', () => {
324
- it('should PASS when directory has .js files', async () => {
325
- fs.existsSync.mockReturnValue(true);
326
- fs.readdirSync.mockReturnValue(['index.js', 'cli.js']);
327
-
328
- const result = await graphDashboardCheck.run(mockContext);
329
- expect(result.status).toBe('PASS');
330
- });
331
-
332
- it('should WARN when directory missing', async () => {
333
- fs.existsSync.mockReturnValue(false);
334
- const result = await graphDashboardCheck.run(mockContext);
335
- expect(result.status).toBe('WARN');
336
- });
337
- });
338
-
339
- describe('code-intel check', () => {
340
- // The code-intel check does a real require() of index.js and triggers
341
- // provider auto-detection. Tests that need provider detection must use
342
- // the real projectRoot and real fs (jest.requireActual).
343
- const realFs = jest.requireActual('fs');
344
- const realProjectRoot = path.join(__dirname, '..', '..', '..', '..', '..');
345
-
346
- it('should return INFO when code-intel dir does not exist', async () => {
347
- fs.existsSync.mockReturnValue(false);
348
- const result = await codeIntelCheck.run(mockContext);
349
- expect(result.status).toBe('INFO');
350
- });
351
-
352
- it('should WARN when index.js missing but dir exists', async () => {
353
- fs.existsSync.mockImplementation((p) => {
354
- // Dir exists, but index.js does not
355
- if (p.includes('index.js')) return false;
356
- return true;
357
- });
358
- const result = await codeIntelCheck.run(mockContext);
359
- expect(result.status).toBe('WARN');
360
- expect(result.message).toContain('index.js not found');
361
- });
362
-
363
- it('should PASS with RegistryProvider when entity-registry exists', async () => {
364
- // Use real fs + real project root so require() resolves the actual module
365
- // and RegistryProvider can load the real entity-registry.yaml
366
- const realContext = {
367
- ...mockContext,
368
- projectRoot: realProjectRoot,
369
- };
370
-
371
- // Temporarily restore real fs for this test
372
- fs.existsSync.mockImplementation(realFs.existsSync);
373
- fs.readFileSync.mockImplementation(realFs.readFileSync);
374
- fs.statSync.mockImplementation(realFs.statSync);
375
-
376
- // Only run if entity-registry actually exists (skip in CI without registry)
377
- const registryPath = path.join(realProjectRoot, '.sinapse-ai', 'data', 'entity-registry.yaml');
378
- if (!realFs.existsSync(registryPath)) {
379
- return; // skip — no registry available
380
- }
381
-
382
- const result = await codeIntelCheck.run(realContext);
383
- expect(result.status).toBe('PASS');
384
- expect(result.message).toContain('RegistryProvider');
385
- });
386
-
387
- it('should WARN when provider detection fails (no valid registry)', async () => {
388
- // Use real project root so require() works, but mock registry as empty
389
- const realContext = {
390
- ...mockContext,
391
- projectRoot: realProjectRoot,
392
- };
393
-
394
- // Real existsSync for module resolution, mocked readFileSync for empty registry
395
- fs.existsSync.mockImplementation((p) => {
396
- if (p.includes('entity-registry.yaml')) return true;
397
- return realFs.existsSync(p);
398
- });
399
- fs.statSync.mockImplementation((p) => {
400
- if (p.includes('entity-registry.yaml')) return { mtimeMs: Date.now(), size: 10 };
401
- return realFs.statSync(p);
402
- });
403
- fs.readFileSync.mockImplementation((p, enc) => {
404
- if (typeof p === 'string' && p.includes('entity-registry.yaml')) {
405
- return 'metadata:\n entityCount: 0';
406
- }
407
- return realFs.readFileSync(p, enc);
408
- });
409
-
410
- const result = await codeIntelCheck.run(realContext);
411
- // Without valid entities, provider won't be available → WARN or INFO
412
- expect(['WARN', 'INFO']).toContain(result.status);
413
- });
414
- });
415
-
416
- describe('ide-sync check', () => {
417
- it('should PASS when counts match', async () => {
418
- fs.existsSync.mockReturnValue(true);
419
- fs.readdirSync.mockImplementation((p) => {
420
- if (p.includes('commands')) return ['dev.md', 'qa.md'];
421
- return ['dev.md', 'qa.md'];
422
- });
423
-
424
- const result = await ideSyncCheck.run(mockContext);
425
- expect(result.status).toBe('PASS');
426
- });
427
-
428
- it('should WARN when IDE has fewer agents than expected (Audit 1 P1 DOC-1)', async () => {
429
- // Post-DOC-1 fix: WARN fires when IDE count < (framework + squad orqx).
430
- // Source dir has 5 framework agents; IDE has only 2 (missing 3 expected).
431
- fs.existsSync.mockReturnValue(true);
432
- fs.readdirSync.mockImplementation((p) => {
433
- if (p.includes('commands')) return ['dev.md', 'qa.md']; // 2 in IDE
434
- // Source agents dir — 5 framework agents (>= IDE count → expected gap)
435
- return ['dev.md', 'qa.md', 'pm.md', 'po.md', 'sm.md'];
436
- });
437
-
438
- const result = await ideSyncCheck.run(mockContext);
439
- expect(result.status).toBe('WARN');
440
- });
441
- });
442
-
443
- // === INS-4.8: New checks ===
444
-
445
- describe('skills-count check', () => {
446
- // v1.4.2: .claude/skills/ is an OPTIONAL user-installed area, not shipped by
447
- // `npx sinapse-ai install`. The check never FAILs: it PASSes proportional to
448
- // however many skills the user installed (>=1), and reports INFO when the
449
- // directory is absent or empty.
450
-
451
- it('should PASS when skill directories with SKILL.md exist', async () => {
452
- fs.existsSync.mockReturnValue(true);
453
- const dirs = Array.from({ length: 8 }, (_, i) => ({
454
- name: `skill-${i}`,
455
- isDirectory: () => true,
456
- isFile: () => false,
457
- }));
458
- fs.readdirSync.mockReturnValue(dirs);
459
-
460
- const result = await skillsCountCheck.run(mockContext);
461
- expect(result.check).toBe('skills-count');
462
- expect(result.status).toBe('PASS');
463
- expect(result.message).toContain('8');
464
- });
465
-
466
- it('should PASS even with a small number of installed skills', async () => {
467
- fs.existsSync.mockReturnValue(true);
468
- const dirs = Array.from({ length: 3 }, (_, i) => ({
469
- name: `skill-${i}`,
470
- isDirectory: () => true,
471
- isFile: () => false,
472
- }));
473
- fs.readdirSync.mockReturnValue(dirs);
474
-
475
- const result = await skillsCountCheck.run(mockContext);
476
- expect(result.status).toBe('PASS');
477
- expect(result.message).toContain('3');
478
- });
479
-
480
- it('should report INFO (not FAIL) when 0 skills found', async () => {
481
- // skills dir exists, but no subdir has a SKILL.md → count 0.
482
- fs.existsSync.mockImplementation((p) => !String(p).includes('SKILL.md'));
483
- const dirs = [{ name: 'empty', isDirectory: () => true, isFile: () => false }];
484
- fs.readdirSync.mockReturnValue(dirs);
485
-
486
- const result = await skillsCountCheck.run(mockContext);
487
- expect(result.status).toBe('INFO');
488
- expect(result.fixCommand).toBeNull();
489
- });
490
-
491
- it('should report INFO (not FAIL) when skills directory missing', async () => {
492
- fs.existsSync.mockReturnValue(false);
493
- const result = await skillsCountCheck.run(mockContext);
494
- expect(result.status).toBe('INFO');
495
- });
496
- });
497
-
498
- describe('commands-count check', () => {
499
- it('should PASS when >=20 command files', async () => {
500
- fs.existsSync.mockReturnValue(true);
501
- const files = Array.from({ length: 22 }, (_, i) => ({
502
- name: `cmd-${i}.md`,
503
- isDirectory: () => false,
504
- isFile: () => true,
505
- }));
506
- fs.readdirSync.mockReturnValue(files);
507
-
508
- const result = await commandsCountCheck.run(mockContext);
509
- expect(result.check).toBe('commands-count');
510
- expect(result.status).toBe('PASS');
511
- expect(result.message).toContain('22');
512
- });
513
-
514
- it('should WARN when 12-19 command files', async () => {
515
- fs.existsSync.mockReturnValue(true);
516
- const files = Array.from({ length: 15 }, (_, i) => ({
517
- name: `cmd-${i}.md`,
518
- isDirectory: () => false,
519
- isFile: () => true,
520
- }));
521
- fs.readdirSync.mockReturnValue(files);
522
-
523
- const result = await commandsCountCheck.run(mockContext);
524
- expect(result.status).toBe('WARN');
525
- expect(result.message).toContain('15/20');
526
- });
527
-
528
- it('should FAIL when <12 command files', async () => {
529
- fs.existsSync.mockReturnValue(true);
530
- const files = Array.from({ length: 5 }, (_, i) => ({
531
- name: `cmd-${i}.md`,
532
- isDirectory: () => false,
533
- isFile: () => true,
534
- }));
535
- fs.readdirSync.mockReturnValue(files);
536
-
537
- const result = await commandsCountCheck.run(mockContext);
538
- expect(result.status).toBe('FAIL');
539
- expect(result.message).toContain('5');
540
- });
541
-
542
- it('should FAIL when commands directory missing', async () => {
543
- fs.existsSync.mockReturnValue(false);
544
- const result = await commandsCountCheck.run(mockContext);
545
- expect(result.status).toBe('FAIL');
546
- });
547
- });
548
-
549
- describe('hooks-claude-count check', () => {
550
- it('should PASS when >=2 hook files and registered', async () => {
551
- fs.existsSync.mockReturnValue(true);
552
- const hookFiles = [
553
- { name: 'enforce-git-push.cjs', isFile: () => true, isDirectory: () => false },
554
- { name: 'pre-commit-check.cjs', isFile: () => true, isDirectory: () => false },
555
- ];
556
- fs.readdirSync.mockReturnValue(hookFiles);
557
- const settingsLocal = {
558
- hooks: {
559
- PreToolUse: [{ command: 'node .claude/hooks/enforce-git-push.cjs' }],
560
- PostToolUse: [{ command: 'node .claude/hooks/pre-commit-check.cjs' }],
561
- },
562
- };
563
- fs.readFileSync.mockReturnValue(JSON.stringify(settingsLocal));
564
-
565
- const result = await hooksClaudeCountCheck.run(mockContext);
566
- expect(result.check).toBe('hooks-claude-count');
567
- expect(result.status).toBe('PASS');
568
- expect(result.message).toContain('2');
569
- });
570
-
571
- it('should WARN when hooks present but not registered', async () => {
572
- fs.existsSync.mockImplementation((p) => {
573
- if (p.includes('settings.local.json')) return true;
574
- return true;
575
- });
576
- const hookFiles = [
577
- { name: 'hook-a.cjs', isFile: () => true, isDirectory: () => false },
578
- { name: 'hook-b.cjs', isFile: () => true, isDirectory: () => false },
579
- ];
580
- fs.readdirSync.mockReturnValue(hookFiles);
581
- fs.readFileSync.mockReturnValue(JSON.stringify({ hooks: {} }));
582
-
583
- const result = await hooksClaudeCountCheck.run(mockContext);
584
- expect(result.status).toBe('WARN');
585
- expect(result.message).toContain('not registered');
586
- });
587
-
588
- it('should WARN when <2 hook files', async () => {
589
- fs.existsSync.mockReturnValue(true);
590
- const hookFiles = [
591
- { name: 'single-hook.cjs', isFile: () => true, isDirectory: () => false },
592
- ];
593
- fs.readdirSync.mockReturnValue(hookFiles);
594
- fs.readFileSync.mockReturnValue(JSON.stringify({ hooks: {} }));
595
-
596
- const result = await hooksClaudeCountCheck.run(mockContext);
597
- expect(result.status).toBe('WARN');
598
- expect(result.message).toContain('1/2');
599
- });
600
-
601
- it('should FAIL when no hook files found', async () => {
602
- fs.existsSync.mockReturnValue(true);
603
- fs.readdirSync.mockReturnValue([]);
604
-
605
- const result = await hooksClaudeCountCheck.run(mockContext);
606
- expect(result.status).toBe('FAIL');
607
- });
608
-
609
- it('should FAIL when hooks directory missing', async () => {
610
- fs.existsSync.mockReturnValue(false);
611
- const result = await hooksClaudeCountCheck.run(mockContext);
612
- expect(result.status).toBe('FAIL');
613
- });
614
- });
615
-
616
- // === INS-4.8: Registry and task validation ===
617
-
618
- describe('check registry (INS-4.8)', () => {
619
- it('should load 15 checks total', () => {
620
- // loadChecks is the real function (not mocked) — verifies registration
621
- const checks = loadChecks();
622
- expect(checks).toHaveLength(16);
623
- });
624
-
625
- it('should include all 3 new checks', () => {
626
- const checks = loadChecks();
627
- const names = checks.map((c) => c.name);
628
- expect(names).toContain('skills-count');
629
- expect(names).toContain('commands-count');
630
- expect(names).toContain('hooks-claude-count');
631
- });
632
- });
633
-
634
- describe('health-check.yaml task (INS-4.8)', () => {
635
- it('should NOT have *doctor alias', () => {
636
- const realFs = jest.requireActual('fs');
637
- const yaml = realFs.readFileSync(
638
- path.join(__dirname, '..', '..', '..', '..', '..', '.sinapse-ai', 'development', 'tasks', 'health-check.yaml'),
639
- 'utf8',
640
- );
641
- // Verify *doctor is not in the aliases list (only *hc should be)
642
- const aliasMatch = yaml.match(/aliases:\s*\n((?:\s+-\s+.*\n)*)/);
643
- expect(aliasMatch).toBeTruthy();
644
- expect(aliasMatch[1]).not.toContain('*doctor');
645
- expect(aliasMatch[1]).toContain('*hc');
646
- });
647
-
648
- it('should reference sinapse doctor --json in instructions', () => {
649
- const realFs = jest.requireActual('fs');
650
- const yaml = realFs.readFileSync(
651
- path.join(__dirname, '..', '..', '..', '..', '..', '.sinapse-ai', 'development', 'tasks', 'health-check.yaml'),
652
- 'utf8',
653
- );
654
- expect(yaml).toContain('sinapse doctor --json');
655
- expect(yaml).toContain('npx sinapse-ai doctor --json');
656
- });
657
-
658
- it('should have governance_map with all 15 checks', () => {
659
- const realFs = jest.requireActual('fs');
660
- const yaml = realFs.readFileSync(
661
- path.join(__dirname, '..', '..', '..', '..', '..', '.sinapse-ai', 'development', 'tasks', 'health-check.yaml'),
662
- 'utf8',
663
- );
664
- const expectedChecks = [
665
- 'settings-json', 'rules-files', 'agent-memory', 'entity-registry',
666
- 'git-hooks', 'core-config', 'claude-md', 'ide-sync', 'graph-dashboard',
667
- 'code-intel', 'node-version', 'npm-packages', 'skills-count',
668
- 'commands-count', 'hooks-claude-count',
669
- ];
670
- for (const check of expectedChecks) {
671
- expect(yaml).toContain(`${check}:`);
672
- }
673
- });
674
- });
675
-