aiox-core 5.0.7 → 5.0.8

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 (191) hide show
  1. package/.aiox-core/cli/commands/pro/buyer.js +379 -0
  2. package/.aiox-core/cli/commands/pro/index.js +191 -52
  3. package/.aiox-core/cli/commands/validate/index.js +2 -0
  4. package/.aiox-core/core/code-intel/helpers/dev-helper.js +1 -1
  5. package/.aiox-core/core/code-intel/helpers/devops-helper.js +0 -1
  6. package/.aiox-core/core/code-intel/helpers/planning-helper.js +1 -1
  7. package/.aiox-core/core/code-intel/helpers/qa-helper.js +2 -2
  8. package/.aiox-core/core/config/schemas/framework-config.schema.json +1 -0
  9. package/.aiox-core/core/config/template-overrides.js +1 -1
  10. package/.aiox-core/core/doctor/checks/ide-sync.js +81 -25
  11. package/.aiox-core/core/doctor/checks/rules-files.js +0 -1
  12. package/.aiox-core/core/doctor/checks/skills-count.js +83 -15
  13. package/.aiox-core/core/graph-dashboard/cli.js +1 -2
  14. package/.aiox-core/core/graph-dashboard/data-sources/code-intel-source.js +1 -1
  15. package/.aiox-core/core/ids/layer-classifier.js +1 -1
  16. package/.aiox-core/core/pro/pro-updater.js +578 -0
  17. package/.aiox-core/core/synapse/context/context-tracker.js +107 -9
  18. package/.aiox-core/core/synapse/layers/layer-processor.js +1 -1
  19. package/.aiox-core/core-config.yaml +15 -1
  20. package/.aiox-core/data/capability-detection.js +15 -15
  21. package/.aiox-core/data/entity-registry.yaml +18 -2
  22. package/.aiox-core/data/registry-update-log.jsonl +5 -0
  23. package/.aiox-core/data/tok3-token-comparison.js +0 -4
  24. package/.aiox-core/data/tool-search-validation.js +1 -1
  25. package/.aiox-core/development/agents/aiox-master.md +44 -6
  26. package/.aiox-core/development/agents/data-engineer.md +4 -4
  27. package/.aiox-core/development/agents/devops.md +52 -2
  28. package/.aiox-core/development/agents/po.md +1 -1
  29. package/.aiox-core/development/agents/qa.md +5 -11
  30. package/.aiox-core/development/agents/sm.md +3 -3
  31. package/.aiox-core/development/agents/ux-design-expert.md +1 -1
  32. package/.aiox-core/development/scripts/unified-activation-pipeline.js +29 -3
  33. package/.aiox-core/development/tasks/dev-develop-story.md +46 -7
  34. package/.aiox-core/development/tasks/devops-pro-access-grant.md +93 -0
  35. package/.aiox-core/development/tasks/devops-pro-activate.md +42 -0
  36. package/.aiox-core/development/tasks/devops-pro-check-access.md +34 -0
  37. package/.aiox-core/development/tasks/devops-pro-request-reset.md +34 -0
  38. package/.aiox-core/development/tasks/devops-pro-resend-verification.md +32 -0
  39. package/.aiox-core/development/tasks/devops-pro-reset-password.md +36 -0
  40. package/.aiox-core/development/tasks/devops-pro-validate-login.md +36 -0
  41. package/.aiox-core/development/tasks/devops-pro-verify-status.md +33 -0
  42. package/.aiox-core/development/tasks/qa-gate.md +54 -4
  43. package/.aiox-core/development/tasks/validate-next-story.md +39 -2
  44. package/.aiox-core/framework-config.yaml +1 -0
  45. package/.aiox-core/infrastructure/scripts/codex-skills-sync/README.md +69 -0
  46. package/.aiox-core/infrastructure/scripts/codex-skills-sync/bootstrap.js +727 -0
  47. package/.aiox-core/infrastructure/scripts/codex-skills-sync/index.js +10 -0
  48. package/.aiox-core/infrastructure/scripts/codex-skills-sync/validate.js +65 -4
  49. package/.aiox-core/infrastructure/scripts/generate-settings-json.js +29 -4
  50. package/.aiox-core/infrastructure/scripts/ide-sync/agent-parser.js +4 -0
  51. package/.aiox-core/infrastructure/scripts/ide-sync/index.js +67 -7
  52. package/.aiox-core/infrastructure/scripts/ide-sync/transformers/claude-code.js +145 -3
  53. package/.aiox-core/infrastructure/scripts/repair-agent-references.js +263 -0
  54. package/.aiox-core/infrastructure/scripts/validate-claude-integration.js +60 -8
  55. package/.aiox-core/infrastructure/scripts/validate-paths.js +13 -0
  56. package/.aiox-core/install-manifest.yaml +134 -82
  57. package/.aiox-core/utils/filters/index.js +2 -1
  58. package/.claude/commands/AIOX/agents/aiox-master.md +21 -0
  59. package/.claude/commands/AIOX/agents/analyst.md +21 -0
  60. package/.claude/commands/AIOX/agents/architect.md +21 -0
  61. package/.claude/commands/AIOX/agents/data-engineer.md +21 -0
  62. package/.claude/commands/AIOX/agents/dev.md +21 -0
  63. package/.claude/commands/AIOX/agents/devops.md +21 -0
  64. package/.claude/commands/AIOX/agents/pm.md +21 -0
  65. package/.claude/commands/AIOX/agents/po.md +21 -0
  66. package/.claude/commands/AIOX/agents/qa.md +21 -0
  67. package/.claude/commands/AIOX/agents/sm.md +21 -0
  68. package/.claude/commands/AIOX/agents/squad-creator.md +21 -0
  69. package/.claude/commands/AIOX/agents/ux-design-expert.md +21 -0
  70. package/.claude/commands/AIOX/scripts/agent-config-loader.js +624 -0
  71. package/.claude/commands/AIOX/scripts/generate-greeting.js +160 -0
  72. package/.claude/commands/AIOX/scripts/greeting-builder.js +866 -0
  73. package/.claude/commands/AIOX/scripts/session-context-loader.js +286 -0
  74. package/.claude/commands/AIOX/stories/story-6.1.4.md +1404 -0
  75. package/.claude/commands/cohort-squad/agents/cohort-manager.md +156 -0
  76. package/.claude/commands/design-system/agents/brad-frost.md +1097 -0
  77. package/.claude/commands/design-system/agents/dan-mall.md +857 -0
  78. package/.claude/commands/design-system/agents/dave-malouf.md +2272 -0
  79. package/.claude/commands/design-system/agents/design-chief.md +102 -0
  80. package/.claude/commands/design-system/agents/nano-banana-generator.md +162 -0
  81. package/.claude/commands/greet.md +101 -0
  82. package/.claude/commands/synapse/manager.md +75 -0
  83. package/.claude/commands/synapse/tasks/add-rule.md +94 -0
  84. package/.claude/commands/synapse/tasks/create-command.md +109 -0
  85. package/.claude/commands/synapse/tasks/create-domain.md +127 -0
  86. package/.claude/commands/synapse/tasks/diagnose-synapse.md +245 -0
  87. package/.claude/commands/synapse/tasks/edit-rule.md +109 -0
  88. package/.claude/commands/synapse/tasks/suggest-domain.md +116 -0
  89. package/.claude/commands/synapse/tasks/toggle-domain.md +83 -0
  90. package/.claude/commands/synapse/templates/domain-template +8 -0
  91. package/.claude/commands/synapse/templates/manifest-entry-template +4 -0
  92. package/.claude/commands/synapse/utils/manifest-parser-reference.md +134 -0
  93. package/.claude/hooks/precompact-session-digest.cjs +2 -2
  94. package/.claude/skills/AIOX/agents/aiox-master/SKILL.md +511 -0
  95. package/.claude/skills/AIOX/agents/analyst/SKILL.md +281 -0
  96. package/.claude/skills/AIOX/agents/architect/SKILL.md +482 -0
  97. package/.claude/skills/AIOX/agents/data-engineer/SKILL.md +503 -0
  98. package/.claude/skills/AIOX/agents/dev/SKILL.md +568 -0
  99. package/.claude/skills/AIOX/agents/devops/SKILL.md +597 -0
  100. package/.claude/skills/AIOX/agents/pm/SKILL.md +385 -0
  101. package/.claude/skills/AIOX/agents/po/SKILL.md +343 -0
  102. package/.claude/skills/AIOX/agents/qa/SKILL.md +451 -0
  103. package/.claude/skills/AIOX/agents/sm/SKILL.md +295 -0
  104. package/.claude/skills/AIOX/agents/squad-creator/SKILL.md +352 -0
  105. package/.claude/skills/AIOX/agents/ux-design-expert/SKILL.md +503 -0
  106. package/.claude/skills/architect-first/SKILL.md +275 -0
  107. package/.claude/skills/architect-first/assets/architecture-template.md +505 -0
  108. package/.claude/skills/architect-first/assets/config-template.yaml +351 -0
  109. package/.claude/skills/architect-first/references/architecture-checklist.md +216 -0
  110. package/.claude/skills/architect-first/references/pre-implementation-checklist.md +119 -0
  111. package/.claude/skills/architect-first/references/stop-rules-guide.md +291 -0
  112. package/.claude/skills/architect-first/references/testing-strategy-guide.md +477 -0
  113. package/.claude/skills/architect-first/scripts/architecture_validator.py +490 -0
  114. package/.claude/skills/architect-first/scripts/check_coupling.py +306 -0
  115. package/.claude/skills/architect-first/scripts/validate_risk_mitigation.py +382 -0
  116. package/.claude/skills/checklist-runner/SKILL.md +113 -0
  117. package/.claude/skills/clone-mind.md +329 -0
  118. package/.claude/skills/coderabbit-review/SKILL.md +106 -0
  119. package/.claude/skills/course-generation-workflow.md +76 -0
  120. package/.claude/skills/enhance-workflow.md +466 -0
  121. package/.claude/skills/mcp-builder/LICENSE.txt +202 -0
  122. package/.claude/skills/mcp-builder/SKILL.md +328 -0
  123. package/.claude/skills/mcp-builder/reference/evaluation.md +602 -0
  124. package/.claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  125. package/.claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  126. package/.claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  127. package/.claude/skills/mcp-builder/scripts/connections.py +151 -0
  128. package/.claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  129. package/.claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  130. package/.claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  131. package/.claude/skills/ralph.md +181 -0
  132. package/.claude/skills/skill-creator/LICENSE.txt +202 -0
  133. package/.claude/skills/skill-creator/SKILL.md +209 -0
  134. package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  135. package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  136. package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  137. package/.claude/skills/squad.md +301 -0
  138. package/.claude/skills/synapse/SKILL.md +132 -0
  139. package/.claude/skills/synapse/assets/README.md +50 -0
  140. package/.claude/skills/synapse/references/brackets.md +100 -0
  141. package/.claude/skills/synapse/references/commands.md +118 -0
  142. package/.claude/skills/synapse/references/domains.md +126 -0
  143. package/.claude/skills/synapse/references/layers.md +186 -0
  144. package/.claude/skills/synapse/references/manifest.md +142 -0
  145. package/.claude/skills/tech-search/SKILL.md +431 -0
  146. package/.claude/skills/tech-search/prompts/page-extract.md +133 -0
  147. package/README.en.md +2 -2
  148. package/README.md +8 -2
  149. package/bin/aiox.js +55 -4
  150. package/bin/utils/framework-guard.js +4 -2
  151. package/bin/utils/pro-detector.js +119 -28
  152. package/bin/utils/validate-publish.js +6 -6
  153. package/docs/aiox-agent-flows/devops-system.md +18 -0
  154. package/docs/aiox-workflows/README.md +1 -0
  155. package/docs/aiox-workflows/pro-access-grant-workflow.md +218 -0
  156. package/docs/guides/pro/access-grant-ops-playbook.md +370 -0
  157. package/docs/guides/pro/install-gate-setup.md +12 -6
  158. package/docs/guides/pro/squad-creator-handoff-pro-access-ops.md +134 -0
  159. package/docs/guides/supabase-ops-handoff.md +768 -0
  160. package/package.json +12 -1
  161. package/packages/aiox-pro-cli/bin/aiox-pro.js +33 -12
  162. package/packages/installer/src/config/configure-environment.js +118 -50
  163. package/packages/installer/src/installer/aiox-core-installer.js +124 -27
  164. package/packages/installer/src/installer/brownfield-upgrader.js +66 -9
  165. package/packages/installer/src/installer/dependency-installer.js +4 -0
  166. package/packages/installer/src/pro/pro-scaffolder.js +5 -5
  167. package/packages/installer/src/updater/index.js +151 -10
  168. package/packages/installer/src/wizard/ide-config-generator.js +73 -7
  169. package/packages/installer/src/wizard/index.js +119 -31
  170. package/packages/installer/src/wizard/pro-setup.js +118 -47
  171. package/packages/installer/src/wizard/validation/validators/dependency-validator.js +32 -25
  172. package/packages/installer/src/wizard/validation/validators/file-structure-validator.js +26 -0
  173. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +84 -1
  174. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +1 -1
  175. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +85 -19
  176. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +4 -4
  177. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +5 -5
  178. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +4 -4
  179. package/packages/installer/tests/unit/merger/yaml-merger.test.js +11 -11
  180. package/pro/README.md +12 -1
  181. package/pro/license/index.js +3 -11
  182. package/pro/license/license-api.js +25 -0
  183. package/pro/license/license-cache.js +135 -31
  184. package/pro/license/license-crypto.js +59 -3
  185. package/pro/package.json +5 -4
  186. package/pro/squads/README.md +16 -16
  187. package/pro/squads/index.js +1 -1
  188. package/scripts/e2e/installed-skills-smoke.js +264 -0
  189. package/scripts/package-synapse.js +3 -3
  190. package/scripts/validate-package-completeness.js +8 -11
  191. package/.aiox-core/lib/build.json +0 -1
@@ -7,7 +7,9 @@ const os = require('os');
7
7
  const {
8
8
  copySkillFiles,
9
9
  copyExtraCommandFiles,
10
+ copyClaudeHooksFolder,
10
11
  createClaudeSettingsLocal,
12
+ shouldCopyProHooks,
11
13
  HOOK_EVENT_MAP,
12
14
  DEFAULT_HOOK_CONFIG,
13
15
  } = require('../../../../../packages/installer/src/wizard/ide-config-generator');
@@ -20,6 +22,15 @@ function cleanup(dir) {
20
22
  fs.rmSync(dir, { recursive: true, force: true });
21
23
  }
22
24
 
25
+ function expectedAvailableHooks(...fileNames) {
26
+ const hooksDir = path.join(__dirname, '../../../../../.claude/hooks');
27
+ if (!fs.existsSync(hooksDir)) {
28
+ throw new Error(`Expected Claude hooks source directory at ${hooksDir}`);
29
+ }
30
+ const available = new Set(fs.readdirSync(hooksDir));
31
+ return fileNames.filter(file => available.has(file)).sort();
32
+ }
33
+
23
34
  describe('artifact-copy-pipeline (Story INS-4.3)', () => {
24
35
  describe('copySkillFiles', () => {
25
36
  test('copies skill directories from source to target via real function', async () => {
@@ -94,7 +105,7 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => {
94
105
  expect(result.count).toBe(1);
95
106
  const content = fs.readFileSync(
96
107
  path.join(targetRoot, '.claude', 'skills', 'test-skill', 'SKILL.md'),
97
- 'utf8'
108
+ 'utf8',
98
109
  );
99
110
  expect(content).toBe('# v2');
100
111
  } finally {
@@ -168,6 +179,78 @@ describe('artifact-copy-pipeline (Story INS-4.3)', () => {
168
179
  });
169
180
  });
170
181
 
182
+ describe('copyClaudeHooksFolder tier selection (Issue #544)', () => {
183
+ test('free tier copies only free hooks and does not register PreCompact', async () => {
184
+ const targetRoot = createTempDir();
185
+
186
+ try {
187
+ const copied = await copyClaudeHooksFolder(targetRoot, { tier: 'free' });
188
+ const fileNames = copied.map(file => path.basename(file)).sort();
189
+
190
+ expect(fileNames).toEqual(expectedAvailableHooks(
191
+ 'README.md',
192
+ 'code-intel-pretool.cjs',
193
+ 'synapse-engine.cjs',
194
+ ));
195
+
196
+ const settingsPath = await createClaudeSettingsLocal(targetRoot);
197
+ expect(settingsPath).toEqual(expect.any(String));
198
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
199
+
200
+ expect(settings.hooks.UserPromptSubmit).toHaveLength(1);
201
+ if (fileNames.includes('code-intel-pretool.cjs')) {
202
+ expect(settings.hooks.PreToolUse).toHaveLength(1);
203
+ } else {
204
+ expect(settings.hooks.PreToolUse).toBeUndefined();
205
+ }
206
+ expect(settings.hooks.PreCompact).toBeUndefined();
207
+ } finally {
208
+ cleanup(targetRoot);
209
+ }
210
+ });
211
+
212
+ test('pro tier copies free hooks plus PreCompact session digest hook', async () => {
213
+ const targetRoot = createTempDir();
214
+
215
+ try {
216
+ const copied = await copyClaudeHooksFolder(targetRoot, { tier: 'pro' });
217
+ const fileNames = copied.map(file => path.basename(file)).sort();
218
+
219
+ expect(fileNames).toEqual(expectedAvailableHooks(
220
+ 'README.md',
221
+ 'code-intel-pretool.cjs',
222
+ 'precompact-session-digest.cjs',
223
+ 'synapse-engine.cjs',
224
+ ));
225
+
226
+ const settingsPath = await createClaudeSettingsLocal(targetRoot);
227
+ expect(settingsPath).toEqual(expect.any(String));
228
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
229
+
230
+ expect(settings.hooks.UserPromptSubmit).toHaveLength(1);
231
+ if (fileNames.includes('code-intel-pretool.cjs')) {
232
+ expect(settings.hooks.PreToolUse).toHaveLength(1);
233
+ } else {
234
+ expect(settings.hooks.PreToolUse).toBeUndefined();
235
+ }
236
+ expect(settings.hooks.PreCompact).toHaveLength(1);
237
+ } finally {
238
+ cleanup(targetRoot);
239
+ }
240
+ });
241
+
242
+ test('explicit wizard tier controls Pro hook selection', () => {
243
+ expect(shouldCopyProHooks({ tier: 'pro' })).toBe(true);
244
+ expect(shouldCopyProHooks({ tier: 'free' })).toBe(false);
245
+ expect(shouldCopyProHooks({ tier: 'community' })).toBe(false);
246
+ expect(shouldCopyProHooks({ tier: 'core' })).toBe(false);
247
+ expect(shouldCopyProHooks({ proTier: 'pro' })).toBe(true);
248
+ expect(shouldCopyProHooks({ tier: 'free', proTier: 'pro' })).toBe(false);
249
+ expect(shouldCopyProHooks({ pro: { enabled: true } })).toBe(true);
250
+ expect(shouldCopyProHooks({ pro: { enabled: false } })).toBe(false);
251
+ });
252
+ });
253
+
171
254
  describe('HOOK_EVENT_MAP (Story MIS-3.1)', () => {
172
255
  test('maps synapse-engine.cjs to UserPromptSubmit', () => {
173
256
  const config = HOOK_EVENT_MAP['synapse-engine.cjs'];
@@ -10,7 +10,7 @@ const {
10
10
 
11
11
  const TEMPLATE_PATH = path.join(
12
12
  __dirname, '..', '..', '..', '..', '..', '.aiox-core',
13
- 'product', 'templates', 'ide-rules', 'claude-rules.md'
13
+ 'product', 'templates', 'ide-rules', 'claude-rules.md',
14
14
  );
15
15
 
16
16
  describe('CLAUDE.md Template v5 (Story INS-4.4)', () => {
@@ -35,8 +35,20 @@ const mockContext = {
35
35
  options: { fix: false, json: false, dryRun: false, quiet: false },
36
36
  };
37
37
 
38
+ const dirEntry = (name) => ({
39
+ name,
40
+ isDirectory: () => true,
41
+ isFile: () => false,
42
+ });
43
+
44
+ const fileEntry = (name) => ({
45
+ name,
46
+ isDirectory: () => false,
47
+ isFile: () => true,
48
+ });
49
+
38
50
  beforeEach(() => {
39
- jest.clearAllMocks();
51
+ jest.resetAllMocks();
40
52
  });
41
53
 
42
54
  describe('node-version check', () => {
@@ -352,11 +364,16 @@ describe('code-intel check', () => {
352
364
  });
353
365
 
354
366
  describe('ide-sync check', () => {
367
+ const portablePath = (targetPath) => String(targetPath).replace(/\\/g, '/');
368
+
355
369
  it('should PASS when counts match', async () => {
356
370
  fs.existsSync.mockReturnValue(true);
357
371
  fs.readdirSync.mockImplementation((p) => {
358
- if (p.includes('commands')) return ['dev.md', 'qa.md'];
359
- return ['dev.md', 'qa.md'];
372
+ const normalizedPath = portablePath(p);
373
+ if (normalizedPath.includes('.claude/skills')) return [dirEntry('dev'), dirEntry('qa')];
374
+ if (normalizedPath.includes('.claude/commands')) return ['dev.md', 'qa.md'];
375
+ if (normalizedPath.includes('development/agents')) return ['dev.md', 'qa.md'];
376
+ return [];
360
377
  });
361
378
 
362
379
  const result = await ideSyncCheck.run(mockContext);
@@ -366,13 +383,45 @@ describe('ide-sync check', () => {
366
383
  it('should WARN when counts mismatch', async () => {
367
384
  fs.existsSync.mockReturnValue(true);
368
385
  fs.readdirSync.mockImplementation((p) => {
369
- if (p.includes('commands')) return ['dev.md', 'qa.md', 'pm.md'];
370
- return ['dev.md', 'qa.md'];
386
+ const normalizedPath = portablePath(p);
387
+ if (normalizedPath.includes('.claude/skills')) return [dirEntry('dev'), dirEntry('qa')];
388
+ if (normalizedPath.includes('commands')) return ['dev.md', 'qa.md', 'pm.md'];
389
+ if (normalizedPath.includes('development/agents')) return ['dev.md', 'qa.md'];
390
+ return [];
371
391
  });
372
392
 
373
393
  const result = await ideSyncCheck.run(mockContext);
374
394
  expect(result.status).toBe('WARN');
375
395
  });
396
+
397
+ it('should WARN when counts match but agent identities mismatch', async () => {
398
+ fs.existsSync.mockReturnValue(true);
399
+ fs.readdirSync.mockImplementation((p) => {
400
+ const normalizedPath = portablePath(p);
401
+ if (normalizedPath.includes('.claude/skills')) return [dirEntry('dev'), dirEntry('pm')];
402
+ if (normalizedPath.includes('.claude/commands')) return ['dev.md', 'qa.md'];
403
+ if (normalizedPath.includes('development/agents')) return ['dev.md', 'qa.md'];
404
+ return [];
405
+ });
406
+
407
+ const result = await ideSyncCheck.run(mockContext);
408
+ expect(result.status).toBe('WARN');
409
+ expect(result.message).toContain('missing: qa');
410
+ expect(result.message).toContain('extra: pm');
411
+ });
412
+
413
+ it('should FAIL when Claude directories cannot be read', async () => {
414
+ fs.existsSync.mockReturnValue(true);
415
+ fs.readdirSync.mockImplementation((p) => {
416
+ const normalizedPath = portablePath(p);
417
+ if (normalizedPath.includes('development/agents')) return ['dev.md', 'qa.md'];
418
+ throw new Error('permission denied');
419
+ });
420
+
421
+ const result = await ideSyncCheck.run(mockContext);
422
+ expect(result.status).toBe('FAIL');
423
+ expect(result.message).toContain('permission denied');
424
+ });
376
425
  });
377
426
 
378
427
  // === INS-4.8: New checks ===
@@ -380,12 +429,13 @@ describe('ide-sync check', () => {
380
429
  describe('skills-count check', () => {
381
430
  it('should PASS when >=7 skills directories with SKILL.md', async () => {
382
431
  fs.existsSync.mockReturnValue(true);
383
- const dirs = Array.from({ length: 8 }, (_, i) => ({
384
- name: `skill-${i}`,
385
- isDirectory: () => true,
386
- isFile: () => false,
387
- }));
388
- fs.readdirSync.mockReturnValue(dirs);
432
+ fs.readdirSync.mockImplementation((p) => {
433
+ if (p.endsWith(path.join('.claude', 'skills'))) {
434
+ return Array.from({ length: 8 }, (_, i) => dirEntry(`skill-${i}`));
435
+ }
436
+ if (p.includes(`${path.sep}skill-`)) return [fileEntry('SKILL.md')];
437
+ return [];
438
+ });
389
439
 
390
440
  const result = await skillsCountCheck.run(mockContext);
391
441
  expect(result.check).toBe('skills-count');
@@ -399,12 +449,13 @@ describe('skills-count check', () => {
399
449
  if (p.includes('SKILL.md')) return true;
400
450
  return true;
401
451
  });
402
- const dirs = Array.from({ length: 3 }, (_, i) => ({
403
- name: `skill-${i}`,
404
- isDirectory: () => true,
405
- isFile: () => false,
406
- }));
407
- fs.readdirSync.mockReturnValue(dirs);
452
+ fs.readdirSync.mockImplementation((p) => {
453
+ if (p.endsWith(path.join('.claude', 'skills'))) {
454
+ return Array.from({ length: 3 }, (_, i) => dirEntry(`skill-${i}`));
455
+ }
456
+ if (p.includes(`${path.sep}skill-`)) return [fileEntry('SKILL.md')];
457
+ return [];
458
+ });
408
459
 
409
460
  const result = await skillsCountCheck.run(mockContext);
410
461
  expect(result.status).toBe('WARN');
@@ -416,8 +467,10 @@ describe('skills-count check', () => {
416
467
  if (p.includes('SKILL.md')) return false;
417
468
  return true;
418
469
  });
419
- const dirs = [{ name: 'empty', isDirectory: () => true, isFile: () => false }];
420
- fs.readdirSync.mockReturnValue(dirs);
470
+ fs.readdirSync.mockImplementation((p) => {
471
+ if (p.endsWith(path.join('.claude', 'skills'))) return [dirEntry('empty')];
472
+ return [];
473
+ });
421
474
 
422
475
  const result = await skillsCountCheck.run(mockContext);
423
476
  expect(result.status).toBe('FAIL');
@@ -429,6 +482,19 @@ describe('skills-count check', () => {
429
482
  const result = await skillsCountCheck.run(mockContext);
430
483
  expect(result.status).toBe('FAIL');
431
484
  });
485
+
486
+ it('should FAIL when skills directory cannot be read', async () => {
487
+ fs.existsSync.mockReturnValue(true);
488
+ fs.readdirSync.mockImplementation(() => {
489
+ const error = new Error('permission denied');
490
+ error.code = 'EACCES';
491
+ throw error;
492
+ });
493
+
494
+ const result = await skillsCountCheck.run(mockContext);
495
+ expect(result.status).toBe('FAIL');
496
+ expect(result.message).toContain('permission denied');
497
+ });
432
498
  });
433
499
 
434
500
  describe('commands-count check', () => {
@@ -12,17 +12,17 @@ const path = require('path');
12
12
 
13
13
  const WIZARD_PATH = path.join(__dirname, '..', '..', 'src', 'wizard', 'index.js');
14
14
  const POPULATE_SCRIPT = path.join(
15
- __dirname, '..', '..', '..', '..', '.aiox-core', 'development', 'scripts', 'populate-entity-registry.js'
15
+ __dirname, '..', '..', '..', '..', '.aiox-core', 'development', 'scripts', 'populate-entity-registry.js',
16
16
  );
17
17
  const DOCTOR_CHECK = path.join(
18
- __dirname, '..', '..', '..', '..', '.aiox-core', 'core', 'doctor', 'checks', 'entity-registry.js'
18
+ __dirname, '..', '..', '..', '..', '.aiox-core', 'core', 'doctor', 'checks', 'entity-registry.js',
19
19
  );
20
20
  const REGISTRY_PATH = path.join(
21
- __dirname, '..', '..', '..', '..', '.aiox-core', 'data', 'entity-registry.yaml'
21
+ __dirname, '..', '..', '..', '..', '.aiox-core', 'data', 'entity-registry.yaml',
22
22
  );
23
23
  const PRE_PUSH_HOOK = path.join(__dirname, '..', '..', '..', '..', '.husky', 'pre-push');
24
24
  const IDS_PRE_PUSH = path.join(
25
- __dirname, '..', '..', '..', '..', '.aiox-core', 'hooks', 'ids-pre-push.js'
25
+ __dirname, '..', '..', '..', '..', '.aiox-core', 'hooks', 'ids-pre-push.js',
26
26
  );
27
27
 
28
28
  describe('Entity Registry Bootstrap (Story INS-4.6)', () => {
@@ -49,7 +49,7 @@ function createTempProject(boundary, existingSettings) {
49
49
  fs.writeFileSync(
50
50
  path.join(claudeDir, 'settings.json'),
51
51
  JSON.stringify(existingSettings, null, 2) + '\n',
52
- 'utf8'
52
+ 'utf8',
53
53
  );
54
54
  }
55
55
 
@@ -256,7 +256,7 @@ describe('generate-settings-json', () => {
256
256
  protected: ['bin/aiox.js'],
257
257
  exceptions: [],
258
258
  },
259
- { language: 'pt', customSetting: true }
259
+ { language: 'pt', customSetting: true },
260
260
  );
261
261
 
262
262
  try {
@@ -273,14 +273,14 @@ describe('generate-settings-json', () => {
273
273
  }
274
274
  });
275
275
 
276
- test('frameworkProtection false preserves user settings and removes permissions', () => {
276
+ test('frameworkProtection false preserves user settings and existing permissions', () => {
277
277
  const tmpDir = createTempProject(
278
278
  {
279
279
  frameworkProtection: false,
280
280
  protected: ['bin/aiox.js'],
281
281
  exceptions: [],
282
282
  },
283
- { language: 'pt', permissions: { deny: ['old-rule'], allow: [] } }
283
+ { language: 'pt', permissions: { deny: ['old-rule'], allow: [] } },
284
284
  );
285
285
 
286
286
  try {
@@ -289,7 +289,7 @@ describe('generate-settings-json', () => {
289
289
  const parsed = JSON.parse(content);
290
290
 
291
291
  expect(parsed.language).toBe('pt');
292
- expect(parsed.permissions).toBeUndefined();
292
+ expect(parsed.permissions).toEqual({ deny: ['old-rule'], allow: [] });
293
293
  } finally {
294
294
  cleanupTempProject(tmpDir);
295
295
  }
@@ -21,7 +21,7 @@ jest.mock('../../../../../.aiox-core/infrastructure/scripts/ide-sync/index', ()
21
21
  // We need to verify that the wizard source code has the correct integration
22
22
  const fs = require('fs');
23
23
  const WIZARD_PATH = path.join(
24
- __dirname, '..', '..', '..', 'src', 'wizard', 'index.js'
24
+ __dirname, '..', '..', '..', 'src', 'wizard', 'index.js',
25
25
  );
26
26
 
27
27
  describe('IDE Sync Integration (Story INS-4.5)', () => {
@@ -34,7 +34,7 @@ describe('IDE Sync Integration (Story INS-4.5)', () => {
34
34
  describe('AC1: IDE sync called via adapter pattern', () => {
35
35
  test('wizard imports commandSync and commandValidate from ide-sync', () => {
36
36
  expect(wizardSource).toContain(
37
- "const { commandSync, commandValidate } = require('../../../../.aiox-core/infrastructure/scripts/ide-sync/index')"
37
+ "const { commandSync, commandValidate } = require('../../../../.aiox-core/infrastructure/scripts/ide-sync/index')",
38
38
  );
39
39
  });
40
40
 
@@ -61,7 +61,7 @@ describe('IDE Sync Integration (Story INS-4.5)', () => {
61
61
  });
62
62
 
63
63
  test('commandSync called with { quiet: true }', () => {
64
- expect(wizardSource).toContain("await commandSync({ quiet: true })");
64
+ expect(wizardSource).toContain('await commandSync({ quiet: true })');
65
65
  });
66
66
 
67
67
  test('does NOT pass projectRoot or ides as parameters to commandSync', () => {
@@ -78,7 +78,7 @@ describe('IDE Sync Integration (Story INS-4.5)', () => {
78
78
  });
79
79
 
80
80
  test('failure message suggests aiox doctor --fix', () => {
81
- expect(wizardSource).toContain("aiox doctor --fix");
81
+ expect(wizardSource).toContain('aiox doctor --fix');
82
82
  });
83
83
 
84
84
  test('install summary includes sync status on success', () => {
@@ -15,7 +15,7 @@ const path = require('path');
15
15
  const fs = require('fs');
16
16
 
17
17
  const { YamlMerger } = require(path.join(
18
- __dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'yaml-merger.js'
18
+ __dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'yaml-merger.js',
19
19
  ));
20
20
 
21
21
  describe('YamlMerger (Story INS-4.7)', () => {
@@ -54,7 +54,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
54
54
  describe('AC1: Strategy registration', () => {
55
55
  test('.yaml extension registered in strategies/index.js', () => {
56
56
  const { hasMergeStrategy, getMergeStrategy } = require(path.join(
57
- __dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'index.js'
57
+ __dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'index.js',
58
58
  ));
59
59
 
60
60
  expect(hasMergeStrategy('config.yaml')).toBe(true);
@@ -64,7 +64,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
64
64
 
65
65
  test('.yml extension also registered', () => {
66
66
  const { hasMergeStrategy } = require(path.join(
67
- __dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'index.js'
67
+ __dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'index.js',
68
68
  ));
69
69
 
70
70
  expect(hasMergeStrategy('config.yml')).toBe(true);
@@ -72,7 +72,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
72
72
 
73
73
  test('YamlMerger exported from merger/index.js', () => {
74
74
  const mergerModule = require(path.join(
75
- __dirname, '..', '..', '..', 'src', 'merger', 'index.js'
75
+ __dirname, '..', '..', '..', 'src', 'merger', 'index.js',
76
76
  ));
77
77
 
78
78
  expect(mergerModule.YamlMerger).toBeDefined();
@@ -91,7 +91,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
91
91
  expect(result.stats.added).toBeGreaterThanOrEqual(1);
92
92
 
93
93
  const addedChange = result.changes.find(
94
- c => c.type === 'added' && c.identifier === 'newFeature'
94
+ c => c.type === 'added' && c.identifier === 'newFeature',
95
95
  );
96
96
  expect(addedChange).toBeDefined();
97
97
  });
@@ -107,7 +107,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
107
107
  expect(result.stats.preserved).toBeGreaterThanOrEqual(1);
108
108
 
109
109
  const preservedChange = result.changes.find(
110
- c => c.type === 'preserved' && c.identifier === 'key'
110
+ c => c.type === 'preserved' && c.identifier === 'key',
111
111
  );
112
112
  expect(preservedChange).toBeDefined();
113
113
  });
@@ -123,7 +123,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
123
123
  expect(result.stats.conflicts).toBeGreaterThanOrEqual(1);
124
124
 
125
125
  const conflictChange = result.changes.find(
126
- c => c.type === 'conflict' && c.identifier === 'setting'
126
+ c => c.type === 'conflict' && c.identifier === 'setting',
127
127
  );
128
128
  expect(conflictChange).toBeDefined();
129
129
  expect(conflictChange.reason).toContain('Keeping user value');
@@ -139,7 +139,7 @@ describe('YamlMerger (Story INS-4.7)', () => {
139
139
  expect(merged.legacyKey).toBe('old-value');
140
140
 
141
141
  const deprecatedChange = result.changes.find(
142
- c => c.type === 'conflict' && c.identifier === 'legacyKey'
142
+ c => c.type === 'conflict' && c.identifier === 'legacyKey',
143
143
  );
144
144
  expect(deprecatedChange).toBeDefined();
145
145
  expect(deprecatedChange.reason).toContain('Deprecated');
@@ -294,7 +294,7 @@ describe('Brownfield Upgrader Integration (Story INS-4.7)', () => {
294
294
  test('brownfield-upgrader imports YamlMerger', () => {
295
295
  const upgraderSource = fs.readFileSync(
296
296
  path.join(__dirname, '..', '..', '..', 'src', 'installer', 'brownfield-upgrader.js'),
297
- 'utf8'
297
+ 'utf8',
298
298
  );
299
299
  expect(upgraderSource).toContain('YamlMerger');
300
300
  expect(upgraderSource).toContain('yaml-merger.js');
@@ -303,7 +303,7 @@ describe('Brownfield Upgrader Integration (Story INS-4.7)', () => {
303
303
  test('upgrader has core-config.yaml merge exception in userModifiedFiles loop', () => {
304
304
  const upgraderSource = fs.readFileSync(
305
305
  path.join(__dirname, '..', '..', '..', 'src', 'installer', 'brownfield-upgrader.js'),
306
- 'utf8'
306
+ 'utf8',
307
307
  );
308
308
  expect(upgraderSource).toContain("file.path.endsWith('core-config.yaml')");
309
309
  expect(upgraderSource).toContain('merger.merge(sourceContent, targetContent)');
@@ -313,7 +313,7 @@ describe('Brownfield Upgrader Integration (Story INS-4.7)', () => {
313
313
  test('upgrader still skips non-yaml user-modified files', () => {
314
314
  const upgraderSource = fs.readFileSync(
315
315
  path.join(__dirname, '..', '..', '..', 'src', 'installer', 'brownfield-upgrader.js'),
316
- 'utf8'
316
+ 'utf8',
317
317
  );
318
318
  expect(upgraderSource).toContain('User modified - preserving local changes');
319
319
  });
package/pro/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Premium features for [Synkra AIOX](https://github.com/SynkraAI/aiox-core).
4
4
 
5
+ > **New npm namespace:** AIOX Pro is published as `@aiox-squads/pro`. This is the canonical successor to the unpublished `@aiox-fullstack/pro` target and the legacy `@aios-fullstack/pro` package.
6
+
5
7
  ## Overview
6
8
 
7
9
  AIOX Pro extends the open-source AIOX framework with premium capabilities:
@@ -16,6 +18,9 @@ AIOX Pro extends the open-source AIOX framework with premium capabilities:
16
18
  AIOX Pro requires an active license and is distributed via npm.
17
19
 
18
20
  ```bash
21
+ # Package install
22
+ npm install @aiox-squads/pro
23
+
19
24
  # Install
20
25
  npx aiox-pro install
21
26
 
@@ -43,6 +48,12 @@ aiox pro activate --key PRO-XXXX-XXXX-XXXX-XXXX
43
48
  aiox pro status
44
49
  ```
45
50
 
51
+ ## Version 0.4.0 - Stable License Machine ID
52
+
53
+ AIOX Pro now derives `machineId` from the native OS machine UUID instead of hostname, CPU, and MAC address. This prevents Wi-Fi MAC rotation, VPN changes, and network interface ordering from consuming extra license seats on the same computer.
54
+
55
+ Existing encrypted license caches are migrated automatically on next read: the client opens the legacy cache key, re-encrypts with the new native machine ID key, and keeps a 90-day legacy fallback window for already activated installations.
56
+
46
57
  ## Structure
47
58
 
48
59
  ```
@@ -58,7 +69,7 @@ aiox-pro/
58
69
  ## Requirements
59
70
 
60
71
  - Node.js >= 18
61
- - aiox-core >= 3.12.0
72
+ - aiox-core >= 5.0.8
62
73
  - Active AIOX Pro license
63
74
 
64
75
  ## License
@@ -5,7 +5,7 @@
5
5
  * and feature gating in AIOX Pro.
6
6
  *
7
7
  * Usage:
8
- * const { featureGate, licenseApi } = require('@aiox-fullstack/pro/license');
8
+ * const { featureGate, licenseApi } = require('@aiox-squads/pro/license');
9
9
  *
10
10
  * // Check feature availability
11
11
  * if (featureGate.isAvailable('pro.squads.premium')) {
@@ -48,18 +48,10 @@ const {
48
48
  const { LicenseApiClient, licenseApi } = require('./license-api');
49
49
 
50
50
  // Crypto utilities
51
- const {
52
- generateMachineId,
53
- maskKey,
54
- validateKeyFormat,
55
- } = require('./license-crypto');
51
+ const { generateMachineId, maskKey, validateKeyFormat } = require('./license-crypto');
56
52
 
57
53
  // Error classes
58
- const {
59
- ProFeatureError,
60
- LicenseActivationError,
61
- LicenseValidationError,
62
- } = require('./errors');
54
+ const { ProFeatureError, LicenseActivationError, LicenseValidationError } = require('./errors');
63
55
 
64
56
  // Graceful degradation utilities
65
57
  const {
@@ -572,6 +572,31 @@ class LicenseApiClient {
572
572
  }
573
573
  }
574
574
 
575
+ // ────────────────────────────────────────────────────────────────
576
+ // Buyer operator methods (Story 123.8 — Cohort CLI migration)
577
+ // ────────────────────────────────────────────────────────────────
578
+
579
+ /**
580
+ * Validate if an email corresponds to an AIOX Pro buyer.
581
+ *
582
+ * Thin wrapper over checkEmail() with a narrower contract intended for
583
+ * CLI buyer-admin operations. Returns exactly { email, isBuyer, hasAccount }.
584
+ *
585
+ * Public endpoint (server-side rate-limited). No admin auth required.
586
+ *
587
+ * @param {string} email - Buyer email to validate
588
+ * @returns {Promise<{email: string, isBuyer: boolean, hasAccount: boolean}>}
589
+ * @throws {AuthError} On rate-limit or network failure
590
+ */
591
+ async validateBuyer(email) {
592
+ const result = await this.checkEmail(email);
593
+ return {
594
+ email: result.email,
595
+ isBuyer: result.isBuyer,
596
+ hasAccount: result.hasAccount,
597
+ };
598
+ }
599
+
575
600
  /**
576
601
  * Request a password reset email via Supabase.
577
602
  *