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.
- package/.aiox-core/cli/commands/pro/buyer.js +379 -0
- package/.aiox-core/cli/commands/pro/index.js +191 -52
- package/.aiox-core/cli/commands/validate/index.js +2 -0
- package/.aiox-core/core/code-intel/helpers/dev-helper.js +1 -1
- package/.aiox-core/core/code-intel/helpers/devops-helper.js +0 -1
- package/.aiox-core/core/code-intel/helpers/planning-helper.js +1 -1
- package/.aiox-core/core/code-intel/helpers/qa-helper.js +2 -2
- package/.aiox-core/core/config/schemas/framework-config.schema.json +1 -0
- package/.aiox-core/core/config/template-overrides.js +1 -1
- package/.aiox-core/core/doctor/checks/ide-sync.js +81 -25
- package/.aiox-core/core/doctor/checks/rules-files.js +0 -1
- package/.aiox-core/core/doctor/checks/skills-count.js +83 -15
- package/.aiox-core/core/graph-dashboard/cli.js +1 -2
- package/.aiox-core/core/graph-dashboard/data-sources/code-intel-source.js +1 -1
- package/.aiox-core/core/ids/layer-classifier.js +1 -1
- package/.aiox-core/core/pro/pro-updater.js +578 -0
- package/.aiox-core/core/synapse/context/context-tracker.js +107 -9
- package/.aiox-core/core/synapse/layers/layer-processor.js +1 -1
- package/.aiox-core/core-config.yaml +15 -1
- package/.aiox-core/data/capability-detection.js +15 -15
- package/.aiox-core/data/entity-registry.yaml +18 -2
- package/.aiox-core/data/registry-update-log.jsonl +5 -0
- package/.aiox-core/data/tok3-token-comparison.js +0 -4
- package/.aiox-core/data/tool-search-validation.js +1 -1
- package/.aiox-core/development/agents/aiox-master.md +44 -6
- package/.aiox-core/development/agents/data-engineer.md +4 -4
- package/.aiox-core/development/agents/devops.md +52 -2
- package/.aiox-core/development/agents/po.md +1 -1
- package/.aiox-core/development/agents/qa.md +5 -11
- package/.aiox-core/development/agents/sm.md +3 -3
- package/.aiox-core/development/agents/ux-design-expert.md +1 -1
- package/.aiox-core/development/scripts/unified-activation-pipeline.js +29 -3
- package/.aiox-core/development/tasks/dev-develop-story.md +46 -7
- package/.aiox-core/development/tasks/devops-pro-access-grant.md +93 -0
- package/.aiox-core/development/tasks/devops-pro-activate.md +42 -0
- package/.aiox-core/development/tasks/devops-pro-check-access.md +34 -0
- package/.aiox-core/development/tasks/devops-pro-request-reset.md +34 -0
- package/.aiox-core/development/tasks/devops-pro-resend-verification.md +32 -0
- package/.aiox-core/development/tasks/devops-pro-reset-password.md +36 -0
- package/.aiox-core/development/tasks/devops-pro-validate-login.md +36 -0
- package/.aiox-core/development/tasks/devops-pro-verify-status.md +33 -0
- package/.aiox-core/development/tasks/qa-gate.md +54 -4
- package/.aiox-core/development/tasks/validate-next-story.md +39 -2
- package/.aiox-core/framework-config.yaml +1 -0
- package/.aiox-core/infrastructure/scripts/codex-skills-sync/README.md +69 -0
- package/.aiox-core/infrastructure/scripts/codex-skills-sync/bootstrap.js +727 -0
- package/.aiox-core/infrastructure/scripts/codex-skills-sync/index.js +10 -0
- package/.aiox-core/infrastructure/scripts/codex-skills-sync/validate.js +65 -4
- package/.aiox-core/infrastructure/scripts/generate-settings-json.js +29 -4
- package/.aiox-core/infrastructure/scripts/ide-sync/agent-parser.js +4 -0
- package/.aiox-core/infrastructure/scripts/ide-sync/index.js +67 -7
- package/.aiox-core/infrastructure/scripts/ide-sync/transformers/claude-code.js +145 -3
- package/.aiox-core/infrastructure/scripts/repair-agent-references.js +263 -0
- package/.aiox-core/infrastructure/scripts/validate-claude-integration.js +60 -8
- package/.aiox-core/infrastructure/scripts/validate-paths.js +13 -0
- package/.aiox-core/install-manifest.yaml +134 -82
- package/.aiox-core/utils/filters/index.js +2 -1
- package/.claude/commands/AIOX/agents/aiox-master.md +21 -0
- package/.claude/commands/AIOX/agents/analyst.md +21 -0
- package/.claude/commands/AIOX/agents/architect.md +21 -0
- package/.claude/commands/AIOX/agents/data-engineer.md +21 -0
- package/.claude/commands/AIOX/agents/dev.md +21 -0
- package/.claude/commands/AIOX/agents/devops.md +21 -0
- package/.claude/commands/AIOX/agents/pm.md +21 -0
- package/.claude/commands/AIOX/agents/po.md +21 -0
- package/.claude/commands/AIOX/agents/qa.md +21 -0
- package/.claude/commands/AIOX/agents/sm.md +21 -0
- package/.claude/commands/AIOX/agents/squad-creator.md +21 -0
- package/.claude/commands/AIOX/agents/ux-design-expert.md +21 -0
- package/.claude/commands/AIOX/scripts/agent-config-loader.js +624 -0
- package/.claude/commands/AIOX/scripts/generate-greeting.js +160 -0
- package/.claude/commands/AIOX/scripts/greeting-builder.js +866 -0
- package/.claude/commands/AIOX/scripts/session-context-loader.js +286 -0
- package/.claude/commands/AIOX/stories/story-6.1.4.md +1404 -0
- package/.claude/commands/cohort-squad/agents/cohort-manager.md +156 -0
- package/.claude/commands/design-system/agents/brad-frost.md +1097 -0
- package/.claude/commands/design-system/agents/dan-mall.md +857 -0
- package/.claude/commands/design-system/agents/dave-malouf.md +2272 -0
- package/.claude/commands/design-system/agents/design-chief.md +102 -0
- package/.claude/commands/design-system/agents/nano-banana-generator.md +162 -0
- package/.claude/commands/greet.md +101 -0
- package/.claude/commands/synapse/manager.md +75 -0
- package/.claude/commands/synapse/tasks/add-rule.md +94 -0
- package/.claude/commands/synapse/tasks/create-command.md +109 -0
- package/.claude/commands/synapse/tasks/create-domain.md +127 -0
- package/.claude/commands/synapse/tasks/diagnose-synapse.md +245 -0
- package/.claude/commands/synapse/tasks/edit-rule.md +109 -0
- package/.claude/commands/synapse/tasks/suggest-domain.md +116 -0
- package/.claude/commands/synapse/tasks/toggle-domain.md +83 -0
- package/.claude/commands/synapse/templates/domain-template +8 -0
- package/.claude/commands/synapse/templates/manifest-entry-template +4 -0
- package/.claude/commands/synapse/utils/manifest-parser-reference.md +134 -0
- package/.claude/hooks/precompact-session-digest.cjs +2 -2
- package/.claude/skills/AIOX/agents/aiox-master/SKILL.md +511 -0
- package/.claude/skills/AIOX/agents/analyst/SKILL.md +281 -0
- package/.claude/skills/AIOX/agents/architect/SKILL.md +482 -0
- package/.claude/skills/AIOX/agents/data-engineer/SKILL.md +503 -0
- package/.claude/skills/AIOX/agents/dev/SKILL.md +568 -0
- package/.claude/skills/AIOX/agents/devops/SKILL.md +597 -0
- package/.claude/skills/AIOX/agents/pm/SKILL.md +385 -0
- package/.claude/skills/AIOX/agents/po/SKILL.md +343 -0
- package/.claude/skills/AIOX/agents/qa/SKILL.md +451 -0
- package/.claude/skills/AIOX/agents/sm/SKILL.md +295 -0
- package/.claude/skills/AIOX/agents/squad-creator/SKILL.md +352 -0
- package/.claude/skills/AIOX/agents/ux-design-expert/SKILL.md +503 -0
- package/.claude/skills/architect-first/SKILL.md +275 -0
- package/.claude/skills/architect-first/assets/architecture-template.md +505 -0
- package/.claude/skills/architect-first/assets/config-template.yaml +351 -0
- package/.claude/skills/architect-first/references/architecture-checklist.md +216 -0
- package/.claude/skills/architect-first/references/pre-implementation-checklist.md +119 -0
- package/.claude/skills/architect-first/references/stop-rules-guide.md +291 -0
- package/.claude/skills/architect-first/references/testing-strategy-guide.md +477 -0
- package/.claude/skills/architect-first/scripts/architecture_validator.py +490 -0
- package/.claude/skills/architect-first/scripts/check_coupling.py +306 -0
- package/.claude/skills/architect-first/scripts/validate_risk_mitigation.py +382 -0
- package/.claude/skills/checklist-runner/SKILL.md +113 -0
- package/.claude/skills/clone-mind.md +329 -0
- package/.claude/skills/coderabbit-review/SKILL.md +106 -0
- package/.claude/skills/course-generation-workflow.md +76 -0
- package/.claude/skills/enhance-workflow.md +466 -0
- package/.claude/skills/mcp-builder/LICENSE.txt +202 -0
- package/.claude/skills/mcp-builder/SKILL.md +328 -0
- package/.claude/skills/mcp-builder/reference/evaluation.md +602 -0
- package/.claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
- package/.claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
- package/.claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
- package/.claude/skills/mcp-builder/scripts/connections.py +151 -0
- package/.claude/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/.claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/.claude/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/.claude/skills/ralph.md +181 -0
- package/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/skill-creator/SKILL.md +209 -0
- package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/.claude/skills/squad.md +301 -0
- package/.claude/skills/synapse/SKILL.md +132 -0
- package/.claude/skills/synapse/assets/README.md +50 -0
- package/.claude/skills/synapse/references/brackets.md +100 -0
- package/.claude/skills/synapse/references/commands.md +118 -0
- package/.claude/skills/synapse/references/domains.md +126 -0
- package/.claude/skills/synapse/references/layers.md +186 -0
- package/.claude/skills/synapse/references/manifest.md +142 -0
- package/.claude/skills/tech-search/SKILL.md +431 -0
- package/.claude/skills/tech-search/prompts/page-extract.md +133 -0
- package/README.en.md +2 -2
- package/README.md +8 -2
- package/bin/aiox.js +55 -4
- package/bin/utils/framework-guard.js +4 -2
- package/bin/utils/pro-detector.js +119 -28
- package/bin/utils/validate-publish.js +6 -6
- package/docs/aiox-agent-flows/devops-system.md +18 -0
- package/docs/aiox-workflows/README.md +1 -0
- package/docs/aiox-workflows/pro-access-grant-workflow.md +218 -0
- package/docs/guides/pro/access-grant-ops-playbook.md +370 -0
- package/docs/guides/pro/install-gate-setup.md +12 -6
- package/docs/guides/pro/squad-creator-handoff-pro-access-ops.md +134 -0
- package/docs/guides/supabase-ops-handoff.md +768 -0
- package/package.json +12 -1
- package/packages/aiox-pro-cli/bin/aiox-pro.js +33 -12
- package/packages/installer/src/config/configure-environment.js +118 -50
- package/packages/installer/src/installer/aiox-core-installer.js +124 -27
- package/packages/installer/src/installer/brownfield-upgrader.js +66 -9
- package/packages/installer/src/installer/dependency-installer.js +4 -0
- package/packages/installer/src/pro/pro-scaffolder.js +5 -5
- package/packages/installer/src/updater/index.js +151 -10
- package/packages/installer/src/wizard/ide-config-generator.js +73 -7
- package/packages/installer/src/wizard/index.js +119 -31
- package/packages/installer/src/wizard/pro-setup.js +118 -47
- package/packages/installer/src/wizard/validation/validators/dependency-validator.js +32 -25
- package/packages/installer/src/wizard/validation/validators/file-structure-validator.js +26 -0
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +84 -1
- package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +1 -1
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +85 -19
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +4 -4
- package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +5 -5
- package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +4 -4
- package/packages/installer/tests/unit/merger/yaml-merger.test.js +11 -11
- package/pro/README.md +12 -1
- package/pro/license/index.js +3 -11
- package/pro/license/license-api.js +25 -0
- package/pro/license/license-cache.js +135 -31
- package/pro/license/license-crypto.js +59 -3
- package/pro/package.json +5 -4
- package/pro/squads/README.md +16 -16
- package/pro/squads/index.js +1 -1
- package/scripts/e2e/installed-skills-smoke.js +264 -0
- package/scripts/package-synapse.js +3 -3
- package/scripts/validate-package-completeness.js +8 -11
- 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.
|
|
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
|
-
|
|
359
|
-
return ['dev
|
|
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
|
-
|
|
370
|
-
return ['dev
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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
|
|
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).
|
|
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(
|
|
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(
|
|
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 >=
|
|
72
|
+
- aiox-core >= 5.0.8
|
|
62
73
|
- Active AIOX Pro license
|
|
63
74
|
|
|
64
75
|
## License
|
package/pro/license/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* and feature gating in AIOX Pro.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* const { featureGate, licenseApi } = require('@aiox-
|
|
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
|
*
|