@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.
Files changed (131) hide show
  1. package/README.md +6 -0
  2. package/docs/design-previews/aurora-command-ui-website.html +884 -0
  3. package/docs/design-previews/aurora-command-ui.html +682 -0
  4. package/docs/design-previews/bold-editorial-ui-website.html +658 -0
  5. package/docs/design-previews/bold-editorial-ui.html +717 -0
  6. package/docs/design-previews/clean-saas-ui-website.html +1202 -0
  7. package/docs/design-previews/clean-saas-ui.html +549 -0
  8. package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
  9. package/docs/design-previews/cognitive-core-ui.html +463 -0
  10. package/docs/design-previews/glassmorphism-ui-website.html +572 -0
  11. package/docs/design-previews/glassmorphism-ui.html +886 -0
  12. package/docs/design-previews/index.html +699 -0
  13. package/docs/design-previews/interface-design-website.html +1187 -0
  14. package/docs/design-previews/interface-design.html +513 -0
  15. package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
  16. package/docs/design-previews/neo-brutalist-ui.html +797 -0
  17. package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
  18. package/docs/design-previews/premium-command-center-ui.html +552 -0
  19. package/docs/design-previews/warm-craft-ui-website.html +684 -0
  20. package/docs/design-previews/warm-craft-ui.html +739 -0
  21. package/docs/en/cli-reference.md +20 -9
  22. package/docs/pt/README.md +7 -0
  23. package/docs/pt/agent-sharding.md +132 -0
  24. package/docs/pt/agentes.md +8 -2
  25. package/docs/pt/busca-de-contexto.md +129 -0
  26. package/docs/pt/cache-de-contexto.md +156 -0
  27. package/docs/pt/comandos-cli.md +28 -0
  28. package/docs/pt/design-hybrid-forge.md +107 -0
  29. package/docs/pt/inicio-rapido.md +54 -3
  30. package/docs/pt/inteligencia-adaptativa.md +324 -0
  31. package/docs/pt/monitor-de-contexto.md +104 -0
  32. package/docs/pt/recuperacao-de-sessao.md +125 -0
  33. package/docs/pt/sandbox.md +125 -0
  34. package/docs/pt/skills.md +98 -6
  35. package/package.json +1 -1
  36. package/src/agent-loader.js +280 -0
  37. package/src/cli.js +94 -0
  38. package/src/commands/agent-loader.js +85 -0
  39. package/src/commands/context-cache.js +90 -0
  40. package/src/commands/context-monitor.js +92 -0
  41. package/src/commands/context-search.js +66 -0
  42. package/src/commands/design-hybrid-options.js +385 -0
  43. package/src/commands/health.js +214 -0
  44. package/src/commands/init.js +54 -13
  45. package/src/commands/install.js +52 -13
  46. package/src/commands/learning-evolve.js +355 -0
  47. package/src/commands/live.js +34 -0
  48. package/src/commands/recovery.js +43 -0
  49. package/src/commands/sandbox.js +37 -0
  50. package/src/commands/setup-context.js +22 -2
  51. package/src/commands/setup.js +178 -0
  52. package/src/commands/skill.js +79 -32
  53. package/src/commands/tool-registry-cmd.js +232 -0
  54. package/src/commands/update.js +7 -0
  55. package/src/constants.js +9 -0
  56. package/src/context-cache.js +159 -0
  57. package/src/context-search.js +326 -0
  58. package/src/design-variation-catalog.js +503 -0
  59. package/src/i18n/messages/en.js +32 -2
  60. package/src/i18n/messages/es.js +30 -2
  61. package/src/i18n/messages/fr.js +30 -2
  62. package/src/i18n/messages/pt-BR.js +32 -2
  63. package/src/install-animation.js +260 -0
  64. package/src/install-profile.js +143 -0
  65. package/src/install-wizard.js +474 -0
  66. package/src/installer.js +38 -10
  67. package/src/parser.js +7 -1
  68. package/src/recovery-context-session.js +154 -0
  69. package/src/runtime-store.js +97 -1
  70. package/src/sandbox.js +177 -0
  71. package/src/tool-executor.js +94 -0
  72. package/src/updater.js +11 -3
  73. package/template/.aioson/agents/analyst.md +58 -3
  74. package/template/.aioson/agents/architect.md +38 -0
  75. package/template/.aioson/agents/design-hybrid-forge.md +127 -0
  76. package/template/.aioson/agents/dev.md +103 -0
  77. package/template/.aioson/agents/deyvin.md +57 -0
  78. package/template/.aioson/agents/pm.md +58 -0
  79. package/template/.aioson/agents/product.md +28 -0
  80. package/template/.aioson/agents/qa.md +79 -0
  81. package/template/.aioson/agents/setup.md +65 -3
  82. package/template/.aioson/agents/sheldon.md +107 -6
  83. package/template/.aioson/agents/tester.md +156 -0
  84. package/template/.aioson/config.md +15 -0
  85. package/template/.aioson/context/forensics/.gitkeep +0 -0
  86. package/template/.aioson/context/seeds/seed-example.md +27 -0
  87. package/template/.aioson/context/user-profile.md +42 -0
  88. package/template/.aioson/locales/en/agents/setup.md +33 -1
  89. package/template/.aioson/locales/es/agents/setup.md +33 -1
  90. package/template/.aioson/locales/fr/agents/setup.md +33 -1
  91. package/template/.aioson/locales/pt-BR/agents/setup.md +33 -1
  92. package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
  93. package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
  94. package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
  95. package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
  96. package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
  97. package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
  98. package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
  99. package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
  100. package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
  101. package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
  102. package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
  103. package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
  104. package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
  105. package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
  106. package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
  107. package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
  108. package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
  109. package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
  110. package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
  111. package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
  112. package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
  113. package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
  114. package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
  115. package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
  116. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +45 -0
  117. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
  118. package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
  119. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
  120. package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
  121. package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +66 -0
  122. package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
  123. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +144 -0
  124. package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
  125. package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
  126. package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +291 -0
  127. package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +117 -0
  128. package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +188 -0
  129. package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
  130. package/template/AGENTS.md +23 -1
  131. package/template/CLAUDE.md +1 -0
@@ -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: force,
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 (requestedLanguage) {
35
- localeApply = await applyAgentLocale(targetDir, requestedLanguage, { dryRun });
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
- logger.log(t('install.done_at', { targetDir }));
44
- logger.log(t('install.files_copied', { count: result.copied.length }));
45
- logger.log(t('install.files_skipped', { count: result.skipped.length }));
46
- logger.log(t('install.next_steps'));
47
- logger.log(t('install.step_setup_context'));
48
- logger.log(t('install.step_agents'));
49
- logger.log(t('install.step_agent_prompt', { tool: promptTool }));
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 };
@@ -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: 'en',
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
  };