@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
|
@@ -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 };
|
package/src/runtime-store.js
CHANGED
|
@@ -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
|
-
|
|
179
|
-
|
|
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,
|
|
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
|
+
---
|