Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .aiox-core/core/errors/pro-error-registry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// PRO-UX.1 / PRO-UX.2 — Pro-specific error registry for the AIOX Pro CLI.
// Extends the canonical error-governance infra (EPIC-AIOX-ERROR-GOVERNANCE):
// reuses AIOXError + ErrorRegistry, maps to EXISTING ErrorCategory values
// (no new categories — constants.js is Object.freeze), and mirrors the
// license-server ErrorCodes (no AIOX_ prefix — deliberate, validated by the
// /^[A-Z0-9_]+$/ regex in ErrorRegistry._normalizeDefinition).
//
// userMessage holds the warm G3-approved PT-BR copy (fallback when the server
// envelope omits message_pt). recovery holds actionable PT-BR steps.

const { ErrorRegistry } = require('./error-registry');
const { ErrorCategory, ErrorSeverity } = require('./constants');
Comment on lines +11 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use absolute imports for core error modules.

Please switch these require('./...') statements to the project’s absolute import form to align with repository standards.

As per coding guidelines, **/*.{js,jsx,ts,tsx}: Use absolute imports instead of relative imports in all code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.aiox-core/core/errors/pro-error-registry.js around lines 11 - 12, Replace
the local relative requires for ErrorRegistry and constants with the project's
absolute import style: change the require('./error-registry') and
require('./constants') calls to the repository's absolute module imports that
export ErrorRegistry, ErrorCategory, and ErrorSeverity, and ensure the top of
pro-error-registry.js imports those same symbols via the absolute paths used
elsewhere in the core errors modules; also update any other relative imports in
this file to match the absolute-import convention.


const PRO_ERROR_DEFINITIONS = Object.freeze([
{
code: 'SEAT_LIMIT_EXCEEDED',
category: ErrorCategory.PERMISSION, // license-server "auth says no" → permission
severity: ErrorSeverity.ERROR,
retryable: false,
exitCode: 13,
userMessage:
'Opa! Você já está usando o Pro no número máximo de máquinas. Pega o código de suporte aqui embaixo e fala com a gente que a gente libera rapidinho.',
recovery: [
'Pega o código de suporte abaixo',
'Cola no chat com o suporte',
'Depois que liberarem, roda o comando de instalação de novo',
],
},
{
code: 'NOT_A_BUYER',
category: ErrorCategory.PERMISSION,
severity: ErrorSeverity.ERROR,
retryable: false,
exitCode: 13,
userMessage:
'Hmm, sua licença Pro não está ativa no momento. Pega o código de suporte aqui embaixo e fala com a gente que resolvemos rapidinho.',
recovery: [
'Pega o código de suporte abaixo',
'Cola no chat com o suporte',
'Aguarda a verificação da sua compra',
],
},
{
code: 'REVOKED_KEY',
category: ErrorCategory.PERMISSION,
severity: ErrorSeverity.ERROR,
retryable: false,
exitCode: 13,
userMessage:
'Hmm, sua licença Pro não está ativa no momento. Pega o código de suporte aqui embaixo e fala com a gente que a gente verifica pra você.',
recovery: [
'Pega o código de suporte abaixo',
'Cola no chat com o suporte',
'Aguarda o retorno do financeiro',
],
},
{
code: 'RATE_LIMITED',
category: ErrorCategory.NETWORK, // throttling → network layer
severity: ErrorSeverity.WARNING,
retryable: true,
userMessage:
'Calma! Foram muitas tentativas em pouco tempo. Espera uns minutinhos e tenta de novo.',
recovery: ['Aguarda 5 minutos', 'Tenta o comando de novo'],
},
{
code: 'PRO_ARTIFACT_UNAVAILABLE',
category: ErrorCategory.EXTERNAL_EXECUTOR, // npm/tarball fetch → external executor
severity: ErrorSeverity.ERROR,
retryable: true,
userMessage:
'Tivemos um probleminha pra baixar o componente Pro. Limpa o cache e tenta de novo em alguns minutos que deve rolar.',
recovery: [
'Aguarda 5 minutos (o servidor pode estar reiniciando)',
'Roda `aiox install --recover-cache` para limpar o cache local',
'Tenta de novo',
],
},
]);

const proErrorRegistry = new ErrorRegistry(PRO_ERROR_DEFINITIONS);

module.exports = { proErrorRegistry, PRO_ERROR_DEFINITIONS };
28 changes: 26 additions & 2 deletions .aiox-core/data/entity-registry.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
metadata:
version: 1.0.0
lastUpdated: '2026-05-18T16:26:51.453Z'
entityCount: 820
lastUpdated: '2026-05-20T17:25:51.033Z'
entityCount: 821
checksumAlgorithm: sha256
resolutionRate: 100
entities:
Expand Down Expand Up @@ -8961,6 +8961,7 @@ entities:
- error-registry
- errors-index
- serializer
- pro-error-registry
dependencies: []
externalDeps: []
plannedDeps: []
Expand All @@ -8982,6 +8983,7 @@ entities:
usedBy:
- aiox-error
- errors-index
- pro-error-registry
dependencies:
- constants
- utils
Expand Down Expand Up @@ -14163,6 +14165,28 @@ entities:
extensionPoints: []
checksum: sha256:dd025894f8f0d3bd22a147dbc0debef8b83e96f3c59483653404b3cd5a01d5aa
lastVerified: '2026-05-18T05:34:46.438Z'
pro-error-registry:
path: .aiox-core/core/errors/pro-error-registry.js
layer: L1
type: module
purpose: Entity at .aiox-core/core/errors/pro-error-registry.js
keywords:
- pro
- error
- registry
usedBy: []
dependencies:
- error-registry
- constants
externalDeps: []
plannedDeps: []
lifecycle: experimental
adaptability:
score: 0.4
constraints: []
extensionPoints: []
checksum: sha256:17aa9830cb745cb7865105d1dffd336fefb8e752d1b8baaf2357509e023d2018
lastVerified: '2026-05-20T17:25:51.025Z'
agents:
aiox-master:
path: .aiox-core/development/agents/aiox-master.md
Expand Down
12 changes: 8 additions & 4 deletions .aiox-core/install-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
# - File types for categorization
#
version: 5.2.7
generated_at: "2026-05-18T16:27:23.965Z"
generated_at: "2026-05-20T17:26:15.208Z"
generator: scripts/generate-install-manifest.js
file_count: 1128
file_count: 1129
files:
- path: cli/commands/config/index.js
hash: sha256:25c4b9bf4e0241abf7754b55153f49f1a214f1fb5fe904a576675634cb7b3da9
Expand Down Expand Up @@ -404,6 +404,10 @@ files:
hash: sha256:462d0aed6f57f2e4b9d51bcb20467451473c927e0d182b5c3ad43f352f44122c
type: core
size: 744
- path: core/errors/pro-error-registry.js
hash: sha256:17aa9830cb745cb7865105d1dffd336fefb8e752d1b8baaf2357509e023d2018
type: core
size: 3228
- path: core/errors/serializer.js
hash: sha256:7144adaf3a245afd732aef16e2cdb61f08246badb6b90ac1a7f4b73705ff3ce8
type: core
Expand Down Expand Up @@ -1297,9 +1301,9 @@ files:
type: data
size: 9590
- path: data/entity-registry.yaml
hash: sha256:986a8c998bb0ca11980f0bf11882f2d661cf53885e56317fef684fb872037b30
hash: sha256:2621bf774c239f67dd2af22b3d7790878926992eb4149dbca5432a28d0fb8970
type: data
size: 579527
size: 580219
- path: data/learned-patterns.yaml
hash: sha256:24ac0b160615583a0ff783d3da8af80b7f94191575d6db2054ec8e10a3f945dc
type: data
Expand Down
66 changes: 66 additions & 0 deletions packages/aiox-pro-cli/src/error-bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// PRO-UX.1 — bridges the license-server error envelope into a canonical
// AIOXError, using the Pro-specific registry with graceful fallback.
//
// 3-tier message fallback: envelope.message_pt → registry.userMessage →
// envelope.message (server EN technical). Envelopes without the PRO-16 fields
// (older server) still produce a valid AIOXError via the registry.

const { AIOXError, defaultErrorRegistry } = require('../../../.aiox-core/core/errors');
const { proErrorRegistry } = require('../../../.aiox-core/core/errors/pro-error-registry');
Comment on lines +8 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Switch to absolute imports in the bridge module.

Please replace these relative imports with absolute paths per repository standards.

As per coding guidelines, **/*.{js,jsx,ts,tsx}: Use absolute imports instead of relative imports in all code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/aiox-pro-cli/src/error-bridge.js` around lines 8 - 9, Replace the
two relative require calls in error-bridge.js with the repository's absolute
import paths: change the require('../../../.aiox-core/core/errors') that
provides AIOXError and defaultErrorRegistry and the
require('../../../.aiox-core/core/errors/pro-error-registry') that provides
proErrorRegistry to use the project's configured absolute module paths (e.g.,
the root-level package/module identifiers used across the repo) so AIOXError,
defaultErrorRegistry, and proErrorRegistry are imported via absolute imports per
coding standards.


const DEFAULT_CODE = 'AIOX_UNKNOWN_ERROR';

/**
* @param {object} envelope - { error: { code, message, message_pt?, recovery_hint?, support_code?, details? } }
* @param {object} [options] - { httpStatus?: number }
* @returns {AIOXError}
*/
function parseEnvelopeToAIOXError(envelope, options = {}) {
const httpStatus = options.httpStatus;
const errorBody = envelope && typeof envelope === 'object' ? envelope.error : null;

if (!errorBody || typeof errorBody !== 'object' || !errorBody.code) {
return new AIOXError('Erro inesperado ao falar com o servidor.', {
code: DEFAULT_CODE,
metadata: { httpStatus, malformedEnvelope: true },
});
}

const code = errorBody.code;

// Tier lookup: Pro registry → default registry → unknown fallback.
let definition = null;
if (proErrorRegistry.has(code)) {
definition = proErrorRegistry.lookup(code);
} else if (defaultErrorRegistry.has(code)) {
definition = defaultErrorRegistry.lookup(code);
} else {
definition = defaultErrorRegistry.lookup(DEFAULT_CODE);
}

// 3-tier message fallback.
const userMessage =
errorBody.message_pt ||
definition.userMessage ||
errorBody.message ||
'Erro inesperado.';

return new AIOXError(userMessage, {
code,
category: definition.category,
severity: definition.severity,
retryable: definition.retryable,
recovery: definition.recovery,
exitCode: definition.exitCode,
userMessage,
metadata: {
support_code: errorBody.support_code,
recovery_hint: errorBody.recovery_hint,
serverMessage: errorBody.message,
serverDetails: errorBody.details,
httpStatus,
},
});
}

module.exports = { parseEnvelopeToAIOXError };
55 changes: 55 additions & 0 deletions packages/aiox-pro-cli/src/recovery-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// PRO-UX.2 — conditional recovery actions keyed by recovery_hint.
// OS-aware cache cleanup (PowerShell vs bash) — fixes the exact failure mode
// from the anchor incident (Robert ran bash `find/rm` in PowerShell).

/**
* Returns the cache-cleanup commands appropriate for the current OS.
* Windows → PowerShell; macOS/Linux → bash.
* @param {string} [platform] - defaults to process.platform (injectable for tests)
* @returns {string[]}
*/
function getCacheCleanCommands(platform = process.platform) {
if (platform === 'win32') {
return [
'Get-ChildItem -Path . -Recurse -Filter "pro" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match "node_modules\\\\@aiox-squads\\\\pro$" } | Remove-Item -Recurse -Force',
'Remove-Item -Recurse -Force $env:USERPROFILE\\.npm\\_npx -ErrorAction SilentlyContinue',
];
}
// darwin / linux
return [
'find . -maxdepth 5 -path "*/node_modules/@aiox-squads/pro" -type d 2>/dev/null -exec rm -rf {} + 2>/dev/null',
'rm -rf ~/.npm/_npx 2>/dev/null',
];
}

/**
* Dispatches a recovery action based on recovery_hint.
* Returns { action, commands?, waitSeconds? } describing what the CLI should do.
* Pure/declarative — the CLI shell decides whether to auto-run or just print.
*
* @param {string} recoveryHint
* @param {object} [context] - { platform?, waitSeconds? }
*/
function planRecoveryAction(recoveryHint, context = {}) {
switch (recoveryHint) {
case 'wait_and_retry':
return { action: 'wait', waitSeconds: context.waitSeconds || 300 };
case 'retry_install_cache_clean':
return {
action: 'clean_cache',
commands: getCacheCleanCommands(context.platform),
};
case 'contact_support_seat_reset':
case 'contact_support_grant':
case 'contact_support_billing':
return { action: 'contact_support' };
case 'verify_email':
return { action: 'verify_email' };
case 'check_credentials':
return { action: 'check_credentials' };
default:
return { action: 'none' };
}
}

module.exports = { getCacheCleanCommands, planRecoveryAction };
42 changes: 42 additions & 0 deletions packages/aiox-pro-cli/src/render-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// PRO-UX.2 — renders an AIOXError (from error-bridge) as warm, actionable CLI
// output. Shows userMessage + numbered recovery steps + support_code (when
// present) + support link (only for contact_support_* hints) + a discreet
// technical footer. Writer is injectable for testability.

const SUPPORT_URL = 'https://suporte.aiox.dev';

/**
* @param {AIOXError} err
* @param {(line: string) => void} [write] - defaults to stderr writer
*/
function renderError(err, write) {
const out = write || ((line) => process.stderr.write(line + '\n'));
const meta = (err && err.metadata) || {};
const recovery = Array.isArray(err && err.recovery) ? err.recovery : [];
const recoveryHint = meta.recovery_hint;
const supportCode = meta.support_code;
const httpStatus = meta.httpStatus;

out(`✗ ${err.userMessage || err.message}`);

if (recovery.length > 0) {
out('');
out('Para resolver:');
recovery.forEach((step, i) => out(` ${i + 1}. ${step}`));
}

if (supportCode) {
out('');
out(`Código de suporte: ${supportCode}`);
if (typeof recoveryHint === 'string' && recoveryHint.startsWith('contact_support_')) {
out(`Suporte: ${SUPPORT_URL}`);
}
}

// Discreet technical footer for debugging — not the student-facing message.
out('');
const statusPart = httpStatus ? ` — HTTP ${httpStatus}` : '';
out(`(${err.code}${statusPart})`);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

module.exports = { renderError, SUPPORT_URL };
20 changes: 18 additions & 2 deletions packages/installer/src/wizard/pro-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,24 @@ class InlineLicenseClient {
try {
const parsed = JSON.parse(data);
if (res.statusCode >= 400) {
const err = new Error(parsed.message || `HTTP ${res.statusCode}`);
err.code = parsed.code;
// PRO-16/PRO-UX: the structured envelope nests fields under
// `error` ({ error: { code, message, message_pt, recovery_hint,
// support_code } }). Older shapes put them at the root. Read the
// nested body first (this is the root cause of the "HTTP 403"
// opaque message — the old code read parsed.message at the root,
// which is undefined for the structured envelope).
const errorBody =
parsed && parsed.error && typeof parsed.error === 'object'
? parsed.error
: parsed;
const err = new Error(
(errorBody && errorBody.message) || parsed.message || `HTTP ${res.statusCode}`,
);
err.code = (errorBody && errorBody.code) || parsed.code;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
err.httpStatus = res.statusCode;
if (parsed && parsed.error) {
err.envelope = parsed; // full envelope for parseEnvelopeToAIOXError
}
reject(err);
} else {
resolve(parsed);
Expand Down
Loading
Loading