@jaimevalasek/aioson 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/docs/design-previews/aurora-command-ui-website.html +884 -0
- package/docs/design-previews/aurora-command-ui.html +682 -0
- package/docs/design-previews/bold-editorial-ui-website.html +658 -0
- package/docs/design-previews/bold-editorial-ui.html +717 -0
- package/docs/design-previews/clean-saas-ui-website.html +1202 -0
- package/docs/design-previews/clean-saas-ui.html +549 -0
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
- package/docs/design-previews/cognitive-core-ui.html +463 -0
- package/docs/design-previews/glassmorphism-ui-website.html +572 -0
- package/docs/design-previews/glassmorphism-ui.html +886 -0
- package/docs/design-previews/index.html +699 -0
- package/docs/design-previews/interface-design-website.html +1187 -0
- package/docs/design-previews/interface-design.html +513 -0
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
- package/docs/design-previews/neo-brutalist-ui.html +797 -0
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
- package/docs/design-previews/premium-command-center-ui.html +552 -0
- package/docs/design-previews/warm-craft-ui-website.html +684 -0
- package/docs/design-previews/warm-craft-ui.html +739 -0
- package/docs/en/cli-reference.md +20 -9
- package/docs/pt/README.md +7 -0
- package/docs/pt/agent-sharding.md +132 -0
- package/docs/pt/agentes.md +8 -2
- package/docs/pt/busca-de-contexto.md +129 -0
- package/docs/pt/cache-de-contexto.md +156 -0
- package/docs/pt/comandos-cli.md +28 -0
- package/docs/pt/design-hybrid-forge.md +107 -0
- package/docs/pt/inicio-rapido.md +54 -3
- package/docs/pt/inteligencia-adaptativa.md +324 -0
- package/docs/pt/monitor-de-contexto.md +104 -0
- package/docs/pt/recuperacao-de-sessao.md +125 -0
- package/docs/pt/sandbox.md +125 -0
- package/docs/pt/skills.md +98 -6
- package/package.json +1 -1
- package/src/agent-loader.js +280 -0
- package/src/cli.js +94 -0
- package/src/commands/agent-loader.js +85 -0
- package/src/commands/context-cache.js +90 -0
- package/src/commands/context-monitor.js +92 -0
- package/src/commands/context-search.js +66 -0
- package/src/commands/design-hybrid-options.js +385 -0
- package/src/commands/health.js +214 -0
- package/src/commands/init.js +54 -13
- package/src/commands/install.js +52 -13
- package/src/commands/learning-evolve.js +355 -0
- package/src/commands/live.js +34 -0
- package/src/commands/recovery.js +43 -0
- package/src/commands/sandbox.js +37 -0
- package/src/commands/setup-context.js +22 -2
- package/src/commands/setup.js +178 -0
- package/src/commands/skill.js +79 -32
- package/src/commands/tool-registry-cmd.js +232 -0
- package/src/commands/update.js +7 -0
- package/src/constants.js +9 -0
- package/src/context-cache.js +159 -0
- package/src/context-search.js +326 -0
- package/src/design-variation-catalog.js +503 -0
- package/src/i18n/messages/en.js +32 -2
- package/src/i18n/messages/es.js +30 -2
- package/src/i18n/messages/fr.js +30 -2
- package/src/i18n/messages/pt-BR.js +32 -2
- package/src/install-animation.js +260 -0
- package/src/install-profile.js +143 -0
- package/src/install-wizard.js +474 -0
- package/src/installer.js +38 -10
- package/src/parser.js +7 -1
- package/src/recovery-context-session.js +154 -0
- package/src/runtime-store.js +97 -1
- package/src/sandbox.js +177 -0
- package/src/tool-executor.js +94 -0
- package/src/updater.js +11 -3
- package/template/.aioson/agents/analyst.md +58 -3
- package/template/.aioson/agents/architect.md +38 -0
- package/template/.aioson/agents/design-hybrid-forge.md +127 -0
- package/template/.aioson/agents/dev.md +103 -0
- package/template/.aioson/agents/deyvin.md +57 -0
- package/template/.aioson/agents/pm.md +58 -0
- package/template/.aioson/agents/product.md +28 -0
- package/template/.aioson/agents/qa.md +79 -0
- package/template/.aioson/agents/setup.md +65 -3
- package/template/.aioson/agents/sheldon.md +107 -6
- package/template/.aioson/agents/tester.md +156 -0
- package/template/.aioson/config.md +15 -0
- package/template/.aioson/context/forensics/.gitkeep +0 -0
- package/template/.aioson/context/seeds/seed-example.md +27 -0
- package/template/.aioson/context/user-profile.md +42 -0
- package/template/.aioson/locales/en/agents/setup.md +33 -1
- package/template/.aioson/locales/es/agents/setup.md +33 -1
- package/template/.aioson/locales/fr/agents/setup.md +33 -1
- package/template/.aioson/locales/pt-BR/agents/setup.md +33 -1
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +45 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +66 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +144 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +291 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +117 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +188 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
- package/template/AGENTS.md +23 -1
- package/template/CLAUDE.md +1 -0
package/src/commands/install.js
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { detectFramework } = require('../detector');
|
|
5
|
-
const { installTemplate } = require('../installer');
|
|
5
|
+
const { installTemplate, readInstallProfile } = require('../installer');
|
|
6
6
|
const { applyAgentLocale } = require('../locales');
|
|
7
7
|
const { resolvePromptTool } = require('../prompt-tool');
|
|
8
|
+
const { runInstallWizard } = require('../install-wizard');
|
|
9
|
+
const { renderRevealAnimation, renderInstallSummary, renderProgress } = require('../install-animation');
|
|
10
|
+
const { getCliVersion } = require('../version');
|
|
8
11
|
|
|
9
12
|
async function runInstall({ args, options, logger, t }) {
|
|
10
13
|
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
11
14
|
const force = Boolean(options.force);
|
|
12
15
|
const dryRun = Boolean(options['dry-run']);
|
|
16
|
+
const noInteractive = Boolean(options['no-interactive']);
|
|
17
|
+
const reconfigure = Boolean(options.reconfigure);
|
|
13
18
|
const requestedLanguage = options.lang || options.language;
|
|
14
19
|
const promptTool = resolvePromptTool(options.tool);
|
|
15
20
|
|
|
@@ -23,16 +28,42 @@ async function runInstall({ args, options, logger, t }) {
|
|
|
23
28
|
logger.log(t('install.framework_not_detected'));
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
// Decide install profile
|
|
32
|
+
let installProfile = null;
|
|
33
|
+
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
34
|
+
|
|
35
|
+
if (!noInteractive && isTTY && !dryRun) {
|
|
36
|
+
const existingProfile = await readInstallProfile(targetDir);
|
|
37
|
+
if (!existingProfile || reconfigure) {
|
|
38
|
+
installProfile = await runInstallWizard({
|
|
39
|
+
noInteractive,
|
|
40
|
+
existingProfile: reconfigure ? existingProfile : null,
|
|
41
|
+
t
|
|
42
|
+
});
|
|
43
|
+
// null = user cancelled → fall back to full install
|
|
44
|
+
} else {
|
|
45
|
+
installProfile = existingProfile;
|
|
46
|
+
logger.log(t('install.using_saved_profile'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// When reconfigure, we need overwrite=true so changed profile is reflected
|
|
51
|
+
const overwrite = force || reconfigure;
|
|
52
|
+
|
|
26
53
|
const result = await installTemplate(targetDir, {
|
|
27
|
-
overwrite
|
|
54
|
+
overwrite,
|
|
28
55
|
dryRun,
|
|
29
56
|
mode: 'install',
|
|
30
|
-
frameworkDetection: detection.framework
|
|
57
|
+
frameworkDetection: detection.framework,
|
|
58
|
+
installProfile,
|
|
59
|
+
onProgress: isTTY && !dryRun ? renderProgress : null
|
|
31
60
|
});
|
|
32
61
|
|
|
62
|
+
// Locale: explicit --lang flag wins over profile, profile wins over nothing
|
|
63
|
+
const effectiveLocale = requestedLanguage || (installProfile && installProfile.locale) || null;
|
|
33
64
|
let localeApply = null;
|
|
34
|
-
if (
|
|
35
|
-
localeApply = await applyAgentLocale(targetDir,
|
|
65
|
+
if (effectiveLocale) {
|
|
66
|
+
localeApply = await applyAgentLocale(targetDir, effectiveLocale, { dryRun });
|
|
36
67
|
if (dryRun) {
|
|
37
68
|
logger.log(t('locale_apply.dry_run_applied', { locale: localeApply.locale }));
|
|
38
69
|
} else {
|
|
@@ -40,13 +71,20 @@ async function runInstall({ args, options, logger, t }) {
|
|
|
40
71
|
}
|
|
41
72
|
}
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
// Reveal animation + summary (TTY only, not dry-run)
|
|
75
|
+
if (isTTY && !dryRun) {
|
|
76
|
+
const version = await getCliVersion();
|
|
77
|
+
await renderRevealAnimation(version);
|
|
78
|
+
renderInstallSummary({ result, installProfile });
|
|
79
|
+
} else {
|
|
80
|
+
logger.log(t('install.done_at', { targetDir }));
|
|
81
|
+
logger.log(t('install.files_copied', { count: result.copied.length }));
|
|
82
|
+
logger.log(t('install.files_skipped', { count: result.skipped.length }));
|
|
83
|
+
logger.log(t('install.next_steps'));
|
|
84
|
+
logger.log(t('install.step_setup_context'));
|
|
85
|
+
logger.log(t('install.step_agents'));
|
|
86
|
+
logger.log(t('install.step_agent_prompt', { tool: promptTool }));
|
|
87
|
+
}
|
|
50
88
|
|
|
51
89
|
if (result.isExistingProject) {
|
|
52
90
|
logger.log('');
|
|
@@ -59,7 +97,8 @@ async function runInstall({ args, options, logger, t }) {
|
|
|
59
97
|
targetDir,
|
|
60
98
|
detection,
|
|
61
99
|
...result,
|
|
62
|
-
localeApply
|
|
100
|
+
localeApply,
|
|
101
|
+
installProfile
|
|
63
102
|
};
|
|
64
103
|
}
|
|
65
104
|
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb, listSquadLearnings, listProjectLearnings, promoteSquadLearning, promoteProjectLearning } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
const AGENTS_DIR = path.join('.aioson', 'agents');
|
|
8
|
+
const EVOLUTION_DIR = path.join('.aioson', 'evolution');
|
|
9
|
+
const CONTEXT_FILE = path.join('.aioson', 'context', 'project.context.md');
|
|
10
|
+
const MAX_FILE_LINES = 300;
|
|
11
|
+
const MIN_FREQUENCY = 2;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mapeia tipo de learning para seção e arquivo alvo.
|
|
15
|
+
*/
|
|
16
|
+
function resolveDeltaTarget(type, squadSlug) {
|
|
17
|
+
const rulesDir = path.join('.aioson', 'rules');
|
|
18
|
+
if (type === 'preference') {
|
|
19
|
+
return { file: CONTEXT_FILE, section: '## Preferências dos Agentes' };
|
|
20
|
+
}
|
|
21
|
+
if (type === 'process') {
|
|
22
|
+
const filename = squadSlug ? `${squadSlug}-process.md` : 'project-process.md';
|
|
23
|
+
return { file: path.join(rulesDir, filename), section: null };
|
|
24
|
+
}
|
|
25
|
+
if (type === 'domain') {
|
|
26
|
+
return { file: CONTEXT_FILE, section: '## Conhecimento de Domínio' };
|
|
27
|
+
}
|
|
28
|
+
if (type === 'quality') {
|
|
29
|
+
return { file: CONTEXT_FILE, section: '## Padrões de Qualidade' };
|
|
30
|
+
}
|
|
31
|
+
return { file: CONTEXT_FILE, section: '## Observações' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gate 1: arquivo não pode estar dentro de .aioson/agents/
|
|
36
|
+
*/
|
|
37
|
+
function passesConstitutionGate(filePath, projectDir) {
|
|
38
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(projectDir, filePath);
|
|
39
|
+
const agentsAbsolute = path.resolve(projectDir, AGENTS_DIR);
|
|
40
|
+
return !absolute.startsWith(agentsAbsolute + path.sep) && absolute !== agentsAbsolute;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gate 2: arquivo alvo não pode ultrapassar MAX_FILE_LINES após append.
|
|
45
|
+
*/
|
|
46
|
+
async function passesSizeGate(filePath, projectDir, newContent) {
|
|
47
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(projectDir, filePath);
|
|
48
|
+
let existingLines = 0;
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.readFile(absolute, 'utf8');
|
|
51
|
+
existingLines = content.split('\n').length;
|
|
52
|
+
} catch {
|
|
53
|
+
existingLines = 0;
|
|
54
|
+
}
|
|
55
|
+
const newLines = String(newContent || '').split('\n').length;
|
|
56
|
+
return (existingLines + newLines) <= MAX_FILE_LINES;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Gera o conteúdo textual de um delta a partir de um grupo de learnings.
|
|
61
|
+
*/
|
|
62
|
+
function buildDeltaContent(learnings, section) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
if (section) {
|
|
65
|
+
lines.push('');
|
|
66
|
+
}
|
|
67
|
+
for (const l of learnings) {
|
|
68
|
+
const confidence = l.confidence === 'high' ? '(alta confiança)' : l.confidence === 'low' ? '(baixa confiança)' : '';
|
|
69
|
+
lines.push(`- ${l.title} ${confidence}`.trim());
|
|
70
|
+
if (l.evidence) {
|
|
71
|
+
lines.push(` > ${l.evidence}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Aplica um delta aprovado no sistema de arquivos.
|
|
79
|
+
*/
|
|
80
|
+
async function applyDelta(delta, projectDir) {
|
|
81
|
+
const absolute = path.isAbsolute(delta.file) ? delta.file : path.resolve(projectDir, delta.file);
|
|
82
|
+
|
|
83
|
+
await fs.mkdir(path.dirname(absolute), { recursive: true });
|
|
84
|
+
|
|
85
|
+
let existing = '';
|
|
86
|
+
try {
|
|
87
|
+
existing = await fs.readFile(absolute, 'utf8');
|
|
88
|
+
} catch {
|
|
89
|
+
existing = '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (delta.section && existing) {
|
|
93
|
+
if (existing.includes(delta.section)) {
|
|
94
|
+
// Insere após o cabeçalho da seção
|
|
95
|
+
const updated = existing.replace(delta.section, `${delta.section}\n${delta.content}`);
|
|
96
|
+
await fs.writeFile(absolute, updated, 'utf8');
|
|
97
|
+
} else {
|
|
98
|
+
// Adiciona a seção no final
|
|
99
|
+
await fs.writeFile(absolute, `${existing}\n${delta.section}\n${delta.content}\n`, 'utf8');
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// Append simples
|
|
103
|
+
await fs.writeFile(absolute, `${existing}\n${delta.content}\n`, 'utf8');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Ponto de entrada principal.
|
|
109
|
+
*/
|
|
110
|
+
async function runLearningEvolve({ args = [], options = {}, logger = console, t = (k) => k } = {}) {
|
|
111
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
112
|
+
const squadSlug = options.squad || null;
|
|
113
|
+
const dryRun = Boolean(options['dry-run'] || options.dry);
|
|
114
|
+
const autoApply = Boolean(options['auto-apply'] || options.auto);
|
|
115
|
+
const quiet = Boolean(options.quiet);
|
|
116
|
+
|
|
117
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
118
|
+
if (!handle) {
|
|
119
|
+
logger.error('Runtime store não encontrado. Execute aioson runtime:init primeiro.');
|
|
120
|
+
return { ok: false, error: 'no_runtime' };
|
|
121
|
+
}
|
|
122
|
+
const { db } = handle;
|
|
123
|
+
|
|
124
|
+
let learnings;
|
|
125
|
+
try {
|
|
126
|
+
if (squadSlug) {
|
|
127
|
+
learnings = listSquadLearnings(db, squadSlug, 'active');
|
|
128
|
+
} else {
|
|
129
|
+
const squad = listSquadLearnings(db, null, 'active');
|
|
130
|
+
const project = listProjectLearnings(db, 'active');
|
|
131
|
+
learnings = [...squad, ...project];
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
db.close();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Filtra por frequência mínima
|
|
138
|
+
const eligible = learnings.filter((l) => Number(l.frequency || 1) >= MIN_FREQUENCY);
|
|
139
|
+
|
|
140
|
+
if (eligible.length === 0) {
|
|
141
|
+
if (!quiet) logger.log('Nenhum learning com frequência suficiente para evoluir (mínimo: 2 ocorrências).');
|
|
142
|
+
return { ok: true, evolved: 0, skipped: 0, proposed: [] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!quiet) logger.log(`Analisando ${eligible.length} learnings elegíveis...`);
|
|
146
|
+
|
|
147
|
+
// Agrupa por tipo
|
|
148
|
+
const grouped = {};
|
|
149
|
+
for (const l of eligible) {
|
|
150
|
+
const key = l.type;
|
|
151
|
+
if (!grouped[key]) grouped[key] = [];
|
|
152
|
+
grouped[key].push(l);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Gera deltas
|
|
156
|
+
const proposed = [];
|
|
157
|
+
const rejected = [];
|
|
158
|
+
|
|
159
|
+
for (const [type, group] of Object.entries(grouped)) {
|
|
160
|
+
const { file, section } = resolveDeltaTarget(type, group[0].squad_slug || null);
|
|
161
|
+
const content = buildDeltaContent(group, section);
|
|
162
|
+
|
|
163
|
+
// Gate 1: Constitution
|
|
164
|
+
if (!passesConstitutionGate(file, projectDir)) {
|
|
165
|
+
rejected.push({ type, reason: 'constitution_gate', file, count: group.length });
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Gate 2: Size
|
|
170
|
+
const sizeOk = await passesSizeGate(file, projectDir, content);
|
|
171
|
+
if (!sizeOk) {
|
|
172
|
+
rejected.push({ type, reason: 'size_gate', file, count: group.length });
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
proposed.push({
|
|
177
|
+
type,
|
|
178
|
+
file,
|
|
179
|
+
section,
|
|
180
|
+
content,
|
|
181
|
+
sourceIds: group.map((l) => l.learning_id),
|
|
182
|
+
count: group.length
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Exibe resultados
|
|
187
|
+
if (!quiet) {
|
|
188
|
+
logger.log('');
|
|
189
|
+
logger.log(`Deltas propostos: ${proposed.length} aprovados, ${rejected.length} rejeitados`);
|
|
190
|
+
logger.log('');
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < proposed.length; i++) {
|
|
193
|
+
const d = proposed[i];
|
|
194
|
+
logger.log(` [${i + 1}] APPEND → ${d.file}`);
|
|
195
|
+
if (d.section) logger.log(` Seção: ${d.section}`);
|
|
196
|
+
logger.log(` Learnings: ${d.count} (${d.type})`);
|
|
197
|
+
logger.log(d.content.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
198
|
+
logger.log('');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const r of rejected) {
|
|
202
|
+
const reason = r.reason === 'constitution_gate' ? 'gate constitucional (arquivo imutável)' : `gate de tamanho (>${MAX_FILE_LINES} linhas)`;
|
|
203
|
+
logger.log(` ✗ Rejeitado [${r.type}] → ${r.file}: ${reason}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (proposed.length === 0) {
|
|
208
|
+
return { ok: true, evolved: 0, skipped: rejected.length, proposed: [] };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Dry-run: só exibe, não aplica
|
|
212
|
+
if (dryRun) {
|
|
213
|
+
logger.log('Modo dry-run: nenhuma alteração foi feita.');
|
|
214
|
+
return { ok: true, evolved: 0, skipped: rejected.length, proposed };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Auto-apply: aplica diretamente
|
|
218
|
+
if (autoApply) {
|
|
219
|
+
return applyProposed(proposed, projectDir, db, logger, quiet, squadSlug);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Modo padrão: salva arquivo pendente
|
|
223
|
+
const evolutionDir = path.resolve(projectDir, EVOLUTION_DIR);
|
|
224
|
+
await fs.mkdir(evolutionDir, { recursive: true });
|
|
225
|
+
|
|
226
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
227
|
+
const pendingFile = path.join(evolutionDir, `pending-${timestamp}.json`);
|
|
228
|
+
await fs.writeFile(pendingFile, JSON.stringify({ createdAt: new Date().toISOString(), projectDir, proposed, rejected }, null, 2), 'utf8');
|
|
229
|
+
|
|
230
|
+
if (!quiet) {
|
|
231
|
+
logger.log(`Proposta salva em: ${pendingFile}`);
|
|
232
|
+
logger.log('Para aplicar: aioson learning:apply . --file=' + path.relative(projectDir, pendingFile));
|
|
233
|
+
logger.log('Para aplicar automaticamente: aioson learning:evolve . --auto-apply');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { ok: true, evolved: 0, skipped: rejected.length, proposed, pendingFile };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function applyProposed(proposed, projectDir, db, logger, quiet, squadSlug) {
|
|
240
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
241
|
+
if (!handle) return { ok: false, error: 'no_runtime' };
|
|
242
|
+
const applyDb = handle.db;
|
|
243
|
+
|
|
244
|
+
let evolved = 0;
|
|
245
|
+
try {
|
|
246
|
+
for (const delta of proposed) {
|
|
247
|
+
await applyDelta(delta, projectDir);
|
|
248
|
+
|
|
249
|
+
// Promove learnings no DB
|
|
250
|
+
for (const id of delta.sourceIds) {
|
|
251
|
+
try {
|
|
252
|
+
if (id.startsWith('sl-') || id.startsWith('pl-')) {
|
|
253
|
+
if (id.startsWith('sl-')) {
|
|
254
|
+
promoteSquadLearning(applyDb, id, delta.file);
|
|
255
|
+
} else {
|
|
256
|
+
promoteProjectLearning(applyDb, id, delta.file);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
promoteSquadLearning(applyDb, id, delta.file);
|
|
260
|
+
}
|
|
261
|
+
} catch { /* learning pode já ter sido promovido */ }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
evolved++;
|
|
265
|
+
if (!quiet) logger.log(` ✓ Aplicado: ${delta.file} (+${delta.count} learnings)`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Registra no log de evolução
|
|
269
|
+
const evolutionDir = path.resolve(projectDir, EVOLUTION_DIR);
|
|
270
|
+
await fs.mkdir(evolutionDir, { recursive: true });
|
|
271
|
+
const logFile = path.join(evolutionDir, 'log.jsonl');
|
|
272
|
+
const logEntry = JSON.stringify({
|
|
273
|
+
appliedAt: new Date().toISOString(),
|
|
274
|
+
deltasCount: evolved,
|
|
275
|
+
squad: squadSlug || null,
|
|
276
|
+
files: proposed.map((d) => d.file)
|
|
277
|
+
});
|
|
278
|
+
await fs.appendFile(logFile, `${logEntry}\n`, 'utf8');
|
|
279
|
+
|
|
280
|
+
if (!quiet) logger.log(`\n${evolved} delta(s) aplicado(s) com sucesso.`);
|
|
281
|
+
} finally {
|
|
282
|
+
applyDb.close();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { ok: true, evolved, skipped: 0, proposed };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Subcomando: apply — aplica um arquivo de deltas pendentes.
|
|
290
|
+
*/
|
|
291
|
+
async function runLearningApply({ args = [], options = {}, logger = console } = {}) {
|
|
292
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
293
|
+
const filePath = options.file ? String(options.file) : null;
|
|
294
|
+
|
|
295
|
+
if (!filePath) {
|
|
296
|
+
logger.error('--file é obrigatório. Exemplo: aioson learning:apply . --file=.aioson/evolution/pending-XXX.json');
|
|
297
|
+
return { ok: false, error: 'file_required' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(projectDir, filePath);
|
|
301
|
+
|
|
302
|
+
let pendingData;
|
|
303
|
+
try {
|
|
304
|
+
pendingData = JSON.parse(await fs.readFile(absolute, 'utf8'));
|
|
305
|
+
} catch (err) {
|
|
306
|
+
logger.error(`Não foi possível ler o arquivo: ${absolute}\n${err.message}`);
|
|
307
|
+
return { ok: false, error: 'file_not_readable' };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const { proposed = [], rejected = [] } = pendingData;
|
|
311
|
+
|
|
312
|
+
if (proposed.length === 0) {
|
|
313
|
+
logger.log('Nenhum delta para aplicar neste arquivo.');
|
|
314
|
+
return { ok: true, evolved: 0 };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
logger.log(`Aplicando ${proposed.length} delta(s)...`);
|
|
318
|
+
|
|
319
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
320
|
+
const db = handle ? handle.db : null;
|
|
321
|
+
|
|
322
|
+
let evolved = 0;
|
|
323
|
+
for (const delta of proposed) {
|
|
324
|
+
try {
|
|
325
|
+
await applyDelta(delta, projectDir);
|
|
326
|
+
if (db) {
|
|
327
|
+
for (const id of (delta.sourceIds || [])) {
|
|
328
|
+
try {
|
|
329
|
+
if (id.startsWith('pl-')) {
|
|
330
|
+
promoteProjectLearning(db, id, delta.file);
|
|
331
|
+
} else {
|
|
332
|
+
promoteSquadLearning(db, id, delta.file);
|
|
333
|
+
}
|
|
334
|
+
} catch { /* ok */ }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
evolved++;
|
|
338
|
+
logger.log(` ✓ ${delta.file}`);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
logger.error(` ✗ Falha em ${delta.file}: ${err.message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (db) db.close();
|
|
345
|
+
|
|
346
|
+
// Remove o arquivo pendente após aplicar
|
|
347
|
+
try { await fs.unlink(absolute); } catch { /* ok */ }
|
|
348
|
+
|
|
349
|
+
logger.log(`\n${evolved}/${proposed.length} delta(s) aplicado(s).`);
|
|
350
|
+
if (rejected.length > 0) logger.log(`${rejected.length} rejeitado(s) previamente pelos gates.`);
|
|
351
|
+
|
|
352
|
+
return { ok: true, evolved };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
module.exports = { runLearningEvolve, runLearningApply };
|
package/src/commands/live.js
CHANGED
|
@@ -1062,6 +1062,23 @@ async function runLiveStart({ args, options = {}, logger, t }) {
|
|
|
1062
1062
|
logger.log(t('live.session_started', { agent: agentName, tool, session: sessionKey, dbPath }));
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
|
+
// Ambient Intelligence: exibe digest de saúde ao iniciar sessão
|
|
1066
|
+
if (!options.json && !options['no-health']) {
|
|
1067
|
+
try {
|
|
1068
|
+
const { getHealthDigest } = require('./health');
|
|
1069
|
+
const items = await getHealthDigest(targetDir);
|
|
1070
|
+
if (items && items.length > 0) {
|
|
1071
|
+
logger.log('');
|
|
1072
|
+
logger.log('AIOSON Health — itens pendentes:');
|
|
1073
|
+
for (const item of items) {
|
|
1074
|
+
logger.log(` ● ${item}`);
|
|
1075
|
+
}
|
|
1076
|
+
logger.log(' → aioson health . para detalhes');
|
|
1077
|
+
logger.log('');
|
|
1078
|
+
}
|
|
1079
|
+
} catch { /* não bloqueia o start */ }
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1065
1082
|
if (child) {
|
|
1066
1083
|
childResult = await waitForChild(child);
|
|
1067
1084
|
}
|
|
@@ -1508,6 +1525,23 @@ async function runLiveClose({ args, options = {}, logger, t }) {
|
|
|
1508
1525
|
logger.log(t('live.session_closed', { agent: context.agentName, session: context.sessionKey, dbPath }));
|
|
1509
1526
|
}
|
|
1510
1527
|
|
|
1528
|
+
// Ambient Intelligence: sugere evolução se há learnings acumulados
|
|
1529
|
+
if (!options.json && !options['no-health']) {
|
|
1530
|
+
try {
|
|
1531
|
+
const { getHealthDigest } = require('./health');
|
|
1532
|
+
const items = await getHealthDigest(targetDir);
|
|
1533
|
+
if (items && items.length > 0) {
|
|
1534
|
+
logger.log('');
|
|
1535
|
+
logger.log('AIOSON Health — itens após sessão:');
|
|
1536
|
+
for (const item of items) {
|
|
1537
|
+
logger.log(` ● ${item}`);
|
|
1538
|
+
}
|
|
1539
|
+
logger.log(' → aioson health . para detalhes e ações');
|
|
1540
|
+
logger.log('');
|
|
1541
|
+
}
|
|
1542
|
+
} catch { /* não bloqueia o close */ }
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1511
1545
|
return {
|
|
1512
1546
|
ok: true,
|
|
1513
1547
|
targetDir,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { generateSessionRecovery, readSessionRecovery } = require('../recovery-context-session');
|
|
5
|
+
|
|
6
|
+
async function runRecoveryGenerate({ args, options, logger }) {
|
|
7
|
+
const cwd = path.resolve(process.cwd(), args[0] || '.');
|
|
8
|
+
const sessionState = {
|
|
9
|
+
goal: options.goal || undefined,
|
|
10
|
+
agent: options.agent || undefined,
|
|
11
|
+
tasks: [],
|
|
12
|
+
notes: []
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const result = await generateSessionRecovery(cwd, sessionState);
|
|
16
|
+
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
logger.error(`recovery:generate failed: ${result.error}`);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
logger.log(`Recovery context generated: ${result.path} (${result.tokens} tokens)`);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function runRecoveryShow({ args, options, logger }) {
|
|
27
|
+
const cwd = path.resolve(process.cwd(), args[0] || '.');
|
|
28
|
+
const content = await readSessionRecovery(cwd);
|
|
29
|
+
|
|
30
|
+
if (!content) {
|
|
31
|
+
logger.log('No recovery context found. Run: aioson recovery:generate');
|
|
32
|
+
return { ok: false, error: 'not_found' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (options.json) {
|
|
36
|
+
return { ok: true, content };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
logger.log(content);
|
|
40
|
+
return { ok: true, content };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { runRecoveryGenerate, runRecoveryShow };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { executeInSandbox } = require('../sandbox');
|
|
5
|
+
|
|
6
|
+
async function runSandboxExec({ args, options, logger }) {
|
|
7
|
+
const command = args[0] || options.command || '';
|
|
8
|
+
const cwd = path.resolve(process.cwd(), options.cwd || '.');
|
|
9
|
+
const timeout = Number(options.timeout) || 30_000;
|
|
10
|
+
const intent = options.intent || undefined;
|
|
11
|
+
|
|
12
|
+
if (!command) {
|
|
13
|
+
logger.error('Usage: aioson sandbox:exec "<command>" [--timeout=30000] [--cwd=.]');
|
|
14
|
+
return { ok: false, error: 'missing_command' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = await executeInSandbox(command, { cwd, timeout, intent });
|
|
18
|
+
|
|
19
|
+
if (options.json) {
|
|
20
|
+
return { ok: result.ok, ...result };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (result.stdout) {
|
|
24
|
+
logger.log(result.stdout);
|
|
25
|
+
}
|
|
26
|
+
if (result.stderr) {
|
|
27
|
+
logger.error(result.stderr);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (result.timedOut) {
|
|
31
|
+
logger.error(`Command timed out after ${timeout}ms`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { ok: result.ok, ...result };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { runSandboxExec };
|
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const readline = require('node:readline/promises');
|
|
5
5
|
const { detectFramework, isMonorepoDetection } = require('../detector');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Infer conversation language from the OS locale environment variables.
|
|
9
|
+
* Supports LANGUAGE, LANG, and LC_ALL in priority order.
|
|
10
|
+
* Maps POSIX locale codes (e.g. pt_BR.UTF-8) to AIOSON locale codes (e.g. pt-BR).
|
|
11
|
+
*/
|
|
12
|
+
function detectSystemLanguage() {
|
|
13
|
+
const raw = process.env.LANGUAGE || process.env.LANG || process.env.LC_ALL || '';
|
|
14
|
+
const base = raw.split(':')[0].split('.')[0].trim();
|
|
15
|
+
if (!base || base === 'C' || base === 'POSIX') return 'en';
|
|
16
|
+
const normalized = base.replace('_', '-');
|
|
17
|
+
const supported = ['en', 'pt-BR', 'es', 'fr'];
|
|
18
|
+
if (supported.includes(normalized)) return normalized;
|
|
19
|
+
const lang = normalized.split('-')[0].toLowerCase();
|
|
20
|
+
if (lang === 'pt') return 'pt-BR';
|
|
21
|
+
if (lang === 'es') return 'es';
|
|
22
|
+
if (lang === 'fr') return 'fr';
|
|
23
|
+
return 'en';
|
|
24
|
+
}
|
|
6
25
|
const { getCliVersionSync } = require('../version');
|
|
7
26
|
const {
|
|
8
27
|
calculateClassification,
|
|
@@ -469,7 +488,7 @@ async function runSetupContext({ args, options, logger, t }) {
|
|
|
469
488
|
profile: 'developer',
|
|
470
489
|
framework: detectedFramework,
|
|
471
490
|
frameworkInstalled: detectedInstalled,
|
|
472
|
-
conversationLanguage:
|
|
491
|
+
conversationLanguage: detectSystemLanguage(),
|
|
473
492
|
designSkill: '',
|
|
474
493
|
testRunner: '',
|
|
475
494
|
web3Enabled: inferredWeb3Enabled,
|
|
@@ -674,5 +693,6 @@ module.exports = {
|
|
|
674
693
|
runSetupContext,
|
|
675
694
|
servicesToContextFields,
|
|
676
695
|
mergeProfileData,
|
|
677
|
-
applyExplicitOverrides
|
|
696
|
+
applyExplicitOverrides,
|
|
697
|
+
detectSystemLanguage
|
|
678
698
|
};
|