@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
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { execFile } = require('node:child_process');
6
+ const { promisify } = require('node:util');
7
+
8
+ const execFileAsync = promisify(execFile);
9
+
10
+ const CONTEXT_DIR = path.join('.aioson', 'context');
11
+ const RECOVERY_FILE = 'recovery-context.md';
12
+ const MAX_TOKENS = 2000;
13
+
14
+ function estimateTokens(str) {
15
+ return Math.ceil(str.length / 4);
16
+ }
17
+
18
+ async function readRecentGitLog(cwd, limit = 10) {
19
+ try {
20
+ const { stdout } = await execFileAsync('git', [
21
+ 'log', `--max-count=${limit}`, '--oneline', '--no-merges'
22
+ ], { cwd });
23
+ return stdout.trim().split('\n').filter(Boolean);
24
+ } catch {
25
+ return [];
26
+ }
27
+ }
28
+
29
+ async function readModifiedFiles(cwd) {
30
+ try {
31
+ const { stdout } = await execFileAsync('git', [
32
+ 'diff', '--name-only', 'HEAD'
33
+ ], { cwd });
34
+ return stdout.trim().split('\n').filter(Boolean);
35
+ } catch {
36
+ return [];
37
+ }
38
+ }
39
+
40
+ function buildSessionRecoveryMarkdown(sessionState, gitLog, modifiedFiles) {
41
+ const lines = [];
42
+
43
+ lines.push('# Recovery Context — Direct Session');
44
+ lines.push(`> Generated: ${new Date().toISOString()}`);
45
+ lines.push('');
46
+
47
+ // Session goal/task
48
+ if (sessionState.goal) {
49
+ lines.push('## Current Goal');
50
+ lines.push(sessionState.goal);
51
+ lines.push('');
52
+ }
53
+
54
+ // Active agent
55
+ if (sessionState.agent) {
56
+ lines.push('## Active Agent');
57
+ lines.push(sessionState.agent);
58
+ lines.push('');
59
+ }
60
+
61
+ // Tasks
62
+ const tasks = Array.isArray(sessionState.tasks) ? sessionState.tasks : [];
63
+ if (tasks.length > 0) {
64
+ lines.push('## Tasks');
65
+ for (const t of tasks.slice(-5)) {
66
+ const status = t.status || 'unknown';
67
+ const title = t.title || t.id || '(untitled)';
68
+ lines.push(`- [${status}] ${title}`);
69
+ }
70
+ lines.push('');
71
+ }
72
+
73
+ // Notes
74
+ const notes = Array.isArray(sessionState.notes) ? sessionState.notes : [];
75
+ if (notes.length > 0) {
76
+ lines.push('## Notes');
77
+ for (const note of notes.slice(-5)) {
78
+ lines.push(`- ${note}`);
79
+ }
80
+ lines.push('');
81
+ }
82
+
83
+ // Modified files
84
+ if (modifiedFiles.length > 0) {
85
+ lines.push('## Modified Files');
86
+ for (const f of modifiedFiles.slice(0, 10)) {
87
+ lines.push(`- ${f}`);
88
+ }
89
+ lines.push('');
90
+ }
91
+
92
+ // Recent git commits
93
+ if (gitLog.length > 0) {
94
+ lines.push('## Recent Commits');
95
+ for (const entry of gitLog.slice(0, 5)) {
96
+ lines.push(`- ${entry}`);
97
+ }
98
+ lines.push('');
99
+ }
100
+
101
+ lines.push('---');
102
+ lines.push('*Inject this file at the top of your next session to restore context after a compact.*');
103
+
104
+ return lines.join('\n');
105
+ }
106
+
107
+ /**
108
+ * Generate and write recovery-context.md for a direct session (no squad).
109
+ * @param {string} cwd — project root directory
110
+ * @param {object} sessionState — { goal?, agent?, tasks?, notes? }
111
+ * @returns {{ ok: boolean, path: string, tokens: number }}
112
+ */
113
+ async function generateSessionRecovery(cwd, sessionState = {}) {
114
+ const [gitLog, modifiedFiles] = await Promise.all([
115
+ readRecentGitLog(cwd),
116
+ readModifiedFiles(cwd)
117
+ ]);
118
+
119
+ let content = buildSessionRecoveryMarkdown(sessionState, gitLog, modifiedFiles);
120
+
121
+ // Enforce token budget
122
+ if (estimateTokens(content) > MAX_TOKENS) {
123
+ content = buildSessionRecoveryMarkdown(sessionState, gitLog.slice(0, 3), modifiedFiles.slice(0, 5));
124
+ }
125
+
126
+ const tokens = estimateTokens(content);
127
+ const outDir = path.join(cwd, CONTEXT_DIR);
128
+ const outPath = path.join(outDir, RECOVERY_FILE);
129
+
130
+ try {
131
+ await fs.mkdir(outDir, { recursive: true });
132
+ await fs.writeFile(outPath, content, 'utf8');
133
+ } catch (err) {
134
+ return { ok: false, error: err.message, path: outPath, tokens };
135
+ }
136
+
137
+ return { ok: true, path: outPath, tokens };
138
+ }
139
+
140
+ /**
141
+ * Read the current session recovery-context.md (returns null if missing).
142
+ * @param {string} cwd
143
+ * @returns {string|null}
144
+ */
145
+ async function readSessionRecovery(cwd) {
146
+ const p = path.join(cwd, CONTEXT_DIR, RECOVERY_FILE);
147
+ try {
148
+ return await fs.readFile(p, 'utf8');
149
+ } catch {
150
+ return null;
151
+ }
152
+ }
153
+
154
+ module.exports = { generateSessionRecovery, readSessionRecovery };
@@ -693,6 +693,34 @@ function ensureLegacyColumns(db) {
693
693
  }
694
694
 
695
695
  try { db.exec('ALTER TABLE worker_runs ADD COLUMN conversation_id TEXT'); } catch { /* já existe */ }
696
+
697
+ // Dynamic Tools (Feature: Tool Registry)
698
+ db.exec(`
699
+ CREATE TABLE IF NOT EXISTS dynamic_tools (
700
+ name TEXT PRIMARY KEY,
701
+ description TEXT NOT NULL,
702
+ input_schema TEXT NOT NULL DEFAULT '{}',
703
+ handler_type TEXT NOT NULL DEFAULT 'shell',
704
+ handler_code TEXT,
705
+ handler_path TEXT,
706
+ squad_slug TEXT,
707
+ registered_at TEXT NOT NULL DEFAULT (datetime('now')),
708
+ registered_by TEXT
709
+ );
710
+ CREATE INDEX IF NOT EXISTS idx_dynamic_tools_squad ON dynamic_tools(squad_slug);
711
+ `);
712
+
713
+ // Evolution Log (Feature: Learning Evolution Pipeline)
714
+ db.exec(`
715
+ CREATE TABLE IF NOT EXISTS evolution_log (
716
+ id TEXT PRIMARY KEY,
717
+ applied_at TEXT NOT NULL,
718
+ deltas_count INTEGER NOT NULL DEFAULT 0,
719
+ squad_slug TEXT,
720
+ files_json TEXT,
721
+ source_learning_ids_json TEXT
722
+ );
723
+ `);
696
724
  }
697
725
 
698
726
  function insertEvent(db, record) {
@@ -2425,6 +2453,66 @@ function deleteROIConfig(db, squadSlug) {
2425
2453
  return db.prepare('DELETE FROM squad_roi_config WHERE squad_slug = ?').run(squadSlug);
2426
2454
  }
2427
2455
 
2456
+ // ─── Dynamic Tools CRUD ───────────────────────────────────────────────────────
2457
+
2458
+ function registerDynamicTool(db, opts) {
2459
+ const now = nowIso();
2460
+ db.prepare(`
2461
+ INSERT OR REPLACE INTO dynamic_tools
2462
+ (name, description, input_schema, handler_type, handler_code, handler_path, squad_slug, registered_at, registered_by)
2463
+ VALUES
2464
+ (@name, @description, @inputSchema, @handlerType, @handlerCode, @handlerPath, @squadSlug, @registeredAt, @registeredBy)
2465
+ `).run({
2466
+ name: String(opts.name),
2467
+ description: String(opts.description),
2468
+ inputSchema: opts.inputSchema ? JSON.stringify(opts.inputSchema) : '{}',
2469
+ handlerType: String(opts.handlerType || 'shell'),
2470
+ handlerCode: opts.handlerCode || null,
2471
+ handlerPath: opts.handlerPath || null,
2472
+ squadSlug: opts.squadSlug || null,
2473
+ registeredAt: now,
2474
+ registeredBy: opts.registeredBy || null
2475
+ });
2476
+ return opts.name;
2477
+ }
2478
+
2479
+ function unregisterDynamicTool(db, name) {
2480
+ return db.prepare('DELETE FROM dynamic_tools WHERE name = ?').run(name);
2481
+ }
2482
+
2483
+ function getDynamicTool(db, name) {
2484
+ return db.prepare('SELECT * FROM dynamic_tools WHERE name = ?').get(name) || null;
2485
+ }
2486
+
2487
+ function listDynamicTools(db, squadSlug = null) {
2488
+ if (squadSlug) {
2489
+ return db.prepare('SELECT * FROM dynamic_tools WHERE squad_slug = ? ORDER BY registered_at DESC').all(squadSlug);
2490
+ }
2491
+ return db.prepare('SELECT * FROM dynamic_tools ORDER BY registered_at DESC').all();
2492
+ }
2493
+
2494
+ // ─── Evolution Log CRUD ───────────────────────────────────────────────────────
2495
+
2496
+ function insertEvolutionLog(db, opts) {
2497
+ const id = `evo-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
2498
+ db.prepare(`
2499
+ INSERT INTO evolution_log (id, applied_at, deltas_count, squad_slug, files_json, source_learning_ids_json)
2500
+ VALUES (@id, @appliedAt, @deltasCount, @squadSlug, @filesJson, @sourceIdsJson)
2501
+ `).run({
2502
+ id,
2503
+ appliedAt: nowIso(),
2504
+ deltasCount: Number(opts.deltasCount || 0),
2505
+ squadSlug: opts.squadSlug || null,
2506
+ filesJson: JSON.stringify(opts.files || []),
2507
+ sourceIdsJson: JSON.stringify(opts.sourceLearningIds || [])
2508
+ });
2509
+ return id;
2510
+ }
2511
+
2512
+ function listEvolutionLog(db, limit = 20) {
2513
+ return db.prepare('SELECT * FROM evolution_log ORDER BY applied_at DESC LIMIT ?').all(limit);
2514
+ }
2515
+
2428
2516
  module.exports = {
2429
2517
  resolveRuntimePaths,
2430
2518
  runtimeStoreExists,
@@ -2518,5 +2606,13 @@ module.exports = {
2518
2606
  // ROI Config CRUD
2519
2607
  upsertROIConfig,
2520
2608
  getROIConfig,
2521
- deleteROIConfig
2609
+ deleteROIConfig,
2610
+ // Dynamic Tools CRUD
2611
+ registerDynamicTool,
2612
+ unregisterDynamicTool,
2613
+ getDynamicTool,
2614
+ listDynamicTools,
2615
+ // Evolution Log CRUD
2616
+ insertEvolutionLog,
2617
+ listEvolutionLog
2522
2618
  };
package/src/sandbox.js ADDED
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('node:child_process');
4
+
5
+ const DEFAULT_TIMEOUT_MS = 30_000;
6
+ const MAX_OUTPUT_BYTES = 5 * 1024; // 5KB before summarization
7
+
8
+ // Credential redaction patterns — covers the most common secret formats
9
+ const REDACTION_PATTERNS = [
10
+ // GitHub tokens (classic and fine-grained)
11
+ { pattern: /ghp_[A-Za-z0-9]{36}/g, replacement: 'ghp_[REDACTED]' },
12
+ { pattern: /github_pat_[A-Za-z0-9_]{82}/g, replacement: 'github_pat_[REDACTED]' },
13
+ // AWS access keys
14
+ { pattern: /AKIA[0-9A-Z]{16}/g, replacement: 'AKIA[REDACTED]' },
15
+ // Google OAuth tokens
16
+ { pattern: /ya29\.[A-Za-z0-9_\-]{50,}/g, replacement: 'ya29.[REDACTED]' },
17
+ // Generic Bearer tokens in headers
18
+ { pattern: /Bearer\s+[A-Za-z0-9\-._~+\/]+=*/gi, replacement: 'Bearer [REDACTED]' },
19
+ // Password in URL (e.g. postgres://user:password@host)
20
+ { pattern: /:[^/:@\s]{4,}@[a-z0-9.\-]+(?::\d+)?/gi, replacement: ':[REDACTED]@host' },
21
+ // Generic password= key=value pairs
22
+ { pattern: /password\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'password=[REDACTED]' },
23
+ { pattern: /passwd\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'passwd=[REDACTED]' },
24
+ // secret= key=value pairs
25
+ { pattern: /secret\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'secret=[REDACTED]' },
26
+ // api_key= or apikey= patterns
27
+ { pattern: /api[_-]?key\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'api_key=[REDACTED]' },
28
+ // Private key blocks
29
+ { pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g, replacement: '-----BEGIN PRIVATE KEY [REDACTED] END PRIVATE KEY-----' },
30
+ ];
31
+
32
+ /**
33
+ * Redact known credential patterns from a string.
34
+ * @param {string} text
35
+ * @returns {string}
36
+ */
37
+ function redactCredentials(text) {
38
+ if (!text) return text;
39
+ let result = text;
40
+ for (const { pattern, replacement } of REDACTION_PATTERNS) {
41
+ result = result.replace(pattern, replacement);
42
+ }
43
+ return result;
44
+ }
45
+
46
+ /**
47
+ * Summarize long output to stay within size budget.
48
+ * @param {string} output
49
+ * @param {string} intent — optional context about what was executed
50
+ * @param {number} maxSize — max bytes to return
51
+ * @returns {string}
52
+ */
53
+ function summarizeOutput(output, intent = '', maxSize = MAX_OUTPUT_BYTES) {
54
+ if (!output || output.length <= maxSize) return output;
55
+
56
+ const half = Math.floor(maxSize / 2);
57
+ const head = output.slice(0, half);
58
+ const tail = output.slice(-half);
59
+ const omitted = output.length - maxSize;
60
+ const intentNote = intent ? ` (${intent})` : '';
61
+
62
+ return `${head}\n\n[... ${omitted} bytes omitted${intentNote} ...]\n\n${tail}`;
63
+ }
64
+
65
+ /**
66
+ * Execute a shell command in a sandboxed subprocess with timeout and redaction.
67
+ *
68
+ * @param {string} command — shell command to run
69
+ * @param {object} opts — { cwd?, timeout?, env?, maxOutput?, intent?, shell? }
70
+ * @returns {{ ok: boolean, stdout: string, stderr: string, exitCode: number|null, timedOut: boolean }}
71
+ */
72
+ async function executeInSandbox(command, opts = {}) {
73
+ const timeout = opts.timeout || DEFAULT_TIMEOUT_MS;
74
+ const cwd = opts.cwd || process.cwd();
75
+ const maxOutput = opts.maxOutput || MAX_OUTPUT_BYTES;
76
+ const intent = opts.intent || command.slice(0, 60);
77
+ const shell = opts.shell !== false; // default true
78
+
79
+ return new Promise((resolve) => {
80
+ const controller = new AbortController();
81
+ let timedOut = false;
82
+ let killed = false;
83
+
84
+ const timer = setTimeout(() => {
85
+ timedOut = true;
86
+ controller.abort();
87
+ }, timeout);
88
+
89
+ const stdoutChunks = [];
90
+ const stderrChunks = [];
91
+ let stdoutSize = 0;
92
+ let stderrSize = 0;
93
+
94
+ const baseOpts = {
95
+ cwd,
96
+ env: { ...process.env, ...(opts.env || {}) },
97
+ signal: controller.signal
98
+ };
99
+
100
+ let child;
101
+ try {
102
+ if (shell) {
103
+ // Use Node's built-in shell wrapping
104
+ child = spawn(command, [], { ...baseOpts, shell: true });
105
+ } else {
106
+ const parts = command.split(/\s+/);
107
+ child = spawn(parts[0], parts.slice(1), { ...baseOpts, shell: false });
108
+ }
109
+ } catch (err) {
110
+ clearTimeout(timer);
111
+ resolve({ ok: false, stdout: '', stderr: err.message, exitCode: null, timedOut: false, error: err.message });
112
+ return;
113
+ }
114
+
115
+ child.stdout.on('data', (chunk) => {
116
+ if (stdoutSize < maxOutput * 2) {
117
+ stdoutChunks.push(chunk);
118
+ stdoutSize += chunk.length;
119
+ }
120
+ });
121
+
122
+ child.stderr.on('data', (chunk) => {
123
+ if (stderrSize < maxOutput * 2) {
124
+ stderrChunks.push(chunk);
125
+ stderrSize += chunk.length;
126
+ }
127
+ });
128
+
129
+ child.on('close', (code, signal) => {
130
+ if (killed) return;
131
+ clearTimeout(timer);
132
+
133
+ const rawStdout = Buffer.concat(stdoutChunks).toString('utf8');
134
+ const rawStderr = Buffer.concat(stderrChunks).toString('utf8');
135
+
136
+ const stdout = redactCredentials(summarizeOutput(rawStdout, intent, maxOutput));
137
+ const stderr = redactCredentials(summarizeOutput(rawStderr, intent, maxOutput));
138
+
139
+ resolve({
140
+ ok: !timedOut && code === 0,
141
+ stdout,
142
+ stderr,
143
+ exitCode: code,
144
+ timedOut,
145
+ signal: signal || null
146
+ });
147
+ });
148
+
149
+ child.on('error', (err) => {
150
+ if (killed) return;
151
+ clearTimeout(timer);
152
+
153
+ if (err.code === 'ABORT_ERR' || timedOut) {
154
+ resolve({
155
+ ok: false,
156
+ stdout: '',
157
+ stderr: `Command timed out after ${timeout}ms`,
158
+ exitCode: null,
159
+ timedOut: true,
160
+ signal: null
161
+ });
162
+ } else {
163
+ resolve({
164
+ ok: false,
165
+ stdout: '',
166
+ stderr: err.message,
167
+ exitCode: null,
168
+ timedOut: false,
169
+ error: err.message
170
+ });
171
+ }
172
+ killed = true;
173
+ });
174
+ });
175
+ }
176
+
177
+ module.exports = { executeInSandbox, redactCredentials, summarizeOutput };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ const { spawnSync } = require('node:child_process');
4
+ const path = require('node:path');
5
+
6
+ const SAFE_ENV_KEYS = new Set(['PATH', 'HOME', 'LANG', 'TERM', 'USER', 'SHELL', 'TMPDIR', 'TMP', 'TEMP']);
7
+ const DEFAULT_TIMEOUT_MS = 30_000;
8
+
9
+ function buildSafeEnv(inputJson) {
10
+ const safe = {};
11
+ for (const key of SAFE_ENV_KEYS) {
12
+ if (process.env[key] !== undefined) {
13
+ safe[key] = process.env[key];
14
+ }
15
+ }
16
+ safe.TOOL_INPUT = typeof inputJson === 'string' ? inputJson : JSON.stringify(inputJson ?? {});
17
+ return safe;
18
+ }
19
+
20
+ /**
21
+ * Executa uma dynamic tool de forma segura via subprocess.
22
+ * @param {object} toolDef - Registro da tool (da tabela dynamic_tools)
23
+ * @param {object|string} inputJson - Input para a tool
24
+ * @param {object} opts
25
+ * @param {string} [opts.projectDir] - Diretório do projeto (para resolver caminhos relativos)
26
+ * @param {number} [opts.timeoutMs] - Timeout em ms (default: 30s)
27
+ * @returns {{ ok: boolean, stdout: string, stderr: string, exitCode: number, error?: string }}
28
+ */
29
+ function executeTool(toolDef, inputJson, opts = {}) {
30
+ const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
31
+ const projectDir = opts.projectDir || process.cwd();
32
+ const safeEnv = buildSafeEnv(inputJson);
33
+
34
+ let result;
35
+
36
+ if (toolDef.handler_type === 'shell') {
37
+ if (!toolDef.handler_code) {
38
+ return { ok: false, stdout: '', stderr: '', exitCode: 1, error: 'handler_code is required for shell tools' };
39
+ }
40
+ result = spawnSync('bash', ['-c', toolDef.handler_code], {
41
+ env: safeEnv,
42
+ encoding: 'utf8',
43
+ timeout: timeoutMs,
44
+ maxBuffer: 1024 * 1024
45
+ });
46
+ } else if (toolDef.handler_type === 'script') {
47
+ if (!toolDef.handler_path) {
48
+ return { ok: false, stdout: '', stderr: '', exitCode: 1, error: 'handler_path is required for script tools' };
49
+ }
50
+ const scriptPath = path.isAbsolute(toolDef.handler_path)
51
+ ? toolDef.handler_path
52
+ : path.resolve(projectDir, toolDef.handler_path);
53
+
54
+ result = spawnSync('node', ['--env-file=', scriptPath], {
55
+ env: safeEnv,
56
+ encoding: 'utf8',
57
+ timeout: timeoutMs,
58
+ maxBuffer: 1024 * 1024,
59
+ input: safeEnv.TOOL_INPUT
60
+ });
61
+
62
+ // Fallback: node sem --env-file= (versões antigas do Node não suportam)
63
+ if (result.error && result.error.code === 'ERR_INVALID_ARG_VALUE') {
64
+ result = spawnSync('node', [scriptPath], {
65
+ env: safeEnv,
66
+ encoding: 'utf8',
67
+ timeout: timeoutMs,
68
+ maxBuffer: 1024 * 1024,
69
+ input: safeEnv.TOOL_INPUT
70
+ });
71
+ }
72
+ } else {
73
+ return { ok: false, stdout: '', stderr: '', exitCode: 1, error: `Unknown handler_type: ${toolDef.handler_type}` };
74
+ }
75
+
76
+ const stdout = String(result.stdout || '').trim();
77
+ const stderr = String(result.stderr || '').trim();
78
+ const exitCode = result.status ?? 1;
79
+
80
+ if (result.error) {
81
+ const isTimeout = result.error.code === 'ETIMEDOUT' || result.error.killed;
82
+ return {
83
+ ok: false,
84
+ stdout,
85
+ stderr,
86
+ exitCode,
87
+ error: isTimeout ? `Tool timed out after ${timeoutMs}ms` : result.error.message
88
+ };
89
+ }
90
+
91
+ return { ok: exitCode === 0, stdout, stderr, exitCode };
92
+ }
93
+
94
+ module.exports = { executeTool, buildSafeEnv };
package/src/updater.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('node:path');
4
- const { detectExistingInstall, installTemplate } = require('./installer');
4
+ const { detectExistingInstall, installTemplate, readInstallProfile } = require('./installer');
5
5
 
6
6
  async function updateInstallation(targetDir, options = {}) {
7
7
  const installed = await detectExistingInstall(targetDir);
@@ -13,17 +13,25 @@ async function updateInstallation(targetDir, options = {}) {
13
13
  };
14
14
  }
15
15
 
16
+ const savedProfile = await readInstallProfile(targetDir);
17
+
18
+ // During update, pass null profile so ALL framework files are installed
19
+ // (not just those matching the saved profile).
20
+ // This ensures new framework files from the new version are always installed.
21
+ // Profile-based filtering only applies to init/install.
16
22
  const result = await installTemplate(targetDir, {
17
23
  overwrite: true,
18
24
  dryRun: Boolean(options.dryRun),
19
25
  mode: 'update',
20
26
  backupOnOverwrite: true,
21
- frameworkDetection: options.frameworkDetection || null
27
+ frameworkDetection: options.frameworkDetection || null,
28
+ installProfile: null
22
29
  });
23
30
 
24
31
  return {
25
32
  ok: true,
26
- ...result
33
+ ...result,
34
+ savedProfile
27
35
  };
28
36
  }
29
37
 
@@ -40,6 +40,25 @@ Check the following before doing anything else:
40
40
  - `.aioson/context/design-doc.md` + `readiness.md` (if present)
41
41
  - `.aioson/context/discovery.md` + `spec.md` (feature mode — project context, if present)
42
42
 
43
+ ## Context loading policy
44
+
45
+ **Sempre carregar:**
46
+ - `.aioson/context/project.context.md`
47
+ - `prd*.md` ou `prd-{slug}.md` relevante
48
+ - `sheldon-enrichment-{slug}.md` (se existir)
49
+
50
+ **Carregar só se relevante ao scope:**
51
+ - `architecture.md` (brownfield apenas)
52
+ - skills do domínio atual
53
+
54
+ **Nunca carregar:**
55
+ - Arquivos de implementação (src/, routes/, etc.)
56
+ - Specs de features não relacionadas
57
+
58
+ ## Disk-first principle
59
+
60
+ Escreva `discovery.md` ou `requirements-{slug}.md` no disco antes de retornar qualquer resposta ao usuário. Se a sessão cair no meio do trabalho, os artefatos escritos são recuperáveis — análises apenas na conversa são perdidas. Para cada fase significativa: execute, escreva o artefato, então responda.
61
+
43
62
  ## Context integrity
44
63
 
45
64
  Read `project.context.md` before starting discovery.
@@ -91,6 +110,8 @@ Before deepening discovery:
91
110
  - use `readiness.md` to avoid unnecessary rediscovery
92
111
  - load only the docs that actually matter for this batch
93
112
  - consult local skills only when they improve domain mapping or flow clarity
113
+ - check `.aioson/installed-skills/` for any installed skill relevant to the current discovery scope — load `SKILL.md` of matching skills, then load per-agent references only if they reduce ambiguity for the current phase
114
+ - if `aioson-spec-driven` is installed (`.aioson/installed-skills/aioson-spec-driven/SKILL.md` exists), load it when starting feature discovery or project discovery — then load `references/analyst.md` from that skill
94
115
 
95
116
  Do not inflate context without need.
96
117
 
@@ -175,8 +196,15 @@ For each new or modified entity, produce field-level detail (same format as Phas
175
196
  4. Relationships (with existing entities from discovery.md)
176
197
  5. Migration additions (ordered)
177
198
  6. Business rules
178
- 7. Edge cases
179
- 8. Out of scope for this feature
199
+ - Use format: `REQ-{slug}-{N}` for each rule (e.g., `REQ-checkout-01`)
200
+ - Each rule must state: condition + expected behavior + who can trigger it
201
+ 7. Acceptance criteria
202
+ - Use format: `AC-{slug}-{N}` (e.g., `AC-checkout-01`)
203
+ - Each AC must be independently verifiable by QA without implementation knowledge
204
+ 8. Edge cases and failure modes
205
+ - Cover: invalid input, empty states, concurrent operations, external service failure
206
+ 9. Out of scope for this feature
207
+ - Be explicit — list what was deliberately excluded and why
180
208
 
181
209
  **`spec-{slug}.md`** — feature memory skeleton (will be enriched by @dev):
182
210
 
@@ -185,6 +213,12 @@ For each new or modified entity, produce field-level detail (same format as Phas
185
213
  feature: {slug}
186
214
  status: in_progress
187
215
  started: {ISO-date}
216
+ phase_gates:
217
+ requirements: approved # approved | pending | needs_work
218
+ design: pending # approved | pending | skipped (MICRO/SMALL sem @architect)
219
+ plan: pending # approved | pending | skipped (MICRO sem implementation-plan)
220
+ last_checkpoint: null # filled by @dev after each completed phase
221
+ pending_review: [] # items that need human review before next phase
188
222
  ---
189
223
 
190
224
  # Spec — {Feature Name}
@@ -209,7 +243,19 @@ started: {ISO-date}
209
243
  [Anything @dev or @qa should know before touching this feature]
210
244
  ```
211
245
 
212
- After producing both files, tell the user: "Feature spec ready. Activate **@dev** to implement it will read `prd-{slug}.md`, `requirements-{slug}.md`, and `spec-{slug}.md`."
246
+ After producing both files, use `AskUserQuestion` with `multiSelect: true` to confirm which requirements are approved:
247
+
248
+ ```
249
+ AskUserQuestion:
250
+ question: "Quais requirements estão aprovados para prosseguir?"
251
+ multiSelect: true
252
+ options:
253
+ - label: "REQ-{slug}-01: [título]"
254
+ - label: "REQ-{slug}-02: [título]"
255
+ - label: "Todos aprovados"
256
+ ```
257
+
258
+ Then tell the user: "Feature spec ready. Activate **@dev** to implement — it will read `prd-{slug}.md`, `requirements-{slug}.md`, and `spec-{slug}.md`."
213
259
 
214
260
  ## MICRO shortcut
215
261
  If classification is MICRO (score 0–1) or the user describes a clearly single-entity project with no integrations, adapt the process:
@@ -254,3 +300,12 @@ Generate `.aioson/context/discovery.md` with the following sections:
254
300
  - If `readiness.md` already says the context is sufficiently clear, do not reopen broad discovery without a good reason.
255
301
  - At session end, after writing the discovery file, register the session: `aioson agent:done . --agent=analyst --summary="<one-line summary of discovery produced>" 2>/dev/null || true`
256
302
  - If `aioson` CLI is not available, write a devlog at session end following the "Devlog" section in `.aioson/config.md`.
303
+
304
+ ---
305
+ ## ▶ Próximo passo
306
+ **[@architect ou @dev]** — [SMALL/MEDIUM: @architect para decisões técnicas | MICRO: @dev direto]
307
+ Ative: `/architect` ou `/dev`
308
+ > Recomendado: `/clear` antes — janela de contexto fresca
309
+
310
+ Também disponível: `/sheldon` (enriquecimento adicional), `/qa` (revisão dos requisitos)
311
+ ---