@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/docs/en/4-agents/README.md +11 -8
  3. package/docs/en/4-agents/forge-run.md +165 -0
  4. package/docs/en/5-reference/README.md +1 -0
  5. package/docs/en/5-reference/cli-reference.md +199 -85
  6. package/docs/en/5-reference/executable-verification.md +165 -0
  7. package/docs/pt/4-agentes/README.md +2 -1
  8. package/docs/pt/4-agentes/forge-run.md +150 -0
  9. package/docs/pt/4-agentes/pm.md +8 -0
  10. package/docs/pt/4-agentes/qa.md +2 -0
  11. package/docs/pt/4-agentes/scope-check.md +19 -1
  12. package/docs/pt/4-agentes/sheldon.md +2 -0
  13. package/docs/pt/4-agentes/validator.md +20 -0
  14. package/docs/pt/5-referencia/autopilot-handoff.md +33 -0
  15. package/docs/pt/5-referencia/comandos-cli.md +64 -9
  16. package/docs/pt/5-referencia/fluxo-artefatos.md +40 -15
  17. package/docs/pt/5-referencia/loop-guardrails.md +19 -0
  18. package/docs/pt/5-referencia/sdd-automation-scripts.md +130 -26
  19. package/package.json +1 -1
  20. package/src/cli.js +70 -54
  21. package/src/commands/context-select.js +1 -0
  22. package/src/commands/forge-compile.js +330 -0
  23. package/src/commands/harness-check.js +159 -0
  24. package/src/commands/harness.js +37 -2
  25. package/src/commands/spec-analyze.js +324 -0
  26. package/src/constants.js +118 -108
  27. package/src/context-selector.js +28 -2
  28. package/src/gateway-pointer-merge.js +25 -4
  29. package/src/harness/contract-schema.js +8 -0
  30. package/src/harness/plan-waves.js +77 -0
  31. package/src/harness/review-payload.js +230 -0
  32. package/src/i18n/messages/en.js +21 -15
  33. package/src/i18n/messages/es.js +15 -13
  34. package/src/i18n/messages/fr.js +15 -13
  35. package/src/i18n/messages/pt-BR.js +21 -15
  36. package/src/parser.js +3 -1
  37. package/template/.aioson/agents/dev.md +67 -66
  38. package/template/.aioson/agents/deyvin.md +79 -74
  39. package/template/.aioson/agents/forge-run.md +57 -0
  40. package/template/.aioson/agents/pm.md +51 -45
  41. package/template/.aioson/agents/qa.md +22 -22
  42. package/template/.aioson/agents/scope-check.md +49 -46
  43. package/template/.aioson/agents/sheldon.md +1 -1
  44. package/template/.aioson/agents/validator.md +16 -5
  45. package/template/.aioson/docs/autopilot-handoff.md +34 -32
  46. package/template/.aioson/docs/sheldon/harness-contract.md +19 -2
  47. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +9 -7
  48. package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +19 -15
  49. package/template/.claude/commands/aioson/agent/forge-run.md +17 -0
  50. package/template/AGENTS.md +7 -5
  51. package/template/CLAUDE.md +4 -3
  52. package/template/OPENCODE.md +24 -22
@@ -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 separator = existing.length === 0 ? '' : existing.endsWith('\n\n') ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
66
- next = `${existing}${separator}${block}`;
67
- action = 'block_appended';
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
+ };
@@ -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>.',
@@ -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:
@@ -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: