sinapse-ai 1.25.2 → 1.26.0

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.
@@ -10,8 +10,16 @@ not as a black-box library.
10
10
 
11
11
  ## Locate the corpus
12
12
 
13
- Resolve the SINAPSE package/repository root, then read
14
- `docs/framework/react-bits/index.md`. Read only the category needed:
13
+ Resolve the corpus in this exact order, then read `index.md` from the first
14
+ location that contains the complete corpus:
15
+
16
+ 1. `<project>/docs/framework/react-bits/`
17
+ 2. `<installed-package>/docs/framework/react-bits/`
18
+ 3. `~/.sinapse/docs/framework/react-bits/`
19
+
20
+ The project location wins so a project can pin or extend its installed research.
21
+ The package location supports package-managed runs, and the global SINAPSE home
22
+ supports `--global-only` installs. Read only the category needed:
15
23
 
16
24
  - `animations.md` for wrappers, cursors, trails, transitions and interactive effects.
17
25
  - `text-animations.md` for headline, counter, scramble, marquee and reveal work.
@@ -21,8 +29,9 @@ Resolve the SINAPSE package/repository root, then read
21
29
  - `audit-findings.md` when dependency, prop, variant or upstream accuracy matters.
22
30
  - `inventory.json` for exact machine-readable props, dependencies and variant state.
23
31
 
24
- If the corpus cannot be found, read `references/corpus-map.md`, then verify current
25
- facts against the official registry and source before implementation.
32
+ If none of those locations has all nine files, state which locations were checked,
33
+ ask the user to run the relevant SINAPSE install/update, then verify current facts
34
+ against the official registry and source before implementation.
26
35
 
27
36
  ## Workflow
28
37
 
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "skillIds": [
4
+ "react-bits-frontend",
4
5
  "sinapse",
5
6
  "sinapse-agent",
6
7
  "sinapse-analyst",
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: react-bits-frontend
3
+ description: Select, install, adapt, combine, optimize, and audit official React Bits components for React, Next.js, Vite, Astro islands, and SINAPSE frontend projects. Use for animated text, cursors, reveals, cards, navigation, galleries, WebGL backgrounds, shaders, particles, motion systems, React Bits CLI/MCP commands, component props/dependencies, or when improving an existing page with React Bits while preserving accessibility, mobile performance, lifecycle cleanup, design-system fit, and license compliance.
4
+ ---
5
+
6
+ # React Bits Frontend
7
+
8
+ Use the source-grounded SINAPSE corpus to implement React Bits as application code,
9
+ not as a black-box library.
10
+
11
+ ## Locate the corpus
12
+
13
+ Resolve the corpus in this exact order, then read `index.md` from the first
14
+ location that contains the complete corpus:
15
+
16
+ 1. `<project>/docs/framework/react-bits/`
17
+ 2. `<installed-package>/docs/framework/react-bits/`
18
+ 3. `~/.sinapse/docs/framework/react-bits/`
19
+
20
+ The project location wins so a project can pin or extend its installed research.
21
+ The package location supports package-managed runs, and the global SINAPSE home
22
+ supports `--global-only` installs. Read only the category needed:
23
+
24
+ - `animations.md` for wrappers, cursors, trails, transitions and interactive effects.
25
+ - `text-animations.md` for headline, counter, scramble, marquee and reveal work.
26
+ - `components.md` for cards, navigation, galleries, inputs and compound UI.
27
+ - `backgrounds.md` for Canvas/WebGL/shader/particle backgrounds.
28
+ - `implementation-playbook.md` for integration, composition, accessibility and tuning.
29
+ - `audit-findings.md` when dependency, prop, variant or upstream accuracy matters.
30
+ - `inventory.json` for exact machine-readable props, dependencies and variant state.
31
+
32
+ If none of those locations has all nine files, state which locations were checked,
33
+ ask the user to run the relevant SINAPSE install/update, then verify current facts
34
+ against the official registry and source before implementation.
35
+
36
+ ## Workflow
37
+
38
+ 1. Inspect the target stack, component architecture, design tokens, performance budget
39
+ and existing animation/scroll ownership.
40
+ 2. Translate the visual request into a user-experience function.
41
+ 3. Search the relevant catalog and shortlist at most three candidates.
42
+ 4. Compare visual fit, dependencies, runtime engine, props, interaction model,
43
+ accessibility, mobile cost and compatibility with existing effects.
44
+ 5. Open the official source for the selected variant. Treat source and registry as
45
+ authoritative when docs conflict.
46
+ 6. Run `npm view <package>` for every declared dependency before installing it.
47
+ 7. Prefer TS-TW or TS-CSS in TypeScript projects. Never use the three empty CurvedInput
48
+ variants recorded in the snapshot; use JS-CSS or verify that upstream fixed them.
49
+ 8. Install through shadcn/jsrepo or copy manually. Keep upstream provenance and the
50
+ required license notice for substantial copies.
51
+ 9. Wrap the imported component behind a local application component. Map design
52
+ tokens, content, breakpoints and quality presets at that boundary.
53
+ 10. Implement reduced-motion behavior, semantic fallback, keyboard/focus behavior,
54
+ cleanup and static/mobile fallback.
55
+ 11. Test load, resize, route unmount/remount, long scroll, coarse pointer, reduced
56
+ motion and low-power mobile. Measure frame time and memory.
57
+ 12. Report component, variant, source SHA/URL, dependencies, adaptations, fallbacks and
58
+ remaining risks.
59
+
60
+ ## Composition rules
61
+
62
+ - Use one dominant motion effect per viewport and no more than two support effects.
63
+ - Prefer the lowest-cost engine that satisfies the visual objective.
64
+ - Allow only one owner for smooth scroll, custom cursor and each render loop.
65
+ - Do not stack multiple heavy WebGL/physics backgrounds without an explicit budget.
66
+ - Pause offscreen/hidden loops and dispose GPU resources on unmount.
67
+ - Preserve content and interaction when animation is disabled or fails.
68
+
69
+ ## Accuracy rules
70
+
71
+ - Do not repeat the promotional “140+” as an exact count. The pinned snapshot has 139.
72
+ - Do not infer functional variants from registry names alone; inspect source bytes/code.
73
+ - Use registry JSON for install dependencies and source for actual API/behavior.
74
+ - Note documentation typos and per-variant divergences from `audit-findings.md`.
75
+ - Do not redistribute React Bits components as a standalone library, bundle, or port.
@@ -34,6 +34,10 @@ function isPathInside(parentPath, candidatePath) {
34
34
  return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
35
35
  }
36
36
 
37
+ function isSafeSkillId(skillId) {
38
+ return typeof skillId === 'string' && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(skillId);
39
+ }
40
+
37
41
  function readCanonicalSource(projectRoot, sourcePath) {
38
42
  if (!sourcePath) {
39
43
  throw new Error('Codex agent pointer does not declare a canonical source');
@@ -329,6 +333,14 @@ function readCodexCatalog(projectRoot = PROJECT_ROOT) {
329
333
  if (typeof catalog.genericAgentSkillId !== 'string' || !catalog.genericAgentSkillId.trim()) {
330
334
  throw new Error('Codex catalog does not declare genericAgentSkillId');
331
335
  }
336
+ const catalogSkillIds = [
337
+ ...catalog.expectedSkillIds,
338
+ ...catalog.publicAliasSkillIds,
339
+ catalog.genericAgentSkillId,
340
+ ];
341
+ if (catalogSkillIds.some((skillId) => !isSafeSkillId(skillId))) {
342
+ throw new Error('Codex catalog contains an unsafe skill ID');
343
+ }
332
344
  return catalog;
333
345
  }
334
346
 
@@ -1,7 +1,7 @@
1
1
  metadata:
2
2
  version: 1.0.0
3
- lastUpdated: '2026-07-15T08:35:27.084Z'
4
- entityCount: 817
3
+ lastUpdated: '2026-07-15T14:30:07.448Z'
4
+ entityCount: 818
5
5
  checksumAlgorithm: sha256
6
6
  resolutionRate: 100
7
7
  entities:
@@ -166,8 +166,8 @@ entities:
166
166
  score: 0.7
167
167
  constraints: []
168
168
  extensionPoints: []
169
- checksum: sha256:1f385982ca84a8232f3f6b1fce4257ca3971a5fd24830b5389477f9a5a8e943a
170
- lastVerified: '2026-07-15T08:12:47.676Z'
169
+ checksum: sha256:86db5a2578b6a0b4849fde4e0f4a197a41560a3a213b247f0edd8522d857ffea
170
+ lastVerified: '2026-07-15T13:41:11.702Z'
171
171
  local:
172
172
  path: bin/commands/local.js
173
173
  layer: L1
@@ -276,8 +276,8 @@ entities:
276
276
  score: 0.7
277
277
  constraints: []
278
278
  extensionPoints: []
279
- checksum: sha256:b51998a172a91e134330847185c078d317ed9be2da6746de3af18d209b7e08d9
280
- lastVerified: '2026-07-15T08:12:47.677Z'
279
+ checksum: sha256:05056e197ecb29e6f63a1d86d9d77d944e3083021bf632b48a76eb4cb2223b95
280
+ lastVerified: '2026-07-15T13:41:11.703Z'
281
281
  command-generator:
282
282
  path: bin/lib/command-generator.js
283
283
  layer: L1
@@ -299,6 +299,7 @@ entities:
299
299
  - constants
300
300
  - squads
301
301
  - fs-utils
302
+ - provider-contract
302
303
  externalDeps: []
303
304
  plannedDeps: []
304
305
  lifecycle: production
@@ -306,8 +307,8 @@ entities:
306
307
  score: 0.7
307
308
  constraints: []
308
309
  extensionPoints: []
309
- checksum: sha256:ed3f86d0349e4a709240728c0872bf44c3ebb2f1b8f3522c83e2f43c9b8c5647
310
- lastVerified: '2026-07-15T02:37:43.455Z'
310
+ checksum: sha256:837f10f3ce227b1f3ff9b3d4c9e824e8928cd67ec07b0dcebd78e85468eed003
311
+ lastVerified: '2026-07-15T13:41:11.703Z'
311
312
  constants:
312
313
  path: bin/lib/constants.js
313
314
  layer: L1
@@ -413,7 +414,8 @@ entities:
413
414
  usedBy:
414
415
  - install
415
416
  - update
416
- dependencies: []
417
+ dependencies:
418
+ - provider-contract
417
419
  externalDeps: []
418
420
  plannedDeps: []
419
421
  lifecycle: production
@@ -421,8 +423,8 @@ entities:
421
423
  score: 0.7
422
424
  constraints: []
423
425
  extensionPoints: []
424
- checksum: sha256:d28b19935a9c75408df9da7430fc3ca9568bdea0cfc1ae3c01d0025ad19fc2a3
425
- lastVerified: '2026-07-15T07:09:56.286Z'
426
+ checksum: sha256:898fbe5a00cf1bb77bc8e8a34ee40337145912374a2e809789a02d5345fdd679
427
+ lastVerified: '2026-07-15T14:30:07.273Z'
426
428
  header:
427
429
  path: bin/lib/header.js
428
430
  layer: L1
@@ -475,6 +477,28 @@ entities:
475
477
  extensionPoints: []
476
478
  checksum: sha256:dc7cc1109f2bbdb7b3c5927df0de2418925842bdacdc0ba82815bfa2783d0b42
477
479
  lastVerified: '2026-06-22T02:41:18.152Z'
480
+ provider-contract:
481
+ path: bin/lib/provider-contract.js
482
+ layer: L1
483
+ type: script
484
+ purpose: Entity at bin\lib\provider-contract.js
485
+ keywords:
486
+ - provider
487
+ - contract
488
+ usedBy:
489
+ - command-generator
490
+ - global-provider-adapters
491
+ - provider-parity
492
+ dependencies: []
493
+ externalDeps: []
494
+ plannedDeps: []
495
+ lifecycle: production
496
+ adaptability:
497
+ score: 0.7
498
+ constraints: []
499
+ extensionPoints: []
500
+ checksum: sha256:a18d271a73b2f371213af4d9503e302e9237920057afd521b723c96efd28fda0
501
+ lastVerified: '2026-07-15T13:41:11.704Z'
478
502
  provider-parity:
479
503
  path: bin/lib/provider-parity.js
480
504
  layer: L1
@@ -486,7 +510,8 @@ entities:
486
510
  usedBy:
487
511
  - install
488
512
  - update
489
- dependencies: []
513
+ dependencies:
514
+ - provider-contract
490
515
  externalDeps: []
491
516
  plannedDeps: []
492
517
  lifecycle: production
@@ -494,8 +519,8 @@ entities:
494
519
  score: 0.7
495
520
  constraints: []
496
521
  extensionPoints: []
497
- checksum: sha256:592206326a2a3c5a3d89068d9ef065d698562aef40e6096d971fb793cea11c97
498
- lastVerified: '2026-07-15T08:35:26.859Z'
522
+ checksum: sha256:5f0ff4517425fef01683f035afef1d139aede2fe47956773a398e7c761ace42d
523
+ lastVerified: '2026-07-15T13:41:11.704Z'
499
524
  setup-statusline:
500
525
  path: bin/lib/setup-statusline.js
501
526
  layer: L1
@@ -7,7 +7,7 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 1.25.2
10
+ version: 1.26.0
11
11
  generator: scripts/generate-install-manifest.js
12
12
  file_count: 1156
13
13
  files:
@@ -1368,9 +1368,9 @@ files:
1368
1368
  type: data
1369
1369
  size: 9671
1370
1370
  - path: data/entity-registry.yaml
1371
- hash: sha256:ed0919b2bc3479beb4e46d3ccfbbbda66e46161c7eebc9018b9f0c8e5278d7f1
1371
+ hash: sha256:08edc9d3cde1bf956cce462004670a3d2924a691a16c27bdfd2bf1ce4de037f8
1372
1372
  type: data
1373
- size: 559991
1373
+ size: 560701
1374
1374
  - path: data/learned-patterns.yaml
1375
1375
  hash: sha256:1a4cd045c087b9dfd7046ff1464a9d2edb85fba77cf0b6fba14f4bb9004c741e
1376
1376
  type: data
package/CHANGELOG.md CHANGED
@@ -1,16 +1,17 @@
1
- ## [1.25.2](https://github.com/caioimori/sinapse-ai/compare/1.25.1...1.25.2) (2026-07-15)
1
+ ## [1.26.0](https://github.com/caioimori/sinapse-ai/compare/1.25.3...1.26.0) (2026-07-15)
2
+
3
+ ### Features
4
+
5
+ * deliver React Bits across providers [Story react-bits-distribution-parity] ([aa191f1](https://github.com/caioimori/sinapse-ai/commit/aa191f102dc417e3a0182c02661907e474226abb))
2
6
 
3
7
  ### Bug Fixes
4
8
 
5
- * **docs:** align public Claude and Codex guidance [Story Cross-Provider] ([8c005ec](https://github.com/caioimori/sinapse-ai/commit/8c005ec4f0c5702131564cb5d3d52fee8ed85b50))
6
- * **docs:** close public provider guidance gaps [Story Cross-Provider] ([43f76e2](https://github.com/caioimori/sinapse-ai/commit/43f76e2b405ffae61598d8999f043a1d24ea24a5))
9
+ * **docs:** align provider skill metrics [Story react-bits-distribution-parity] ([729d120](https://github.com/caioimori/sinapse-ai/commit/729d120ee1735473d01971efdb746be59d2d19d8))
10
+ * **react-bits:** harden corpus delivery and orchestration [Story react-bits-distribution-parity] ([6456062](https://github.com/caioimori/sinapse-ai/commit/6456062cd756826e6bc14c698ebde07298d8a5eb))
7
11
 
8
12
  ### Documentation
9
13
 
10
- * harden provider backup guidance [Story cross-provider-cli-quality] ([5f4bffd](https://github.com/caioimori/sinapse-ai/commit/5f4bffd351c7ac1c0ee5cde2d7388e34409e0566))
11
- * **story:** normalize validation terminology [Story sinapse-only-catalog-cleanup] ([7e0ecb9](https://github.com/caioimori/sinapse-ai/commit/7e0ecb9fb01a6626ccc7c665d262c38c61196120))
12
- * **story:** reconcile post-release evidence [Story sinapse-only-catalog-cleanup] ([3c10c65](https://github.com/caioimori/sinapse-ai/commit/3c10c65fcfdca77ec0d1c1af6a2831af39b0adf0))
13
- * **story:** record SINAPSE 1.25.1 release [Story sinapse-only-catalog-cleanup] ([ba9f776](https://github.com/caioimori/sinapse-ai/commit/ba9f77607879c6efe8511e38cfeab6c71d2335c4))
14
+ * close installer parity release evidence [Story hotfix-installer-provider-adapter-parity-1.25.3] ([#386](https://github.com/caioimori/sinapse-ai/issues/386)) ([ceafcb8](https://github.com/caioimori/sinapse-ai/commit/ceafcb8ac522835c264115e9ee16cba6e49e173c))
14
15
 
15
16
  # Changelog
16
17
 
package/README.en.md CHANGED
@@ -309,7 +309,7 @@ Both IDEs have access to all 17 squads, 172 agents, workflows, and knowledge bas
309
309
  | Native agents | 172 | 172 |
310
310
  | Direct activation | `@agent-name` | `$sinapse-agent agent-id` |
311
311
  | Primary routing | `@sinapse-orqx` | `$snps` |
312
- | Installed skills | 36 | 37 |
312
+ | Installed skills | 37 | 37 |
313
313
  | Registered hooks | 20 native registrations | 9 lifecycle events through the bridge |
314
314
  | Validation source | `npm run validate:providers` | `npm run validate:codex-native` |
315
315
 
package/README.md CHANGED
@@ -331,7 +331,7 @@ Ambas as IDEs têm acesso a todos os 17 squads, 172 agentes, workflows e knowled
331
331
  | Agentes nativos | 172 | 172 |
332
332
  | Ativação direta | `@agent-name` | `$sinapse-agent agent-id` |
333
333
  | Roteamento principal | `@sinapse-orqx` | `$snps` |
334
- | Skills instaladas | 36 | 37 |
334
+ | Skills instaladas | 37 | 37 |
335
335
  | Hooks registrados | 20 registros nativos | 9 eventos de lifecycle via bridge |
336
336
  | Fonte de validação | `npm run validate:providers` | `npm run validate:codex-native` |
337
337
 
@@ -50,6 +50,10 @@ const {
50
50
  } = require('../lib/command-generator');
51
51
  const { deliverGlobalProviderAdapters, getGlobalCommandStagingDir } = require('../lib/global-provider-adapters');
52
52
  const { assertProviderAdapterParity } = require('../lib/provider-parity');
53
+ const {
54
+ copyReactBitsCapabilitySync,
55
+ REACT_BITS_SKILL_RELATIVE_PATH,
56
+ } = require('../../packages/installer/src/installer/react-bits-corpus');
53
57
  // Follow-up #13 — wire the transactional backup/rollback engine into the
54
58
  // installer so an in-place UPGRADE (upsert) that fails mid-flight is restored
55
59
  // to its previous state instead of leaving ~/.sinapse half-updated.
@@ -317,7 +321,12 @@ async function cmdInstallGlobal(opts = {}) {
317
321
  // rich, frontmatter'd stubs are always the final word. Idempotent.
318
322
  try {
319
323
  const commandsDir = getGlobalCommandStagingDir({ llmChoice, sinapseHome: SINAPSE_HOME, claudeCommandsDir: CLAUDE_COMMANDS_DIR });
320
- const reconciled = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir });
324
+ const reconciled = deliverGlobalProviderAdapters({
325
+ llmChoice,
326
+ home: HOME,
327
+ commandsDir,
328
+ reactBitsSkillPath: path.join(SINAPSE_HOME, REACT_BITS_SKILL_RELATIVE_PATH),
329
+ });
321
330
  const total = reconciled.claude.length + reconciled.codex.length + reconciled.skills.length;
322
331
  if (total) logger.always(` ${GREEN}OK${NC} Global provider adapters reconciled (${total} artifacts)`);
323
332
  } catch (e) {
@@ -557,10 +566,13 @@ function installFatalPhases({ squads, squadsDir, isUpsert, llmChoice, existing,
557
566
  logger.always(` ${GREEN}OK${NC} core development (${coreAgents} agents)`);
558
567
  }
559
568
 
569
+ const reactBits = copyReactBitsCapabilitySync(ROOT, SINAPSE_HOME);
570
+ if (reactBits.files.length) logger.always(` ${GREEN}OK${NC} React Bits corpus (${reactBits.files.length} files)`);
571
+
560
572
  // Phase 2: Generate agent commands (shared with `update` via command-generator).
561
573
  logger.always(`\n${CYAN}Phase 2:${NC} Generating agent commands`);
562
574
  const commandStagingDir = getGlobalCommandStagingDir({ llmChoice, sinapseHome: SINAPSE_HOME, claudeCommandsDir: CLAUDE_COMMANDS_DIR });
563
- const { writtenAgents, totalAgents } = regenerateAgentCommands({
575
+ const { writtenAgents, canonicalAgents } = regenerateAgentCommands({
564
576
  sinapseHome: SINAPSE_HOME,
565
577
  commandsDir: commandStagingDir,
566
578
  squads,
@@ -570,8 +582,17 @@ function installFatalPhases({ squads, squadsDir, isUpsert, llmChoice, existing,
570
582
  logger.always(` ${GREEN}OK${NC} ${writtenAgents.size} total command files`);
571
583
 
572
584
  // Phase 2b: Install global agents based on LLM choice
573
- const globalAdapters = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir: commandStagingDir });
574
- const activeAgentCount = assertProviderAdapterParity(llmChoice, globalAdapters, totalAgents);
585
+ const globalAdapters = deliverGlobalProviderAdapters({
586
+ llmChoice,
587
+ home: HOME,
588
+ commandsDir: commandStagingDir,
589
+ reactBitsSkillPath: path.join(SINAPSE_HOME, REACT_BITS_SKILL_RELATIVE_PATH),
590
+ });
591
+ const activeAgentCount = assertProviderAdapterParity(
592
+ llmChoice,
593
+ globalAdapters,
594
+ canonicalAgents,
595
+ );
575
596
  const installedAgentFilenames = new Set([...globalAdapters.claude, ...globalAdapters.codex]);
576
597
  const installedIdes = [];
577
598
  if (globalAdapters.claude.length) installedIdes.push('claude-code');
@@ -170,7 +170,7 @@ function removeOrqxAgentsFrom(dir) {
170
170
  return removeInstalledAgentsFrom(dir);
171
171
  }
172
172
 
173
- const MANAGED_GLOBAL_SKILL_IDS = ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent'];
173
+ const MANAGED_GLOBAL_SKILL_IDS = ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent', 'react-bits-frontend'];
174
174
  const MANAGED_GLOBAL_SKILL_MARKER = 'SINAPSE-MANAGED:global-skill';
175
175
 
176
176
  function removeManagedGlobalSkills(home = HOME, options = {}) {
@@ -36,6 +36,10 @@ const {
36
36
  } = require('./uninstall');
37
37
  const { regenerateAgentCommands } = require('../lib/command-generator');
38
38
  const { deliverGlobalProviderAdapters, getGlobalCommandStagingDir } = require('../lib/global-provider-adapters');
39
+ const {
40
+ copyReactBitsCapabilitySync,
41
+ REACT_BITS_SKILL_RELATIVE_PATH,
42
+ } = require('../../packages/installer/src/installer/react-bits-corpus');
39
43
  const { assertProviderAdapterParity } = require('../lib/provider-parity');
40
44
  const { execSync } = require('child_process');
41
45
 
@@ -164,13 +168,16 @@ async function cmdUpdateGlobal() {
164
168
  logger.always(` ${GREEN}OK${NC} core development`);
165
169
  }
166
170
 
171
+ const reactBits = copyReactBitsCapabilitySync(ROOT, SINAPSE_HOME);
172
+ if (reactBits.files.length) logger.always(` ${GREEN}OK${NC} React Bits corpus (${reactBits.files.length} files)`);
173
+
167
174
  // Phase 2: Regenerate commands — shared with `install` so `update` produces the
168
175
  // SAME complete set (every specialist command + the rich master Imperator stubs),
169
176
  // not just the `-orqx` subset it used to write.
170
177
  logger.always(`\n${CYAN}Phase 2:${NC} Regenerating commands`);
171
178
  const commandStagingDir = getGlobalCommandStagingDir({ llmChoice, sinapseHome: SINAPSE_HOME, claudeCommandsDir: CLAUDE_COMMANDS_DIR });
172
179
  rmDirSync(commandStagingDir);
173
- const { writtenAgents, totalAgents } = regenerateAgentCommands({
180
+ const { writtenAgents, canonicalAgents, totalAgents } = regenerateAgentCommands({
174
181
  sinapseHome: SINAPSE_HOME,
175
182
  commandsDir: commandStagingDir,
176
183
  squads,
@@ -180,8 +187,17 @@ async function cmdUpdateGlobal() {
180
187
  logger.always(` ${GREEN}OK${NC} ${writtenAgents.size} command files (${totalAgents} agents total)`);
181
188
 
182
189
  // Phase 2b: Install global agents based on LLM choice
183
- const globalAdapters = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir: commandStagingDir });
184
- const activeAgentCount = assertProviderAdapterParity(llmChoice, globalAdapters, totalAgents);
190
+ const globalAdapters = deliverGlobalProviderAdapters({
191
+ llmChoice,
192
+ home: HOME,
193
+ commandsDir: commandStagingDir,
194
+ reactBitsSkillPath: path.join(SINAPSE_HOME, REACT_BITS_SKILL_RELATIVE_PATH),
195
+ });
196
+ const activeAgentCount = assertProviderAdapterParity(
197
+ llmChoice,
198
+ globalAdapters,
199
+ canonicalAgents,
200
+ );
185
201
  if (globalAdapters.claude.length) logger.always(` ${GREEN}OK${NC} Claude Code global agents (${globalAdapters.claude.length})`);
186
202
  if (globalAdapters.codex.length) logger.always(` ${GREEN}OK${NC} Codex global agents (${globalAdapters.codex.length} TOML, ${globalAdapters.skills.length} skills)`);
187
203
 
@@ -20,6 +20,7 @@ const { toForwardSlash, atomicWriteFileSync } = require('./fs-utils');
20
20
  // sync-counts uses so the master-stub headline can never drift from README/persona
21
21
  // (Constitution Article VII). Fails open to 0 if the dir is unavailable.
22
22
  const CORE_AGENTS_DIR = path.join(ROOT, '.sinapse-ai', 'development', 'agents');
23
+ const { MASTER_ALIAS_ENTRY_POINTS } = require('./provider-contract');
23
24
 
24
25
  function countCoreAgents(dir = CORE_AGENTS_DIR) {
25
26
  try {
@@ -207,7 +208,7 @@ If the task requires expertise outside this squad:
207
208
  * @param {string} [deps.commandsDir] - Target commands dir.
208
209
  * @param {Array} deps.squads - Squad descriptors ({ name, agents, ... }).
209
210
  * @param {string} [deps.sinapseMasterDest] - Path to the installed sinapse/ master squad.
210
- * @returns {{ writtenAgents: Set<string>, totalAgents: number }}
211
+ * @returns {{ writtenAgents: Set<string>, canonicalAgents: Array<{ id: string, file: string, origin: string }>, aliasEntryPoints: string[], totalAgents: number }}
211
212
  */
212
213
  function regenerateAgentCommands(deps = {}) {
213
214
  const {
@@ -222,20 +223,21 @@ function regenerateAgentCommands(deps = {}) {
222
223
  throw new Error('regenerateAgentCommands: `squads` array is required');
223
224
  }
224
225
 
225
- fs.mkdirSync(commandsDir, { recursive: true });
226
-
227
- // Clear old commands (dir may not exist on a fresh install — guarded).
228
- try {
229
- for (const f of fs.readdirSync(commandsDir)) {
230
- fs.unlinkSync(path.join(commandsDir, f));
231
- }
232
- } catch { /* dir may not exist yet */ }
233
-
234
226
  const sinapseBase = toForwardSlash(sinapseHome);
235
227
  const writtenAgents = new Set();
228
+ const catalogById = new Map();
229
+ const definitions = [];
230
+
231
+ function registerDefinition(definition) {
232
+ const previous = catalogById.get(definition.id);
233
+ if (previous) {
234
+ throw new Error(`Duplicate canonical agent ID "${definition.id}" from ${previous.origin} and ${definition.origin}`);
235
+ }
236
+ catalogById.set(definition.id, definition);
237
+ definitions.push(definition);
238
+ }
236
239
 
237
- // 1. A command/subagent for EVERY agent (not only -orqx), so every specialist
238
- // is invokable via @agent and /SINAPSE:agents:agent.
240
+ // Inventory every canonical definition before clearing or writing commands.
239
241
  for (const squad of squads) {
240
242
  const squadPath = `${sinapseBase}/${squad.name}`;
241
243
  const agentsDir = path.join(sinapseHome, squad.name, 'agents');
@@ -244,10 +246,14 @@ function regenerateAgentCommands(deps = {}) {
244
246
  const squadAgents = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
245
247
  for (const file of squadAgents) {
246
248
  const agentId = file.replace('.md', '');
247
- const meta = extractAgentMeta(path.join(agentsDir, file));
248
- const cmdContent = generateCommandMd(agentId, meta.name, meta.icon, squad.name, squadPath, file);
249
- atomicWriteFileSync(path.join(commandsDir, `${agentId}.md`), cmdContent);
250
- writtenAgents.add(file);
249
+ registerDefinition({
250
+ id: agentId,
251
+ file,
252
+ origin: path.join(agentsDir, file),
253
+ squadName: squad.name,
254
+ squadPath,
255
+ hasManifest: true,
256
+ });
251
257
  }
252
258
  }
253
259
 
@@ -257,29 +263,37 @@ function regenerateAgentCommands(deps = {}) {
257
263
  if (fs.existsSync(coreAgentsDir)) {
258
264
  const coreBase = toForwardSlash(coreDevelopmentDest);
259
265
  for (const file of fs.readdirSync(coreAgentsDir).filter(f => f.endsWith('.md'))) {
260
- if (file === 'README.md' || file === 'MEMORY.md' || file.startsWith('_') || writtenAgents.has(file)) continue;
266
+ if (file === 'README.md' || file === 'MEMORY.md' || file.startsWith('_')) continue;
261
267
  const agentId = file.replace('.md', '');
262
- const meta = extractAgentMeta(path.join(coreAgentsDir, file));
263
- const cmdContent = generateCommandMd(agentId, meta.name, meta.icon, 'core', coreBase, file, { hasManifest: false });
264
- atomicWriteFileSync(path.join(commandsDir, `${agentId}.md`), cmdContent);
265
- writtenAgents.add(file);
268
+ registerDefinition({
269
+ id: agentId,
270
+ file,
271
+ origin: path.join(coreAgentsDir, file),
272
+ squadName: 'core',
273
+ squadPath: coreBase,
274
+ hasManifest: false,
275
+ });
266
276
  }
267
277
  }
268
278
 
269
- // 2. Commands for sinapse/ master squad agents.
270
- if (fs.existsSync(sinapseMasterDest)) {
271
- const masterAgentsDir = path.join(sinapseMasterDest, 'agents');
272
- if (fs.existsSync(masterAgentsDir)) {
273
- for (const file of fs.readdirSync(masterAgentsDir).filter(f => f.endsWith('.md'))) {
274
- if (writtenAgents.has(file)) continue;
275
- const agentId = file.replace('.md', '');
276
- const meta = extractAgentMeta(path.join(masterAgentsDir, file));
277
- const squadPath = `${sinapseBase}/sinapse`;
278
- const cmdContent = generateCommandMd(agentId, meta.name, meta.icon, 'sinapse', squadPath, file);
279
- atomicWriteFileSync(path.join(commandsDir, `${agentId}.md`), cmdContent);
280
- writtenAgents.add(file);
281
- }
282
- }
279
+ fs.mkdirSync(commandsDir, { recursive: true });
280
+ for (const file of fs.readdirSync(commandsDir)) {
281
+ fs.unlinkSync(path.join(commandsDir, file));
282
+ }
283
+
284
+ for (const definition of definitions) {
285
+ const meta = extractAgentMeta(definition.origin);
286
+ const cmdContent = generateCommandMd(
287
+ definition.id,
288
+ meta.name,
289
+ meta.icon,
290
+ definition.squadName,
291
+ definition.squadPath,
292
+ definition.file,
293
+ { hasManifest: definition.hasManifest },
294
+ );
295
+ atomicWriteFileSync(path.join(commandsDir, definition.file), cmdContent);
296
+ writtenAgents.add(definition.file);
283
297
  }
284
298
 
285
299
  // 3. Master entry points (@sinapse, @snps, @sinapse-orqx, @snps-orqx) → rich
@@ -301,7 +315,16 @@ function regenerateAgentCommands(deps = {}) {
301
315
  }
302
316
  }
303
317
 
304
- return { writtenAgents, totalAgents: writtenAgents.size };
318
+ const aliasEntryPoints = [...MASTER_ALIAS_ENTRY_POINTS]
319
+ .filter((id) => writtenAgents.has(`${id}.md`));
320
+ const canonicalAgents = definitions.map(({ id, file, origin }) => ({ id, file, origin }));
321
+
322
+ return {
323
+ writtenAgents,
324
+ canonicalAgents,
325
+ aliasEntryPoints,
326
+ totalAgents: canonicalAgents.length,
327
+ };
305
328
  }
306
329
 
307
330
  module.exports = {
@@ -309,4 +332,5 @@ module.exports = {
309
332
  generateMasterStub,
310
333
  regenerateAgentCommands,
311
334
  countCoreAgents,
335
+ MASTER_ALIAS_ENTRY_POINTS,
312
336
  };