@jaimevalasek/aioson 1.21.7 → 1.22.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/CHANGELOG.md +39 -2
- package/docs/en/1-understand/ecosystem-map.md +1 -1
- package/docs/en/2-start/initial-decisions.md +1 -1
- package/docs/en/4-agents/README.md +8 -7
- package/docs/en/4-agents/discovery-design-doc.md +150 -0
- package/docs/en/5-reference/cli-reference.md +42 -16
- package/docs/en/README.md +2 -2
- package/docs/pt/4-agentes/README.md +8 -6
- package/docs/pt/4-agentes/briefing-refiner.md +122 -0
- package/docs/pt/4-agentes/discovery-design-doc.md +133 -74
- package/docs/pt/4-agentes/scope-check.md +65 -0
- package/docs/pt/5-referencia/README.md +1 -0
- package/docs/pt/5-referencia/comandos-cli.md +5 -4
- package/docs/pt/5-referencia/feature-archive.md +1 -0
- package/docs/pt/5-referencia/feature-export.md +155 -0
- package/docs/pt/README.md +2 -2
- package/docs/pt/agentes.md +3 -1
- package/package.json +1 -1
- package/src/agent-manifests.js +14 -3
- package/src/agents.js +21 -20
- package/src/cli.js +72 -52
- package/src/commands/briefing.js +28 -150
- package/src/commands/commit-prepare.js +5 -2
- package/src/commands/feature-archive.js +48 -12
- package/src/commands/feature-close.js +40 -0
- package/src/commands/feature-export.js +242 -0
- package/src/commands/gate-check.js +8 -3
- package/src/commands/git-guard.js +58 -0
- package/src/commands/harness-gate.js +120 -0
- package/src/commands/harness-status.js +157 -0
- package/src/commands/harness.js +18 -1
- package/src/commands/live.js +120 -115
- package/src/commands/parallel-doctor.js +2 -1
- package/src/commands/pulse-update.js +2 -2
- package/src/commands/scan-project.js +12 -2
- package/src/commands/self-implement-loop.js +305 -5
- package/src/commands/workflow-next.js +477 -425
- package/src/constants.js +21 -11
- package/src/context-search.js +3 -0
- package/src/doctor.js +24 -8
- package/src/dossier/schema.js +4 -3
- package/src/harness/active-contract.js +41 -0
- package/src/harness/attempt-artifacts.js +95 -0
- package/src/harness/budget-guard.js +127 -0
- package/src/harness/circuit-breaker.js +7 -0
- package/src/harness/contract-schema.js +324 -0
- package/src/harness/criteria-runner.js +136 -0
- package/src/harness/git-baseline.js +204 -0
- package/src/harness/glob-match.js +126 -0
- package/src/harness/guard-events.js +71 -0
- package/src/harness/human-gate.js +182 -0
- package/src/harness/scope-guard.js +115 -0
- package/src/i18n/messages/en.js +24 -21
- package/src/i18n/messages/es.js +11 -9
- package/src/i18n/messages/fr.js +11 -9
- package/src/i18n/messages/pt-BR.js +24 -21
- package/src/lib/briefing-refiner/apply-feedback.js +134 -0
- package/src/lib/briefing-refiner/briefing-paths.js +41 -0
- package/src/lib/briefing-refiner/briefing-registry.js +204 -0
- package/src/lib/briefing-refiner/briefing-sections.js +110 -0
- package/src/lib/briefing-refiner/feedback-schema.js +122 -0
- package/src/lib/briefing-refiner/refinement-report.js +39 -0
- package/src/lib/briefing-refiner/review-html.js +230 -0
- package/src/lib/dev-resume.js +94 -45
- package/src/parser.js +8 -5
- package/src/preflight-engine.js +88 -84
- package/src/runtime-store.js +2 -0
- package/src/sandbox.js +17 -3
- package/template/.aioson/agents/analyst.md +27 -23
- package/template/.aioson/agents/architect.md +7 -3
- package/template/.aioson/agents/briefing-refiner.md +121 -0
- package/template/.aioson/agents/briefing.md +83 -74
- package/template/.aioson/agents/committer.md +8 -0
- package/template/.aioson/agents/copywriter.md +19 -7
- package/template/.aioson/agents/design-hybrid-forge.md +16 -5
- package/template/.aioson/agents/dev.md +68 -66
- package/template/.aioson/agents/deyvin.md +97 -90
- package/template/.aioson/agents/discover.md +2 -2
- package/template/.aioson/agents/discovery-design-doc.md +34 -30
- package/template/.aioson/agents/genome.md +82 -71
- package/template/.aioson/agents/neo.md +11 -3
- package/template/.aioson/agents/orache.md +10 -0
- package/template/.aioson/agents/orchestrator.md +68 -68
- package/template/.aioson/agents/pentester.md +15 -6
- package/template/.aioson/agents/pm.md +30 -25
- package/template/.aioson/agents/product.md +108 -108
- package/template/.aioson/agents/profiler-enricher.md +10 -0
- package/template/.aioson/agents/profiler-forge.md +10 -0
- package/template/.aioson/agents/profiler-researcher.md +11 -0
- package/template/.aioson/agents/qa.md +28 -20
- package/template/.aioson/agents/scope-check.md +176 -164
- package/template/.aioson/agents/setup.md +11 -1
- package/template/.aioson/agents/sheldon.md +38 -38
- package/template/.aioson/agents/site-forge.md +15 -6
- package/template/.aioson/agents/squad.md +12 -0
- package/template/.aioson/agents/tester.md +209 -209
- package/template/.aioson/agents/ux-ui.md +2 -2
- package/template/.aioson/agents/validator.md +10 -2
- package/template/.aioson/config.md +31 -28
- package/template/.aioson/docs/autopilot-handoff.md +46 -0
- package/template/.aioson/docs/dossier/agent-templates.md +191 -0
- package/template/.aioson/docs/dossier/schema.md +218 -0
- package/template/.claude/commands/aioson/agent/briefing-refiner.md +17 -0
- package/template/AGENTS.md +50 -47
- package/template/CLAUDE.md +29 -27
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Glob matcher mínimo e determinístico para o scope guard (loop-guardrails D1).
|
|
5
|
+
*
|
|
6
|
+
* Subset estrito suportado: `**`, `*`, `?` (incluindo `**` + `/` nas bordas).
|
|
7
|
+
* Qualquer sintaxe fora do subset (extglob `{}[]!()`, classes, negação) é
|
|
8
|
+
* REJEITADA pelo validador — nunca mismatch silencioso em fronteira de
|
|
9
|
+
* segurança. `picomatch` é o upgrade path documentado se o subset apertar.
|
|
10
|
+
*
|
|
11
|
+
* Semântica de caminho (decisão registrada em spec-loop-guardrails.md):
|
|
12
|
+
* - paths e patterns são normalizados para `/` antes do match (EC-6);
|
|
13
|
+
* - pattern SEM `/` casa contra o basename de qualquer profundidade
|
|
14
|
+
* (estilo gitignore: `*.pem` casa `certs/server.pem`);
|
|
15
|
+
* - pattern COM `/` casa contra o caminho relativo completo.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const INVALID_GLOB_CHARS = /[{}[\]()!]/;
|
|
19
|
+
|
|
20
|
+
/** Normaliza separadores para `/` e remove `./` inicial. */
|
|
21
|
+
function normalizePath(p) {
|
|
22
|
+
let out = String(p == null ? '' : p).replace(/\\/g, '/');
|
|
23
|
+
while (out.startsWith('./')) out = out.slice(2);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Valida um pattern contra o subset estrito.
|
|
29
|
+
* Retorna { ok: true } ou { ok: false, reason }.
|
|
30
|
+
*/
|
|
31
|
+
function validateGlobPattern(pattern) {
|
|
32
|
+
if (typeof pattern !== 'string' || pattern.trim() === '') {
|
|
33
|
+
return { ok: false, reason: 'pattern must be a non-empty string' };
|
|
34
|
+
}
|
|
35
|
+
const normalized = normalizePath(pattern);
|
|
36
|
+
const invalid = normalized.match(INVALID_GLOB_CHARS);
|
|
37
|
+
if (invalid) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
reason: `unsupported glob syntax "${invalid[0]}" — only **, * and ? are allowed (strict subset)`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { ok: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const REGEX_SPECIALS = /[.+^$|]/g;
|
|
47
|
+
|
|
48
|
+
/** Compila um pattern (já validado) para RegExp anchored. */
|
|
49
|
+
function globToRegExp(pattern) {
|
|
50
|
+
const normalized = normalizePath(pattern);
|
|
51
|
+
let regex = '';
|
|
52
|
+
let i = 0;
|
|
53
|
+
while (i < normalized.length) {
|
|
54
|
+
const ch = normalized[i];
|
|
55
|
+
if (ch === '*') {
|
|
56
|
+
if (normalized[i + 1] === '*') {
|
|
57
|
+
// `**` — atravessa separadores
|
|
58
|
+
const prev = normalized[i - 1];
|
|
59
|
+
const next = normalized[i + 2];
|
|
60
|
+
if ((prev === undefined || prev === '/') && next === '/') {
|
|
61
|
+
// `**/` no início ou após `/` — zero ou mais segmentos completos
|
|
62
|
+
regex += '(?:[^/]+/)*';
|
|
63
|
+
i += 3;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (next === undefined && (prev === undefined || prev === '/')) {
|
|
67
|
+
// `/**` no fim ou pattern `**` puro — qualquer resto (inclusive vazio? não:
|
|
68
|
+
// `secrets/**` exige algo dentro de secrets/; `**` puro casa tudo)
|
|
69
|
+
regex += prev === '/' ? '.+' : '.*';
|
|
70
|
+
i += 2;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// `**` colado em texto (ex.: `a**b`) — trata como `.*`
|
|
74
|
+
regex += '.*';
|
|
75
|
+
i += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
regex += '[^/]*';
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (ch === '?') {
|
|
83
|
+
regex += '[^/]';
|
|
84
|
+
i += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
regex += ch.replace(REGEX_SPECIALS, '\\$&');
|
|
88
|
+
i += 1;
|
|
89
|
+
}
|
|
90
|
+
return new RegExp(`^${regex}$`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Casa um path contra um pattern do subset.
|
|
95
|
+
* Pattern sem `/` casa contra o basename (estilo gitignore).
|
|
96
|
+
*/
|
|
97
|
+
function matchGlob(pattern, filePath) {
|
|
98
|
+
const normalizedPattern = normalizePath(pattern);
|
|
99
|
+
const normalizedPath = normalizePath(filePath);
|
|
100
|
+
if (!normalizedPattern || !normalizedPath) return false;
|
|
101
|
+
|
|
102
|
+
if (!normalizedPattern.includes('/')) {
|
|
103
|
+
const basename = normalizedPath.split('/').pop();
|
|
104
|
+
return globToRegExp(normalizedPattern).test(basename);
|
|
105
|
+
}
|
|
106
|
+
return globToRegExp(normalizedPattern).test(normalizedPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Retorna o primeiro pattern da lista que casa o path, ou null.
|
|
111
|
+
*/
|
|
112
|
+
function matchAny(patterns, filePath) {
|
|
113
|
+
if (!Array.isArray(patterns)) return null;
|
|
114
|
+
for (const pattern of patterns) {
|
|
115
|
+
if (matchGlob(pattern, filePath)) return pattern;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
normalizePath,
|
|
122
|
+
validateGlobPattern,
|
|
123
|
+
globToRegExp,
|
|
124
|
+
matchGlob,
|
|
125
|
+
matchAny
|
|
126
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Telemetria dos guards do self:loop (loop-guardrails D6).
|
|
5
|
+
*
|
|
6
|
+
* Helper único de emissão para os tipos de evento novos (requirements §2.5):
|
|
7
|
+
* scope_violation, budget_warning, budget_exceeded, runtime_exceeded,
|
|
8
|
+
* human_gate_requested, human_gate_decision, criteria_check_failed,
|
|
9
|
+
* failure_signature_repeat, contract_invalid, diff_limit_exceeded.
|
|
10
|
+
*
|
|
11
|
+
* Sempre best-effort (espelha BR-NC-11 / neural-chain-telemetry): telemetria
|
|
12
|
+
* NUNCA quebra o loop. `token_count` carrega a estimativa chars/4 quando o
|
|
13
|
+
* evento é de tentativa (REQ-7) — telemetria apenas; enforcement lê o
|
|
14
|
+
* acumulador em progress.json (D3).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const GUARD_EVENT_TYPES = Object.freeze([
|
|
18
|
+
'scope_violation',
|
|
19
|
+
'budget_warning',
|
|
20
|
+
'budget_exceeded',
|
|
21
|
+
'runtime_exceeded',
|
|
22
|
+
'human_gate_requested',
|
|
23
|
+
'human_gate_decision',
|
|
24
|
+
'criteria_check_failed',
|
|
25
|
+
'failure_signature_repeat',
|
|
26
|
+
'contract_invalid',
|
|
27
|
+
'diff_limit_exceeded'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Emite um evento de guard no runtime store. Nunca lança.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} targetDir — raiz do projeto
|
|
34
|
+
* @param {object} event
|
|
35
|
+
* @param {string} event.eventType — um de GUARD_EVENT_TYPES
|
|
36
|
+
* @param {string} [event.agent] — default 'self-loop'
|
|
37
|
+
* @param {string} [event.message]
|
|
38
|
+
* @param {object} [event.payload] — vai para payload_json (slug, attempt, etc.)
|
|
39
|
+
* @param {number|null} [event.tokenCount] — estimativa chars/4 da tentativa
|
|
40
|
+
* @returns {boolean} true se gravou
|
|
41
|
+
*/
|
|
42
|
+
async function emitGuardEvent(targetDir, { eventType, agent = 'self-loop', message = '', payload = null, tokenCount = null } = {}) {
|
|
43
|
+
if (!GUARD_EVENT_TYPES.includes(eventType)) return false;
|
|
44
|
+
let db = null;
|
|
45
|
+
try {
|
|
46
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
47
|
+
const opened = await openRuntimeDb(targetDir);
|
|
48
|
+
db = opened.db;
|
|
49
|
+
db.prepare(`
|
|
50
|
+
INSERT INTO execution_events (event_type, agent_name, message, payload_json, token_count, created_at)
|
|
51
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
52
|
+
`).run(
|
|
53
|
+
eventType,
|
|
54
|
+
agent,
|
|
55
|
+
message || eventType,
|
|
56
|
+
payload ? JSON.stringify(payload) : null,
|
|
57
|
+
tokenCount === null || tokenCount === undefined ? null : Math.round(tokenCount),
|
|
58
|
+
new Date().toISOString()
|
|
59
|
+
);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false; // D6: telemetria nunca quebra o loop
|
|
63
|
+
} finally {
|
|
64
|
+
try { if (db) db.close(); } catch { /* ignore */ }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
GUARD_EVENT_TYPES,
|
|
70
|
+
emitGuardEvent
|
|
71
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Human gates temáticos do self:loop (loop-guardrails REQ-12/13/14/15 + D4).
|
|
5
|
+
*
|
|
6
|
+
* Estado em disco:
|
|
7
|
+
* - `.aioson/plans/{slug}/gates/{id}.json` — decisão humana persistida
|
|
8
|
+
* (schema requirements §2.4 + campo aditivo `run_id` para suprimir
|
|
9
|
+
* re-detecção do mesmo tema dentro do run)
|
|
10
|
+
* - `progress.json` — `status='human_gate'` + `pending_gates[]` (D4)
|
|
11
|
+
*
|
|
12
|
+
* O tema `publish` é gate de COMANDO (intercepta feature:close, REQ-13) —
|
|
13
|
+
* nunca entra na detecção por diff.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const { matchAny } = require('./glob-match');
|
|
20
|
+
|
|
21
|
+
const GATE_STATUSES = Object.freeze(['pending', 'approved', 'rejected']);
|
|
22
|
+
|
|
23
|
+
function gatesDir(planDir) {
|
|
24
|
+
return path.join(planDir, 'gates');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function gatePath(planDir, gateId) {
|
|
28
|
+
const safeId = String(gateId).replace(/[^A-Za-z0-9._-]/g, '_');
|
|
29
|
+
return path.join(gatesDir(planDir), `${safeId}.json`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Carrega todos os gates do slug (array vazio se nenhum). */
|
|
33
|
+
function loadGates(planDir) {
|
|
34
|
+
const dir = gatesDir(planDir);
|
|
35
|
+
if (!fs.existsSync(dir)) return [];
|
|
36
|
+
const gates = [];
|
|
37
|
+
for (const file of fs.readdirSync(dir)) {
|
|
38
|
+
if (!file.endsWith('.json')) continue;
|
|
39
|
+
try {
|
|
40
|
+
gates.push(JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8')));
|
|
41
|
+
} catch { /* gate corrompido — ignorado na leitura, decisão manual via fs */ }
|
|
42
|
+
}
|
|
43
|
+
return gates;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pendingGates(planDir) {
|
|
47
|
+
return loadGates(planDir).filter((g) => g.status === 'pending');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detecção por tema (REQ-12): diff da tentativa casando os globs do tema E
|
|
52
|
+
* tema listado em required_for. `publish` nunca é detectado por diff (REQ-13).
|
|
53
|
+
* Temas já cobertos por gate `approved` do MESMO run não re-disparam.
|
|
54
|
+
*
|
|
55
|
+
* @returns {Array<{theme, triggeredBy: string[]}>}
|
|
56
|
+
*/
|
|
57
|
+
function detectGates({ changedFiles = [], requiredFor = [], themePaths = {}, existingGates = [], runId = null }) {
|
|
58
|
+
const detections = [];
|
|
59
|
+
for (const theme of requiredFor) {
|
|
60
|
+
if (theme === 'publish') continue; // gate de comando, nunca diff
|
|
61
|
+
const globs = themePaths[theme] || [];
|
|
62
|
+
if (!globs.length) continue;
|
|
63
|
+
const alreadyHandled = existingGates.some(
|
|
64
|
+
(g) => g.theme === theme && g.run_id === runId && (g.status === 'approved' || g.status === 'pending')
|
|
65
|
+
);
|
|
66
|
+
if (alreadyHandled) continue;
|
|
67
|
+
const triggeredBy = changedFiles
|
|
68
|
+
.filter((f) => matchAny(globs, f.path))
|
|
69
|
+
.map((f) => f.path);
|
|
70
|
+
if (triggeredBy.length > 0) {
|
|
71
|
+
detections.push({ theme, triggeredBy });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return detections;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Cria e persiste um gate `pending` (schema §2.4). `id` único por slug:
|
|
79
|
+
* `{theme}-{n}` com n incremental sobre os gates existentes do tema.
|
|
80
|
+
*/
|
|
81
|
+
function createGate(planDir, { theme, attempt, triggeredBy = [], diffSummary = '', runId = null }) {
|
|
82
|
+
const existing = loadGates(planDir).filter((g) => g.theme === theme);
|
|
83
|
+
const id = `${theme}-${existing.length + 1}`;
|
|
84
|
+
const gate = {
|
|
85
|
+
id,
|
|
86
|
+
theme,
|
|
87
|
+
status: 'pending',
|
|
88
|
+
attempt,
|
|
89
|
+
triggered_by: triggeredBy,
|
|
90
|
+
diff_summary: diffSummary,
|
|
91
|
+
requested_at: new Date().toISOString(),
|
|
92
|
+
decided_at: null,
|
|
93
|
+
decided_by: null,
|
|
94
|
+
reason: null,
|
|
95
|
+
run_id: runId
|
|
96
|
+
};
|
|
97
|
+
fs.mkdirSync(gatesDir(planDir), { recursive: true });
|
|
98
|
+
fs.writeFileSync(gatePath(planDir, id), JSON.stringify(gate, null, 2), 'utf8');
|
|
99
|
+
return gate;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Entra no estado HUMAN_GATE (D4): muta `progress` (caller persiste via cb).
|
|
104
|
+
*/
|
|
105
|
+
function enterHumanGate(progress, gateIds) {
|
|
106
|
+
progress.status = 'human_gate';
|
|
107
|
+
const pending = new Set(Array.isArray(progress.pending_gates) ? progress.pending_gates : []);
|
|
108
|
+
for (const id of gateIds) pending.add(id);
|
|
109
|
+
progress.pending_gates = [...pending];
|
|
110
|
+
progress.last_updated = new Date().toISOString();
|
|
111
|
+
return progress;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Decide um gate (REQ-14). Idempotente: gate já decidido → no-op com aviso.
|
|
116
|
+
* EC-8: gate inexistente → erro explícito sem efeito colateral.
|
|
117
|
+
*
|
|
118
|
+
* @returns {{ ok, error?, idempotent?, gate? }}
|
|
119
|
+
*/
|
|
120
|
+
function decideGate(planDir, gateId, { decision, by = null, reason = null }) {
|
|
121
|
+
if (!GATE_STATUSES.includes(decision) || decision === 'pending') {
|
|
122
|
+
return { ok: false, error: 'invalid_decision' };
|
|
123
|
+
}
|
|
124
|
+
const file = gatePath(planDir, gateId);
|
|
125
|
+
if (!fs.existsSync(file)) {
|
|
126
|
+
return { ok: false, error: 'gate_not_found', gateId };
|
|
127
|
+
}
|
|
128
|
+
let gate;
|
|
129
|
+
try {
|
|
130
|
+
gate = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
131
|
+
} catch {
|
|
132
|
+
return { ok: false, error: 'gate_corrupted', gateId };
|
|
133
|
+
}
|
|
134
|
+
if (gate.status !== 'pending') {
|
|
135
|
+
return { ok: true, idempotent: true, gate };
|
|
136
|
+
}
|
|
137
|
+
if (decision === 'rejected' && !(reason && String(reason).trim())) {
|
|
138
|
+
return { ok: false, error: 'reason_required_on_reject', gateId };
|
|
139
|
+
}
|
|
140
|
+
gate.status = decision;
|
|
141
|
+
gate.decided_at = new Date().toISOString();
|
|
142
|
+
gate.decided_by = by || null;
|
|
143
|
+
gate.reason = reason || null;
|
|
144
|
+
fs.writeFileSync(file, JSON.stringify(gate, null, 2), 'utf8');
|
|
145
|
+
return { ok: true, idempotent: false, gate };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Reconcilia `progress` após decisões (REQ-15): remove o gate decidido de
|
|
150
|
+
* `pending_gates`; sem pendências → `status='in_progress'` (retomada
|
|
151
|
+
* idempotente; gate rejeitado fica como auditoria e não bloqueia runs novos).
|
|
152
|
+
* Muta `progress` (caller persiste).
|
|
153
|
+
*/
|
|
154
|
+
function resolveGateState(progress, planDir) {
|
|
155
|
+
const stillPending = new Set(pendingGates(planDir).map((g) => g.id));
|
|
156
|
+
const current = Array.isArray(progress.pending_gates) ? progress.pending_gates : [];
|
|
157
|
+
progress.pending_gates = current.filter((id) => stillPending.has(id));
|
|
158
|
+
if (progress.pending_gates.length === 0 && progress.status === 'human_gate') {
|
|
159
|
+
progress.status = 'in_progress';
|
|
160
|
+
}
|
|
161
|
+
progress.last_updated = new Date().toISOString();
|
|
162
|
+
return progress;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Gate de comando `publish` (REQ-13): existe gate publish aprovado?
|
|
167
|
+
*/
|
|
168
|
+
function hasApprovedPublishGate(planDir) {
|
|
169
|
+
return loadGates(planDir).some((g) => g.theme === 'publish' && g.status === 'approved');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
loadGates,
|
|
174
|
+
pendingGates,
|
|
175
|
+
detectGates,
|
|
176
|
+
createGate,
|
|
177
|
+
enterHumanGate,
|
|
178
|
+
decideGate,
|
|
179
|
+
resolveGateState,
|
|
180
|
+
hasApprovedPublishGate,
|
|
181
|
+
gatePath
|
|
182
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scope guard do self:loop (loop-guardrails REQ-4/5/6 + REQ-10).
|
|
5
|
+
*
|
|
6
|
+
* Módulo puro: recebe o changed set (já calculado por git-baseline) e o
|
|
7
|
+
* contrato RESOLVIDO (contract-schema.resolveContract — defaults proibidos já
|
|
8
|
+
* mesclados). Não faz I/O.
|
|
9
|
+
*
|
|
10
|
+
* Precedência (REQ-5): deny vence allow — path que casa `forbidden_files` é
|
|
11
|
+
* violação mesmo casando `allowed_files`. Defaults proibidos são sempre
|
|
12
|
+
* aplicados (REQ-4) porque vêm mesclados do resolveContract.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { matchAny } = require('./glob-match');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} params
|
|
19
|
+
* @param {Array<{path, status}>} params.changedFiles — changed set da tentativa
|
|
20
|
+
* @param {Array<{path, reason}>} [params.rehashViolations] — D2 (git-baseline)
|
|
21
|
+
* @param {string[]|null} params.allowedGlobs — null = sem allowlist
|
|
22
|
+
* @param {string[]} params.forbiddenGlobs — já mesclados com defaults
|
|
23
|
+
* @returns {{ ok: boolean, violations: Array<{path, status, glob, reason}> }}
|
|
24
|
+
*/
|
|
25
|
+
function checkScope({ changedFiles = [], rehashViolations = [], allowedGlobs = null, forbiddenGlobs = [] }) {
|
|
26
|
+
const violations = [];
|
|
27
|
+
|
|
28
|
+
for (const file of changedFiles) {
|
|
29
|
+
const forbiddenMatch = matchAny(forbiddenGlobs, file.path);
|
|
30
|
+
if (forbiddenMatch) {
|
|
31
|
+
violations.push({
|
|
32
|
+
path: file.path,
|
|
33
|
+
status: file.status,
|
|
34
|
+
glob: forbiddenMatch,
|
|
35
|
+
reason: `matches forbidden glob "${forbiddenMatch}"${file.status === 'deleted' ? ' (deletion counts — EC-4)' : ''}`
|
|
36
|
+
});
|
|
37
|
+
continue; // deny vence allow (REQ-5)
|
|
38
|
+
}
|
|
39
|
+
if (allowedGlobs && !matchAny(allowedGlobs, file.path)) {
|
|
40
|
+
violations.push({
|
|
41
|
+
path: file.path,
|
|
42
|
+
status: file.status,
|
|
43
|
+
glob: null,
|
|
44
|
+
reason: 'outside allowed_files allowlist'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const rehash of rehashViolations) {
|
|
50
|
+
violations.push({
|
|
51
|
+
path: rehash.path,
|
|
52
|
+
status: 'modified',
|
|
53
|
+
glob: null,
|
|
54
|
+
reason: rehash.reason
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ok: violations.length === 0, violations };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Conta linhas efetivas de diff (+/− excluindo headers +++/---). */
|
|
62
|
+
function countDiffLines(diffPatch) {
|
|
63
|
+
if (!diffPatch) return 0;
|
|
64
|
+
let count = 0;
|
|
65
|
+
for (const line of String(diffPatch).split('\n')) {
|
|
66
|
+
if ((line.startsWith('+') && !line.startsWith('+++')) ||
|
|
67
|
+
(line.startsWith('-') && !line.startsWith('---'))) {
|
|
68
|
+
count += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return count;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Limites de diff (REQ-10, should-have). Avaliados sobre o MESMO conjunto do
|
|
76
|
+
* scope guard. `null`/`undefined` = sem limite.
|
|
77
|
+
*
|
|
78
|
+
* @returns {{ ok: boolean, exceeded: Array<{limit, actual, max}> }}
|
|
79
|
+
*/
|
|
80
|
+
function checkDiffLimits({ changedFiles = [], diffPatch = '', maxChangedFiles = null, maxDiffLines = null }) {
|
|
81
|
+
const exceeded = [];
|
|
82
|
+
|
|
83
|
+
if (Number.isInteger(maxChangedFiles) && maxChangedFiles > 0 && changedFiles.length > maxChangedFiles) {
|
|
84
|
+
exceeded.push({ limit: 'max_changed_files', actual: changedFiles.length, max: maxChangedFiles });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (Number.isInteger(maxDiffLines) && maxDiffLines > 0) {
|
|
88
|
+
const actual = countDiffLines(diffPatch);
|
|
89
|
+
if (actual > maxDiffLines) {
|
|
90
|
+
exceeded.push({ limit: 'max_diff_lines', actual, max: maxDiffLines });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { ok: exceeded.length === 0, exceeded };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Feedback de reparo/rollback injetado na próxima iteração após violação
|
|
99
|
+
* (REQ-6). Texto direto para o agente: reverter os paths e permanecer no escopo.
|
|
100
|
+
*/
|
|
101
|
+
function buildRollbackFeedback(violations) {
|
|
102
|
+
const lines = violations.slice(0, 10).map((v) => ` - ${v.path} (${v.reason})`);
|
|
103
|
+
return [
|
|
104
|
+
'SCOPE VIOLATION — files were changed outside the contract scope:',
|
|
105
|
+
...lines,
|
|
106
|
+
'Revert these changes (git checkout -- <path> / delete untracked files) and redo the task touching ONLY files inside the allowed scope.'
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {
|
|
111
|
+
checkScope,
|
|
112
|
+
checkDiffLimits,
|
|
113
|
+
countDiffLines,
|
|
114
|
+
buildRollbackFeedback
|
|
115
|
+
};
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -182,14 +182,14 @@ module.exports = {
|
|
|
182
182
|
'aioson plan [path] [--sub=show|status|checkpoint|stale|register] [--feature=<slug>] [--phase=<N>] [--locale=en]',
|
|
183
183
|
help_squad_plan:
|
|
184
184
|
'aioson squad:plan [path] [--sub=show|status|checkpoint|stale|register] [--squad=<slug>] [--round=<N>] [--locale=en]',
|
|
185
|
-
help_squad_learning:
|
|
186
|
-
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=en]',
|
|
187
|
-
help_agent_audit:
|
|
188
|
-
'aioson agent:audit [path] [--runtime-only|--template-only|--inception] [--locales] [--verbose] [--fix] [--json] [--locale=en]',
|
|
189
|
-
help_quality_audit:
|
|
190
|
-
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=en]',
|
|
191
|
-
help_squad_dashboard:
|
|
192
|
-
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=en]',
|
|
185
|
+
help_squad_learning:
|
|
186
|
+
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=en]',
|
|
187
|
+
help_agent_audit:
|
|
188
|
+
'aioson agent:audit [path] [--runtime-only|--template-only|--inception] [--locales] [--verbose] [--fix] [--json] [--locale=en]',
|
|
189
|
+
help_quality_audit:
|
|
190
|
+
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=en]',
|
|
191
|
+
help_squad_dashboard:
|
|
192
|
+
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=en]',
|
|
193
193
|
help_squad_worker:
|
|
194
194
|
'aioson squad:worker [path] [--sub=list|run|test|logs|scaffold] [--squad=<slug>] [--worker=<slug>] [--input=<json>] [--locale=en]',
|
|
195
195
|
help_squad_daemon:
|
|
@@ -203,7 +203,7 @@ module.exports = {
|
|
|
203
203
|
help_commit_prepare:
|
|
204
204
|
'aioson commit:prepare [path] [--staged-only] [--agent-safe] [--mode=guarded|trusted|headless] [--json] [--locale=en]',
|
|
205
205
|
help_learning:
|
|
206
|
-
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=en]',
|
|
206
|
+
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=en]',
|
|
207
207
|
help_runtime_init:
|
|
208
208
|
'aioson runtime:init [path] [--json] [--locale=en]',
|
|
209
209
|
help_runtime_ingest:
|
|
@@ -222,12 +222,12 @@ module.exports = {
|
|
|
222
222
|
'aioson runtime:task:fail [path] --task=<key> [--goal=<text>] [--json] [--locale=en]',
|
|
223
223
|
help_runtime_fail:
|
|
224
224
|
'aioson runtime:fail [path] --run=<key> [--message=<text>] [--summary=<text>] [--output=<path>] [--json] [--locale=en]',
|
|
225
|
-
help_runtime_status:
|
|
226
|
-
'aioson runtime:status [path] [--json] [--locale=en]',
|
|
227
|
-
help_agent_recover:
|
|
228
|
-
'aioson agent:recover [path] [--dry-run] [--older-than=<24h|7d>] [--json] [--locale=en]',
|
|
229
|
-
help_runtime_log:
|
|
230
|
-
'aioson runtime:log [path] --agent=<name> --message=<text> [--type=<event>] [--finish] [--status=completed|failed] [--summary=<text>] [--title=<task-title>] [--json] [--locale=en]',
|
|
225
|
+
help_runtime_status:
|
|
226
|
+
'aioson runtime:status [path] [--json] [--locale=en]',
|
|
227
|
+
help_agent_recover:
|
|
228
|
+
'aioson agent:recover [path] [--dry-run] [--older-than=<24h|7d>] [--json] [--locale=en]',
|
|
229
|
+
help_runtime_log:
|
|
230
|
+
'aioson runtime:log [path] --agent=<name> --message=<text> [--type=<event>] [--finish] [--status=completed|failed] [--summary=<text>] [--title=<task-title>] [--json] [--locale=en]',
|
|
231
231
|
help_runtime_session_start:
|
|
232
232
|
'aioson runtime:session:start [path] --agent=<name> [--title=<text>] [--message=<text>] [--session=<key>] [--json] [--locale=en]',
|
|
233
233
|
help_runtime_session_log:
|
|
@@ -258,10 +258,10 @@ module.exports = {
|
|
|
258
258
|
'aioson skill:install [path] --slug=<name> [--from=npm|cloud|./path] [--force] [--json] [--locale=en]',
|
|
259
259
|
help_skill_list:
|
|
260
260
|
'aioson skill:list [path] [--json] [--locale=en]',
|
|
261
|
-
help_skill_remove:
|
|
262
|
-
'aioson skill:remove [path] --slug=<name> [--json] [--locale=en]',
|
|
263
|
-
help_skill_audit:
|
|
264
|
-
'aioson skill:audit [path] [--json] [--locale=en]',
|
|
261
|
+
help_skill_remove:
|
|
262
|
+
'aioson skill:remove [path] --slug=<name> [--json] [--locale=en]',
|
|
263
|
+
help_skill_audit:
|
|
264
|
+
'aioson skill:audit [path] [--json] [--locale=en]',
|
|
265
265
|
help_design_hybrid_options:
|
|
266
266
|
'aioson design-hybrid:options [path] [--advanced] [--json] [--locale=en]',
|
|
267
267
|
help_cloud_import_squad:
|
|
@@ -450,6 +450,8 @@ module.exports = {
|
|
|
450
450
|
bootstrap_coverage_hint_seed: 'Run /discover to seed .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
451
451
|
features_dir_present: 'Features directory present (.aioson/context/features/)',
|
|
452
452
|
features_dir_present_hint: 'Create .aioson/context/features/ to host per-feature dossiers (doctor --fix will create it).',
|
|
453
|
+
auto_handoff_declared: 'Autopilot handoff flag declared (auto_handoff in project.context.md)',
|
|
454
|
+
auto_handoff_declared_hint: 'The autopilot-handoff protocol is installed but auto_handoff is not set in project.context.md frontmatter — autopilot stays inactive. Set auto_handoff: true to enable it, or auto_handoff: false to silence this warning.',
|
|
453
455
|
claude_commands_present: 'Claude slash commands present ({missing} missing of {required})',
|
|
454
456
|
claude_commands_present_hint: 'Missing: {paths}. Run `aioson doctor . --fix` to restore them from the template.',
|
|
455
457
|
version_drift: 'CLI version matches project.context.md (context: {context}, CLI: {cli})',
|
|
@@ -735,7 +737,7 @@ module.exports = {
|
|
|
735
737
|
'dApp context detected; include Web3 skills during @architect and @dev.',
|
|
736
738
|
note_micro_scope: 'Keep implementation scope minimal and avoid optional agents.',
|
|
737
739
|
note_product_optional: '@product is optional for MICRO — skip it and go straight to @dev if the idea is already clear.',
|
|
738
|
-
note_feature_flow: 'New feature workflow (after initial setup): @product → @analyst → @scope-check → @dev → @qa. No @setup required.'
|
|
740
|
+
note_feature_flow: 'New feature workflow (after initial setup): @product → @analyst → @scope-check → @dev → @qa. No @setup required.'
|
|
739
741
|
},
|
|
740
742
|
workflow_next: {
|
|
741
743
|
title: 'Workflow handoff for {mode} ({classification}):',
|
|
@@ -1135,6 +1137,7 @@ module.exports = {
|
|
|
1135
1137
|
session_already_active: 'Live session already active: {agent} | session: {session} | run: {runKey} ({dbPath})',
|
|
1136
1138
|
session_started: 'Live session started: {agent} | tool: {tool} | session: {session} ({dbPath})',
|
|
1137
1139
|
event_recorded: 'Live event recorded: {agent} | {eventType} | {session} ({dbPath})',
|
|
1140
|
+
standalone_event_recorded: 'Standalone runtime event recorded: {agent} | {eventType} | run: {runKey} ({dbPath})',
|
|
1138
1141
|
handoff_recorded: 'Live handoff recorded: {from} -> {to} | {session} ({dbPath})',
|
|
1139
1142
|
session_closed: 'Live session closed: {agent} | {session} ({dbPath})',
|
|
1140
1143
|
process_dead_warning: 'Process is dead while the live session is still open. Close it manually with `aioson live:close . --status=failed`.',
|
|
@@ -1209,7 +1212,7 @@ module.exports = {
|
|
|
1209
1212
|
folder_required_example_prompt:
|
|
1210
1213
|
' Ready prompt : aioson agent:prompt analyst --tool=codex',
|
|
1211
1214
|
folder_required_example_next:
|
|
1212
|
-
' Workflow after full scan: @analyst -> @scope-check -> @architect -> @dev',
|
|
1215
|
+
' Workflow after full scan: @analyst -> @scope-check -> @architect -> @dev',
|
|
1213
1216
|
folder_not_found: 'Folder "{folder}" was not found in this project. Top-level directories detected: {available}',
|
|
1214
1217
|
config_missing: '{file} not found. To use LLM mode, copy aioson-models.json and fill in your API keys.',
|
|
1215
1218
|
config_invalid: 'Invalid JSON in aioson-models.json: {error}',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -160,12 +160,12 @@ module.exports = {
|
|
|
160
160
|
'aioson squad:pipeline [path] [--sub=list|show|status] [--pipeline=<slug>] [--locale=es]',
|
|
161
161
|
help_squad_investigate:
|
|
162
162
|
'aioson squad:investigate [path] [--sub=list|show|score|link|register] [--investigation=<slug>] [--squad=<slug>] [--locale=es]',
|
|
163
|
-
help_squad_learning:
|
|
164
|
-
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=es]',
|
|
165
|
-
help_quality_audit:
|
|
166
|
-
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=es]',
|
|
167
|
-
help_squad_dashboard:
|
|
168
|
-
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=es]',
|
|
163
|
+
help_squad_learning:
|
|
164
|
+
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=es]',
|
|
165
|
+
help_quality_audit:
|
|
166
|
+
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=es]',
|
|
167
|
+
help_squad_dashboard:
|
|
168
|
+
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=es]',
|
|
169
169
|
help_squad_worker:
|
|
170
170
|
'aioson squad:worker [path] [--sub=list|run|test|logs|scaffold] [--squad=<slug>] [--worker=<slug>] [--input=<json>] [--locale=es]',
|
|
171
171
|
help_squad_daemon:
|
|
@@ -179,7 +179,7 @@ module.exports = {
|
|
|
179
179
|
help_commit_prepare:
|
|
180
180
|
'aioson commit:prepare [path] [--staged-only] [--agent-safe] [--mode=guarded|trusted|headless] [--json] [--locale=es]',
|
|
181
181
|
help_learning:
|
|
182
|
-
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=es]',
|
|
182
|
+
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=es]',
|
|
183
183
|
dashboard_moved:
|
|
184
184
|
'El flujo `{command}` fue eliminado del CLI. El dashboard de AIOSON ahora se instala por separado. Abre la app del dashboard en tu computadora, crea o agrega un proyecto y selecciona la carpeta que ya contiene `.aioson/`.',
|
|
185
185
|
dashboard_moved_line: '{message}\n',
|
|
@@ -324,6 +324,8 @@ module.exports = {
|
|
|
324
324
|
bootstrap_coverage_hint_seed: 'Ejecute /discover para sembrar .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
325
325
|
features_dir_present: 'Directorio de features presente (.aioson/context/features/)',
|
|
326
326
|
features_dir_present_hint: 'Cree .aioson/context/features/ para hospedar dossiers por feature (doctor --fix lo crea).',
|
|
327
|
+
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff en project.context.md)',
|
|
328
|
+
auto_handoff_declared_hint: 'El protocolo autopilot-handoff esta instalado pero auto_handoff no esta definido en el frontmatter de project.context.md — el autopilot queda inactivo. Defina auto_handoff: true para activarlo, o auto_handoff: false para silenciar este aviso.',
|
|
327
329
|
claude_commands_present: 'Slash commands de Claude presentes ({missing} ausentes de {required})',
|
|
328
330
|
claude_commands_present_hint: 'Ausentes: {paths}. Ejecute `aioson doctor . --fix` para restaurarlos.',
|
|
329
331
|
version_drift: 'Version del CLI coincide con project.context.md (contexto: {context}, CLI: {cli})',
|
|
@@ -609,7 +611,7 @@ module.exports = {
|
|
|
609
611
|
note_product_optional:
|
|
610
612
|
'@product es opcional para MICRO — omitelo y ve directo a @dev si la idea ya esta clara.',
|
|
611
613
|
note_feature_flow:
|
|
612
|
-
'Flujo para nueva feature (tras la configuracion inicial): @product → @analyst → @scope-check → @dev → @qa. Sin @setup.'
|
|
614
|
+
'Flujo para nueva feature (tras la configuracion inicial): @product → @analyst → @scope-check → @dev → @qa. Sin @setup.'
|
|
613
615
|
},
|
|
614
616
|
parallel_init: {
|
|
615
617
|
context_missing:
|
|
@@ -1020,7 +1022,7 @@ module.exports = {
|
|
|
1020
1022
|
folder_required_example_prompt:
|
|
1021
1023
|
' Prompt listo : aioson agent:prompt analyst --tool=codex',
|
|
1022
1024
|
folder_required_example_next:
|
|
1023
|
-
' Flujo tras escaneo completo: @analyst -> @scope-check -> @architect -> @dev',
|
|
1025
|
+
' Flujo tras escaneo completo: @analyst -> @scope-check -> @architect -> @dev',
|
|
1024
1026
|
folder_not_found: 'La carpeta "{folder}" no existe en este proyecto. Directorios de nivel superior detectados: {available}',
|
|
1025
1027
|
config_missing: '{file} no encontrado. Para usar el modo con LLM, copia aioson-models.json y completa tus claves de API.',
|
|
1026
1028
|
config_invalid: 'JSON invalido en aioson-models.json: {error}',
|