@jaimevalasek/aioson 1.23.1 → 1.28.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 +56 -0
- package/docs/en/4-agents/README.md +11 -8
- package/docs/en/4-agents/forge-run.md +165 -0
- package/docs/en/5-reference/README.md +1 -0
- package/docs/en/5-reference/cli-reference.md +199 -85
- package/docs/en/5-reference/executable-verification.md +165 -0
- package/docs/pt/4-agentes/README.md +2 -1
- package/docs/pt/4-agentes/forge-run.md +150 -0
- package/docs/pt/4-agentes/pm.md +8 -0
- package/docs/pt/4-agentes/qa.md +2 -0
- package/docs/pt/4-agentes/scope-check.md +19 -1
- package/docs/pt/4-agentes/sheldon.md +2 -0
- package/docs/pt/4-agentes/validator.md +20 -0
- package/docs/pt/5-referencia/autopilot-handoff.md +33 -0
- package/docs/pt/5-referencia/comandos-cli.md +64 -9
- package/docs/pt/5-referencia/fluxo-artefatos.md +40 -15
- package/docs/pt/5-referencia/loop-guardrails.md +19 -0
- package/docs/pt/5-referencia/sdd-automation-scripts.md +130 -26
- package/package.json +1 -1
- package/src/cli.js +70 -54
- package/src/commands/context-select.js +1 -0
- package/src/commands/forge-compile.js +330 -0
- package/src/commands/harness-check.js +159 -0
- package/src/commands/harness.js +37 -2
- package/src/commands/spec-analyze.js +324 -0
- package/src/constants.js +118 -108
- package/src/context-selector.js +28 -2
- package/src/gateway-pointer-merge.js +25 -4
- package/src/harness/contract-schema.js +8 -0
- package/src/harness/plan-waves.js +77 -0
- package/src/harness/review-payload.js +230 -0
- package/src/i18n/messages/en.js +21 -15
- package/src/i18n/messages/es.js +15 -13
- package/src/i18n/messages/fr.js +15 -13
- package/src/i18n/messages/pt-BR.js +21 -15
- package/src/parser.js +3 -1
- package/template/.aioson/agents/dev.md +67 -66
- package/template/.aioson/agents/deyvin.md +79 -74
- package/template/.aioson/agents/forge-run.md +57 -0
- package/template/.aioson/agents/pm.md +51 -45
- package/template/.aioson/agents/qa.md +22 -22
- package/template/.aioson/agents/scope-check.md +49 -46
- package/template/.aioson/agents/sheldon.md +1 -1
- package/template/.aioson/agents/validator.md +16 -5
- package/template/.aioson/docs/autopilot-handoff.md +34 -32
- package/template/.aioson/docs/sheldon/harness-contract.md +19 -2
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +9 -7
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +19 -15
- package/template/.claude/commands/aioson/agent/forge-run.md +17 -0
- package/template/AGENTS.md +7 -5
- package/template/CLAUDE.md +4 -3
- package/template/OPENCODE.md +24 -22
package/src/context-selector.js
CHANGED
|
@@ -29,6 +29,12 @@ const FOUNDATION_CONTEXT_BASENAMES = new Set([
|
|
|
29
29
|
'memory-index.md'
|
|
30
30
|
]);
|
|
31
31
|
|
|
32
|
+
const ACTIVATION_ONLY_CONTEXT_PATHS = new Set([
|
|
33
|
+
'.aioson/context/project.context.md',
|
|
34
|
+
'.aioson/context/project-pulse.md',
|
|
35
|
+
'.aioson/context/dev-state.md'
|
|
36
|
+
]);
|
|
37
|
+
|
|
32
38
|
const UNIVERSAL_ALWAYS_CONTEXT_BASENAMES = new Set([
|
|
33
39
|
'project.context.md',
|
|
34
40
|
'project-pulse.md'
|
|
@@ -59,6 +65,18 @@ function normalizeFeaturePointer(value) {
|
|
|
59
65
|
return normalized;
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
function isActivationOnlyTask(agent, mode, task) {
|
|
69
|
+
if (agent !== 'deyvin' || mode !== 'planning') return false;
|
|
70
|
+
const normalized = normalizeToken(task);
|
|
71
|
+
if (!normalized) return true;
|
|
72
|
+
return (
|
|
73
|
+
normalized.includes('agent activation') ||
|
|
74
|
+
normalized.includes('activation only') ||
|
|
75
|
+
normalized.includes('without concrete task') ||
|
|
76
|
+
normalized.includes('no concrete task')
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
function parseListValue(value) {
|
|
63
81
|
if (value === undefined || value === null) return [];
|
|
64
82
|
const raw = String(value).trim();
|
|
@@ -254,6 +272,7 @@ function scoreCandidate(candidate, context) {
|
|
|
254
272
|
const base = path.basename(candidate.path);
|
|
255
273
|
|
|
256
274
|
if (!appliesToAgent(candidate.frontmatter, context.agent)) return null;
|
|
275
|
+
if (context.activationOnly && !ACTIVATION_ONLY_CONTEXT_PATHS.has(candidate.path)) return null;
|
|
257
276
|
|
|
258
277
|
if (candidate.modes.length > 0 && !candidate.modes.map(normalizeToken).includes(context.mode)) {
|
|
259
278
|
return null;
|
|
@@ -285,6 +304,9 @@ function scoreCandidate(candidate, context) {
|
|
|
285
304
|
}
|
|
286
305
|
|
|
287
306
|
const activeFeature = context.feature || context.activeFeature || '';
|
|
307
|
+
if (context.activationOnly && candidate.featureSlug && !context.feature) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
288
310
|
if (candidate.featureSlug && activeFeature && candidate.featureSlug === activeFeature) {
|
|
289
311
|
score += 45;
|
|
290
312
|
reasons.push(`feature:${candidate.featureSlug}`);
|
|
@@ -342,6 +364,7 @@ async function selectContext(targetDir, options = {}) {
|
|
|
342
364
|
const task = String(options.task || options.goal || '').trim();
|
|
343
365
|
const paths = splitOptionList(options.paths || options.path).map(normalizeSlashes);
|
|
344
366
|
const feature = normalizeFeaturePointer(options.feature || options.slug || '');
|
|
367
|
+
const activationOnly = isActivationOnlyTask(agent, mode, task);
|
|
345
368
|
|
|
346
369
|
const pulse = await readProjectPulse(targetDir);
|
|
347
370
|
const devState = await readDevState(targetDir);
|
|
@@ -367,7 +390,8 @@ async function selectContext(targetDir, options = {}) {
|
|
|
367
390
|
paths,
|
|
368
391
|
feature,
|
|
369
392
|
activeFeature,
|
|
370
|
-
lookup
|
|
393
|
+
lookup,
|
|
394
|
+
activationOnly
|
|
371
395
|
});
|
|
372
396
|
if (scored) selected.push(scored);
|
|
373
397
|
}
|
|
@@ -382,6 +406,7 @@ async function selectContext(targetDir, options = {}) {
|
|
|
382
406
|
paths,
|
|
383
407
|
feature: feature || null,
|
|
384
408
|
active_feature: activeFeature || null,
|
|
409
|
+
activation_only: activationOnly,
|
|
385
410
|
selected
|
|
386
411
|
};
|
|
387
412
|
}
|
|
@@ -390,5 +415,6 @@ module.exports = {
|
|
|
390
415
|
selectContext,
|
|
391
416
|
collectCandidates,
|
|
392
417
|
parseListValue,
|
|
393
|
-
pathMatchesPattern
|
|
418
|
+
pathMatchesPattern,
|
|
419
|
+
isActivationOnlyTask
|
|
394
420
|
};
|
|
@@ -41,6 +41,20 @@ function findBlockRange(content) {
|
|
|
41
41
|
return { start, end };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function isLegacyUnmanagedGateway(content) {
|
|
45
|
+
const trimmed = String(content || '').trim();
|
|
46
|
+
if (!trimmed.startsWith('# AIOSON')) return false;
|
|
47
|
+
const hasBoot = trimmed.includes('## Mandatory first action') || trimmed.includes('## Boot');
|
|
48
|
+
const hasProjectContext = trimmed.includes('.aioson/context/project.context.md');
|
|
49
|
+
const hasAgentPointers = trimmed.includes('.aioson/agents/') || trimmed.includes('## Agent files') || trimmed.includes('## Agents');
|
|
50
|
+
const hasManagedMarkers = trimmed.includes(MARKER_BEGIN) || trimmed.includes(MARKER_END);
|
|
51
|
+
return hasBoot && hasProjectContext && hasAgentPointers && !hasManagedMarkers;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function stripLegacyUnmanagedGateway(content) {
|
|
55
|
+
return isLegacyUnmanagedGateway(content) ? '' : content;
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
async function mergeGatewayPointer({ templatePath, targetPath, backupRoot, targetDir, dryRun = false }) {
|
|
45
59
|
const templateContent = await fs.readFile(templatePath, 'utf8');
|
|
46
60
|
const block = buildBlock(templateContent);
|
|
@@ -56,15 +70,21 @@ async function mergeGatewayPointer({ templatePath, targetPath, backupRoot, targe
|
|
|
56
70
|
let next;
|
|
57
71
|
let action;
|
|
58
72
|
if (range) {
|
|
59
|
-
const before = existing.slice(0, range.start);
|
|
73
|
+
const before = stripLegacyUnmanagedGateway(existing.slice(0, range.start));
|
|
60
74
|
const after = existing.slice(range.end);
|
|
61
75
|
const cleanBefore = before.length === 0 || before.endsWith('\n') ? before : `${before}\n`;
|
|
62
76
|
next = `${cleanBefore}${block}${after}`;
|
|
63
77
|
action = 'block_updated';
|
|
64
78
|
} else {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
const cleanExisting = stripLegacyUnmanagedGateway(existing);
|
|
80
|
+
if (cleanExisting.length === 0) {
|
|
81
|
+
next = block;
|
|
82
|
+
action = 'legacy_replaced';
|
|
83
|
+
} else {
|
|
84
|
+
const separator = cleanExisting.length === 0 ? '' : cleanExisting.endsWith('\n\n') ? '' : cleanExisting.endsWith('\n') ? '\n' : '\n\n';
|
|
85
|
+
next = `${cleanExisting}${separator}${block}`;
|
|
86
|
+
action = 'block_appended';
|
|
87
|
+
}
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
if (next === existing) return { action: 'unchanged' };
|
|
@@ -98,5 +118,6 @@ module.exports = {
|
|
|
98
118
|
isGatewayPointerPath,
|
|
99
119
|
buildBlock,
|
|
100
120
|
findBlockRange,
|
|
121
|
+
isLegacyUnmanagedGateway,
|
|
101
122
|
mergeGatewayPointer
|
|
102
123
|
};
|
|
@@ -250,6 +250,14 @@ function validateContract(contract) {
|
|
|
250
250
|
if (criterion.binary !== undefined && typeof criterion.binary !== 'boolean') {
|
|
251
251
|
errors.push({ field: `criteria[${i}].binary`, reason: 'must be a boolean' });
|
|
252
252
|
}
|
|
253
|
+
// Cobertura executável: critério binário sem verification continua
|
|
254
|
+
// válido (julgado pelo @validator), mas é dívida de verificação.
|
|
255
|
+
if (criterion.binary === true && criterion.verification === undefined) {
|
|
256
|
+
warnings.push({
|
|
257
|
+
field: `criteria[${i}].verification`,
|
|
258
|
+
reason: `binary criterion "${criterion.id}" has no executable verification command — @validator will LLM-judge it`
|
|
259
|
+
});
|
|
260
|
+
}
|
|
253
261
|
});
|
|
254
262
|
}
|
|
255
263
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parser da tabela "Execution Sequence" do implementation-plan-{slug}.md
|
|
5
|
+
* (convenção Wave do @pm — Fase 4/5 do plano de verificação executável).
|
|
6
|
+
*
|
|
7
|
+
* Compartilhado entre `spec:analyze` (check wave_file_overlap) e
|
|
8
|
+
* `forge:compile` (compilação spec → workflow script). Sem coluna Wave a
|
|
9
|
+
* função retorna null — chamadores tratam como "convenção ausente"
|
|
10
|
+
* (retrocompat com planos antigos).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} content — markdown do implementation-plan
|
|
15
|
+
* @returns {Array<{phase, wave, files: string[], scope, done}>|null}
|
|
16
|
+
*/
|
|
17
|
+
function parseExecutionWaves(content) {
|
|
18
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
19
|
+
let columns = null;
|
|
20
|
+
const rows = [];
|
|
21
|
+
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed.startsWith('|')) {
|
|
25
|
+
if (columns && rows.length) break; // fim da tabela alvo
|
|
26
|
+
columns = columns && rows.length === 0 ? columns : null;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const cells = trimmed.split('|').slice(1, -1).map((c) => c.trim());
|
|
30
|
+
const lower = cells.map((c) => c.toLowerCase());
|
|
31
|
+
|
|
32
|
+
if (!columns) {
|
|
33
|
+
if (lower.includes('wave') && lower.some((c) => c.includes('phase')) && lower.some((c) => c.includes('file'))) {
|
|
34
|
+
columns = {
|
|
35
|
+
phase: lower.findIndex((c) => c.includes('phase')),
|
|
36
|
+
wave: lower.indexOf('wave'),
|
|
37
|
+
files: lower.findIndex((c) => c.includes('file')),
|
|
38
|
+
scope: lower.findIndex((c) => c.includes('scope')),
|
|
39
|
+
done: lower.findIndex((c) => c.includes('done'))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (cells.every((c) => /^:?-{2,}:?$/.test(c))) continue; // separador
|
|
46
|
+
|
|
47
|
+
const wave = parseInt(cells[columns.wave], 10);
|
|
48
|
+
if (!Number.isInteger(wave)) continue;
|
|
49
|
+
const files = (cells[columns.files] || '')
|
|
50
|
+
.split(/,|<br\s*\/?\s*>/i)
|
|
51
|
+
.map((f) => f.replace(/`/g, '').trim().replace(/\\/g, '/').toLowerCase())
|
|
52
|
+
.filter((f) => f && !/^(\.{3}|-|—)$/.test(f));
|
|
53
|
+
rows.push({
|
|
54
|
+
phase: cells[columns.phase] || `row ${rows.length + 1}`,
|
|
55
|
+
wave,
|
|
56
|
+
files,
|
|
57
|
+
scope: columns.scope >= 0 ? (cells[columns.scope] || '') : '',
|
|
58
|
+
done: columns.done >= 0 ? (cells[columns.done] || '') : ''
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return columns ? rows : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Agrupa as fases por wave, em ordem ascendente. */
|
|
66
|
+
function groupByWave(rows) {
|
|
67
|
+
const byWave = new Map();
|
|
68
|
+
for (const row of rows || []) {
|
|
69
|
+
if (!byWave.has(row.wave)) byWave.set(row.wave, []);
|
|
70
|
+
byWave.get(row.wave).push(row);
|
|
71
|
+
}
|
|
72
|
+
return [...byWave.entries()]
|
|
73
|
+
.sort((a, b) => a[0] - b[0])
|
|
74
|
+
.map(([wave, phases]) => ({ wave, phases }));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { parseExecutionWaves, groupByWave };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Payload de revisão fresh-context para o prompt do @validator (Fase 2 do
|
|
5
|
+
* plano de verificação executável).
|
|
6
|
+
*
|
|
7
|
+
* `harness:validate` gera o prompt do @validator headless; este módulo anexa
|
|
8
|
+
* a ele tudo que um contexto ISOLADO precisa para validar sem herdar o
|
|
9
|
+
* histórico da sessão que implementou: diff vs base, lista de arquivos
|
|
10
|
+
* alterados (incluindo untracked), resultado dos checks determinísticos
|
|
11
|
+
* (last-check-output.json) e a instrução de onde gravar o veredito JSON.
|
|
12
|
+
*
|
|
13
|
+
* Nunca lança: fora de um repo git (ou em falha de I/O) retorna um payload
|
|
14
|
+
* degradado com a nota correspondente — os testes do router rodam em tmpdir
|
|
15
|
+
* sem git e devem continuar passando.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const { execFileSync } = require('node:child_process');
|
|
21
|
+
|
|
22
|
+
const { parsePorcelain } = require('./git-baseline');
|
|
23
|
+
const { matchGlob } = require('./glob-match');
|
|
24
|
+
|
|
25
|
+
/** Estado do framework não é superfície de revisão (mesmo precedente do git-baseline). */
|
|
26
|
+
const FRAMEWORK_STATE_GLOB = '.aioson/**';
|
|
27
|
+
|
|
28
|
+
const DEFAULT_MAX_DIFF_BYTES = 200000;
|
|
29
|
+
|
|
30
|
+
function git(targetDir, gitArgs) {
|
|
31
|
+
return execFileSync('git', gitArgs, {
|
|
32
|
+
cwd: targetDir,
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
35
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a ref base do diff, na ordem:
|
|
41
|
+
* 1. `baseRef` explícito (--base)
|
|
42
|
+
* 2. `baseline.json` do plan dir (HEAD capturado no preflight do self:loop)
|
|
43
|
+
* 3. merge-base de HEAD com main/master (local)
|
|
44
|
+
* 4. 'HEAD' (apenas mudanças não commitadas)
|
|
45
|
+
*
|
|
46
|
+
* @returns {{ base: string, source: string }}
|
|
47
|
+
*/
|
|
48
|
+
function resolveBase(targetDir, planDir, baseRef) {
|
|
49
|
+
if (baseRef) return { base: String(baseRef), source: 'explicit --base' };
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const baselinePath = path.join(planDir, 'baseline.json');
|
|
53
|
+
if (fs.existsSync(baselinePath)) {
|
|
54
|
+
const baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));
|
|
55
|
+
if (baseline && baseline.head) return { base: baseline.head, source: 'baseline.json (loop preflight)' };
|
|
56
|
+
}
|
|
57
|
+
} catch { /* baseline ilegível — segue o fallback */ }
|
|
58
|
+
|
|
59
|
+
for (const branch of ['main', 'master']) {
|
|
60
|
+
try {
|
|
61
|
+
const mergeBase = git(targetDir, ['merge-base', 'HEAD', branch]).trim();
|
|
62
|
+
const head = git(targetDir, ['rev-parse', 'HEAD']).trim();
|
|
63
|
+
// merge-base === HEAD significa que estamos NO branch (ou atrás dele):
|
|
64
|
+
// o diff vs base seria vazio; cai para HEAD (mudanças não commitadas).
|
|
65
|
+
if (mergeBase && mergeBase !== head) return { base: mergeBase, source: `merge-base with ${branch}` };
|
|
66
|
+
} catch { /* branch ausente — tenta o próximo */ }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { base: 'HEAD', source: 'fallback (uncommitted changes only)' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Trunca em fronteira de linha com marcador; bytes UTF-8. */
|
|
73
|
+
function truncateDiff(diff, maxBytes) {
|
|
74
|
+
const bytes = Buffer.byteLength(diff, 'utf8');
|
|
75
|
+
if (bytes <= maxBytes) return { diff, truncated: false, bytes };
|
|
76
|
+
let slice = Buffer.from(diff, 'utf8').subarray(0, maxBytes).toString('utf8');
|
|
77
|
+
// remove eventual caractere multibyte cortado e fecha na última linha completa
|
|
78
|
+
slice = slice.replace(/�+$/g, '');
|
|
79
|
+
const lastNewline = slice.lastIndexOf('\n');
|
|
80
|
+
if (lastNewline > 0) slice = slice.slice(0, lastNewline);
|
|
81
|
+
return {
|
|
82
|
+
diff: `${slice}\n... [diff truncated at ${maxBytes} bytes of ${bytes} — read the changed files directly for the rest]`,
|
|
83
|
+
truncated: true,
|
|
84
|
+
bytes
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Sumariza last-check-output.json em linhas de texto; null quando ausente. */
|
|
89
|
+
function summarizeCheckOutput(planDir) {
|
|
90
|
+
const checkPath = path.join(planDir, 'last-check-output.json');
|
|
91
|
+
if (!fs.existsSync(checkPath)) return null;
|
|
92
|
+
try {
|
|
93
|
+
const report = JSON.parse(fs.readFileSync(checkPath, 'utf8'));
|
|
94
|
+
const lines = [
|
|
95
|
+
`Ran at: ${report.checked_at} — ${report.passed}/${report.executable_total} executable checks passed, ${report.skipped_no_verification} criteria without verification.`
|
|
96
|
+
];
|
|
97
|
+
for (const check of report.checks || []) {
|
|
98
|
+
lines.push(`- ${check.ok ? 'PASS' : 'FAIL'} ${check.id} — \`${check.command}\` (exit ${check.exitCode}${check.timedOut ? ', timeout' : ''})`);
|
|
99
|
+
}
|
|
100
|
+
lines.push('Copy these exit-code verdicts verbatim into `results[].passed` for their criteria — do not re-judge them.');
|
|
101
|
+
return lines.join('\n');
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Monta o payload de revisão. Nunca lança.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} targetDir — raiz do projeto
|
|
111
|
+
* @param {string} planDir — .aioson/plans/{slug}
|
|
112
|
+
* @param {object} opts
|
|
113
|
+
* @param {string} [opts.slug]
|
|
114
|
+
* @param {string} [opts.baseRef] — ref explícita (--base)
|
|
115
|
+
* @param {number} [opts.maxDiffBytes]
|
|
116
|
+
* @param {string} [opts.outputPath] — onde o validator grava o JSON
|
|
117
|
+
* @returns {{ ok, base, baseSource, changedFiles, untracked, truncated, diffBytes, hasChecks, text }}
|
|
118
|
+
*/
|
|
119
|
+
function buildReviewPayload(targetDir, planDir, opts = {}) {
|
|
120
|
+
const maxDiffBytes = Number.isInteger(Number(opts.maxDiffBytes)) && Number(opts.maxDiffBytes) > 0
|
|
121
|
+
? Number(opts.maxDiffBytes)
|
|
122
|
+
: DEFAULT_MAX_DIFF_BYTES;
|
|
123
|
+
const checkSummary = summarizeCheckOutput(planDir);
|
|
124
|
+
|
|
125
|
+
const header = [
|
|
126
|
+
'',
|
|
127
|
+
'---',
|
|
128
|
+
'',
|
|
129
|
+
'## Review payload (generated by `aioson harness:validate`)',
|
|
130
|
+
'',
|
|
131
|
+
'> Run this prompt in a **fresh, isolated context** (subagent/Task tool or a separate session). Never validate inline in the session that implemented the feature — the implementation history biases the verdict.',
|
|
132
|
+
''
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const outputBlock = [
|
|
136
|
+
'### Verdict output',
|
|
137
|
+
'',
|
|
138
|
+
`Write **only** the validator JSON verdict${opts.outputPath ? ` to: \`${opts.outputPath}\`` : ''}.`,
|
|
139
|
+
opts.slug ? `The orchestrating session then consumes it with: \`aioson harness:validate . --slug=${opts.slug}\`` : '',
|
|
140
|
+
''
|
|
141
|
+
].filter((line) => line !== '');
|
|
142
|
+
|
|
143
|
+
const checksBlock = [
|
|
144
|
+
'### Deterministic check results (`aioson harness:check`)',
|
|
145
|
+
'',
|
|
146
|
+
checkSummary || 'No `last-check-output.json` found — run `aioson harness:check` first (criteria with `verification` must be decided by exit code, not judgment).',
|
|
147
|
+
''
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
let gitFailed = false;
|
|
151
|
+
let base = null;
|
|
152
|
+
let baseSource = null;
|
|
153
|
+
let changedFiles = [];
|
|
154
|
+
let untracked = [];
|
|
155
|
+
let diffResult = { diff: '', truncated: false, bytes: 0 };
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const resolved = resolveBase(targetDir, planDir, opts.baseRef);
|
|
159
|
+
base = resolved.base;
|
|
160
|
+
baseSource = resolved.source;
|
|
161
|
+
|
|
162
|
+
const nameStatus = git(targetDir, ['diff', '--name-status', base]);
|
|
163
|
+
changedFiles = nameStatus
|
|
164
|
+
.split('\n')
|
|
165
|
+
.map((line) => line.trim())
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.map((line) => {
|
|
168
|
+
const [status, ...rest] = line.split('\t');
|
|
169
|
+
return { status, path: rest.join('\t') };
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
untracked = parsePorcelain(git(targetDir, ['status', '--porcelain', '-uall']))
|
|
173
|
+
.filter((entry) => entry.status === 'added' && !matchGlob(FRAMEWORK_STATE_GLOB, entry.path))
|
|
174
|
+
.map((entry) => entry.path);
|
|
175
|
+
|
|
176
|
+
diffResult = truncateDiff(git(targetDir, ['diff', base]), maxDiffBytes);
|
|
177
|
+
} catch {
|
|
178
|
+
gitFailed = true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let diffBlock;
|
|
182
|
+
if (gitFailed) {
|
|
183
|
+
diffBlock = [
|
|
184
|
+
'### Diff under review',
|
|
185
|
+
'',
|
|
186
|
+
'Diff unavailable (not a git repository or git failed). Review the files listed in `progress.json.completed_steps` directly.',
|
|
187
|
+
''
|
|
188
|
+
];
|
|
189
|
+
} else {
|
|
190
|
+
const fileLines = [
|
|
191
|
+
...changedFiles.map((f) => `- ${f.status} ${f.path}`),
|
|
192
|
+
...untracked.map((p) => `- ?? ${p} (untracked)`)
|
|
193
|
+
];
|
|
194
|
+
diffBlock = [
|
|
195
|
+
`### Changed files vs base \`${base}\` (${baseSource})`,
|
|
196
|
+
'',
|
|
197
|
+
fileLines.length ? fileLines.join('\n') : '(no changes detected)',
|
|
198
|
+
'',
|
|
199
|
+
'### Unified diff',
|
|
200
|
+
'',
|
|
201
|
+
'Untracked files do not appear in the diff below — read them directly.',
|
|
202
|
+
'',
|
|
203
|
+
'```diff',
|
|
204
|
+
diffResult.diff || '(empty diff)',
|
|
205
|
+
'```',
|
|
206
|
+
''
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const text = [...header, ...checksBlock, ...diffBlock, ...outputBlock].join('\n');
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
ok: !gitFailed,
|
|
214
|
+
base,
|
|
215
|
+
baseSource,
|
|
216
|
+
changedFiles,
|
|
217
|
+
untracked,
|
|
218
|
+
truncated: diffResult.truncated,
|
|
219
|
+
diffBytes: diffResult.bytes,
|
|
220
|
+
hasChecks: Boolean(checkSummary),
|
|
221
|
+
text
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
DEFAULT_MAX_DIFF_BYTES,
|
|
227
|
+
resolveBase,
|
|
228
|
+
truncateDiff,
|
|
229
|
+
buildReviewPayload
|
|
230
|
+
};
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -21,16 +21,16 @@ module.exports = {
|
|
|
21
21
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=en]',
|
|
22
22
|
help_agent_help:
|
|
23
23
|
'aioson agent:help [agent] [--json]',
|
|
24
|
-
help_agent_invoke:
|
|
25
|
-
'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=en]',
|
|
26
|
-
help_agent_epilogue:
|
|
27
|
-
'aioson agent:epilogue [path] --agent=<agent> --summary=<text> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=en]',
|
|
24
|
+
help_agent_invoke:
|
|
25
|
+
'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=en]',
|
|
26
|
+
help_agent_epilogue:
|
|
27
|
+
'aioson agent:epilogue [path] --agent=<agent> --summary=<text> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=en]',
|
|
28
28
|
help_context_validate: 'aioson context:validate [path] [--json] [--locale=en]',
|
|
29
|
-
help_context_pack:
|
|
30
|
-
'aioson context:pack [path] [--agent=<agent>] [--goal=<text>] [--module=<module-or-folder>] [--max-files=8] [--json] [--locale=en]',
|
|
31
|
-
help_context_select:
|
|
32
|
-
'aioson context:select [path] [--agent=<agent>] [--mode=planning|executing] [--task=<text>] [--paths=<path[,path2]>] [--feature=<slug>] [--json] [--locale=en]',
|
|
33
|
-
help_context_load:
|
|
29
|
+
help_context_pack:
|
|
30
|
+
'aioson context:pack [path] [--agent=<agent>] [--goal=<text>] [--module=<module-or-folder>] [--max-files=8] [--json] [--locale=en]',
|
|
31
|
+
help_context_select:
|
|
32
|
+
'aioson context:select [path] [--agent=<agent>] [--mode=planning|executing] [--task=<text>] [--paths=<path[,path2]>] [--feature=<slug>] [--json] [--locale=en]',
|
|
33
|
+
help_context_load:
|
|
34
34
|
'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<name> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=en]',
|
|
35
35
|
help_chain_audit:
|
|
36
36
|
'aioson chain:audit <file> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=en]',
|
|
@@ -122,10 +122,10 @@ module.exports = {
|
|
|
122
122
|
'aioson workflow:next [path] [--complete[=<agent>]] [--agent=<agent>] [--skip=<agent>] [--status] [--suggest] [--tool=codex|claude|opencode] [--json] [--locale=en]',
|
|
123
123
|
help_workflow_status:
|
|
124
124
|
'aioson workflow:status [path] [--suggest] [--tool=codex|claude|opencode] [--json] [--locale=en]',
|
|
125
|
-
help_workflow_execute:
|
|
126
|
-
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=en]',
|
|
127
|
-
help_review_cycle:
|
|
128
|
-
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=en]',
|
|
125
|
+
help_workflow_execute:
|
|
126
|
+
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=en]',
|
|
127
|
+
help_review_cycle:
|
|
128
|
+
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=en]',
|
|
129
129
|
help_parallel_init:
|
|
130
130
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=en]',
|
|
131
131
|
help_parallel_doctor:
|
|
@@ -155,7 +155,9 @@ module.exports = {
|
|
|
155
155
|
help_harness_init:
|
|
156
156
|
'aioson harness:init [path] --slug=<slug> [--mode=BALANCED|URGENT|ECONOMICAL] [--locale=en]',
|
|
157
157
|
help_harness_validate:
|
|
158
|
-
'aioson harness:validate [path] --slug=<slug> [--artifact=<path>] [--locale=en]',
|
|
158
|
+
'aioson harness:validate [path] --slug=<slug> [--base=<ref>] [--no-diff] [--max-diff-bytes=<n>] [--artifact=<path>] [--locale=en]',
|
|
159
|
+
help_harness_check:
|
|
160
|
+
'aioson harness:check [path] --slug=<slug> [--criteria=C1,C2] [--timeout=<ms>] [--json] [--locale=en]',
|
|
159
161
|
help_harness_retro:
|
|
160
162
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=en]',
|
|
161
163
|
help_harness_preview:
|
|
@@ -1082,7 +1084,11 @@ module.exports = {
|
|
|
1082
1084
|
contract_not_found: 'Contract not found for slug: {slug}',
|
|
1083
1085
|
validating: 'Validating harness for {slug}...',
|
|
1084
1086
|
blocked: 'Execution paused: {reason}',
|
|
1085
|
-
init_dry_run: '[dry-run] Would initialize harness for {slug}'
|
|
1087
|
+
init_dry_run: '[dry-run] Would initialize harness for {slug}',
|
|
1088
|
+
check_header: 'Harness check — {slug}',
|
|
1089
|
+
check_no_executable: ' No criteria with verification commands ({total} criteria total). @validator judges them all.',
|
|
1090
|
+
check_summary: ' Checks: {passed}/{executable} passed ({skipped} without verification — judged by @validator)',
|
|
1091
|
+
check_unknown_criteria: 'Unknown criteria ids: {ids}'
|
|
1086
1092
|
},
|
|
1087
1093
|
web_map: {
|
|
1088
1094
|
url_missing: 'Missing required option: --url=<url>.',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -22,16 +22,16 @@ module.exports = {
|
|
|
22
22
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=es]',
|
|
23
23
|
help_agent_help:
|
|
24
24
|
'aioson agent:help [agent] [--json]',
|
|
25
|
-
help_agent_invoke:
|
|
26
|
-
'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=es]',
|
|
27
|
-
help_agent_epilogue:
|
|
28
|
-
'aioson agent:epilogue [path] --agent=<agente> --summary=<texto> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=es]',
|
|
25
|
+
help_agent_invoke:
|
|
26
|
+
'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=es]',
|
|
27
|
+
help_agent_epilogue:
|
|
28
|
+
'aioson agent:epilogue [path] --agent=<agente> --summary=<texto> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=es]',
|
|
29
29
|
help_context_validate: 'aioson context:validate [path] [--json] [--locale=es]',
|
|
30
|
-
help_context_pack:
|
|
31
|
-
'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-o-carpeta>] [--max-files=8] [--json] [--locale=es]',
|
|
32
|
-
help_context_select:
|
|
33
|
-
'aioson context:select [path] [--agent=<agente>] [--mode=planning|executing] [--task=<texto>] [--paths=<ruta[,ruta2]>] [--feature=<slug>] [--json] [--locale=es]',
|
|
34
|
-
help_context_load:
|
|
30
|
+
help_context_pack:
|
|
31
|
+
'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-o-carpeta>] [--max-files=8] [--json] [--locale=es]',
|
|
32
|
+
help_context_select:
|
|
33
|
+
'aioson context:select [path] [--agent=<agente>] [--mode=planning|executing] [--task=<texto>] [--paths=<ruta[,ruta2]>] [--feature=<slug>] [--json] [--locale=es]',
|
|
34
|
+
help_context_load:
|
|
35
35
|
'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nombre> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=es]',
|
|
36
36
|
help_chain_audit:
|
|
37
37
|
'aioson chain:audit <archivo> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=es]',
|
|
@@ -112,10 +112,10 @@ module.exports = {
|
|
|
112
112
|
'aioson test:package [source-path] [--keep] [--dry-run] [--json] [--locale=es]',
|
|
113
113
|
help_workflow_plan:
|
|
114
114
|
'aioson workflow:plan [path] [--classification=MICRO|SMALL|MEDIUM] [--json] [--locale=es]',
|
|
115
|
-
help_workflow_execute:
|
|
116
|
-
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=es]',
|
|
117
|
-
help_review_cycle:
|
|
118
|
-
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=es]',
|
|
115
|
+
help_workflow_execute:
|
|
116
|
+
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=es]',
|
|
117
|
+
help_review_cycle:
|
|
118
|
+
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=es]',
|
|
119
119
|
help_parallel_init:
|
|
120
120
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=es]',
|
|
121
121
|
help_parallel_doctor:
|
|
@@ -142,6 +142,8 @@ module.exports = {
|
|
|
142
142
|
'aioson qa:scan [path] [--url=<app-url>] [--depth=3] [--max-pages=50] [--headed] [--html] [--json] [--locale=es]',
|
|
143
143
|
help_qa_report:
|
|
144
144
|
'aioson qa:report [path] [--html] [--json] [--locale=es]',
|
|
145
|
+
help_harness_check:
|
|
146
|
+
'aioson harness:check [path] --slug=<slug> [--criteria=C1,C2] [--timeout=<ms>] [--json] [--locale=es]',
|
|
145
147
|
help_harness_retro:
|
|
146
148
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=es]',
|
|
147
149
|
help_harness_preview:
|
package/src/i18n/messages/fr.js
CHANGED
|
@@ -22,16 +22,16 @@ module.exports = {
|
|
|
22
22
|
'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=fr]',
|
|
23
23
|
help_agent_help:
|
|
24
24
|
'aioson agent:help [agent] [--json]',
|
|
25
|
-
help_agent_invoke:
|
|
26
|
-
'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=fr]',
|
|
27
|
-
help_agent_epilogue:
|
|
28
|
-
'aioson agent:epilogue [path] --agent=<agent> --summary=<texte> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=fr]',
|
|
25
|
+
help_agent_invoke:
|
|
26
|
+
'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=fr]',
|
|
27
|
+
help_agent_epilogue:
|
|
28
|
+
'aioson agent:epilogue [path] --agent=<agent> --summary=<texte> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=fr]',
|
|
29
29
|
help_context_validate: 'aioson context:validate [path] [--json] [--locale=fr]',
|
|
30
|
-
help_context_pack:
|
|
31
|
-
'aioson context:pack [path] [--agent=<agent>] [--goal=<texte>] [--module=<module-ou-dossier>] [--max-files=8] [--json] [--locale=fr]',
|
|
32
|
-
help_context_select:
|
|
33
|
-
'aioson context:select [path] [--agent=<agent>] [--mode=planning|executing] [--task=<texte>] [--paths=<chemin[,chemin2]>] [--feature=<slug>] [--json] [--locale=fr]',
|
|
34
|
-
help_context_load:
|
|
30
|
+
help_context_pack:
|
|
31
|
+
'aioson context:pack [path] [--agent=<agent>] [--goal=<texte>] [--module=<module-ou-dossier>] [--max-files=8] [--json] [--locale=fr]',
|
|
32
|
+
help_context_select:
|
|
33
|
+
'aioson context:select [path] [--agent=<agent>] [--mode=planning|executing] [--task=<texte>] [--paths=<chemin[,chemin2]>] [--feature=<slug>] [--json] [--locale=fr]',
|
|
34
|
+
help_context_load:
|
|
35
35
|
'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nom> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=fr]',
|
|
36
36
|
help_chain_audit:
|
|
37
37
|
'aioson chain:audit <fichier> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=fr]',
|
|
@@ -112,10 +112,10 @@ module.exports = {
|
|
|
112
112
|
'aioson test:package [source-path] [--keep] [--dry-run] [--json] [--locale=fr]',
|
|
113
113
|
help_workflow_plan:
|
|
114
114
|
'aioson workflow:plan [path] [--classification=MICRO|SMALL|MEDIUM] [--json] [--locale=fr]',
|
|
115
|
-
help_workflow_execute:
|
|
116
|
-
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=fr]',
|
|
117
|
-
help_review_cycle:
|
|
118
|
-
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=fr]',
|
|
115
|
+
help_workflow_execute:
|
|
116
|
+
'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=fr]',
|
|
117
|
+
help_review_cycle:
|
|
118
|
+
'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=fr]',
|
|
119
119
|
help_parallel_init:
|
|
120
120
|
'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=fr]',
|
|
121
121
|
help_parallel_doctor:
|
|
@@ -142,6 +142,8 @@ module.exports = {
|
|
|
142
142
|
'aioson qa:scan [path] [--url=<app-url>] [--depth=3] [--max-pages=50] [--headed] [--html] [--json] [--locale=fr]',
|
|
143
143
|
help_qa_report:
|
|
144
144
|
'aioson qa:report [path] [--html] [--json] [--locale=fr]',
|
|
145
|
+
help_harness_check:
|
|
146
|
+
'aioson harness:check [path] --slug=<slug> [--criteria=C1,C2] [--timeout=<ms>] [--json] [--locale=fr]',
|
|
145
147
|
help_harness_retro:
|
|
146
148
|
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=fr]',
|
|
147
149
|
help_harness_preview:
|