@jaimevalasek/aioson 1.33.1 → 1.37.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 (162) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/docs/en/1-understand/ecosystem-map.md +45 -29
  3. package/docs/en/1-understand/glossary.md +6 -6
  4. package/docs/en/1-understand/what-is-aioson.md +5 -5
  5. package/docs/en/2-start/existing-project.md +7 -7
  6. package/docs/en/2-start/first-project.md +33 -39
  7. package/docs/en/2-start/initial-decisions.md +22 -19
  8. package/docs/en/3-recipes/README.md +2 -2
  9. package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
  10. package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +13 -3
  11. package/docs/en/3-recipes/full-feature-with-sheldon.md +329 -338
  12. package/docs/en/4-agents/README.md +33 -16
  13. package/docs/en/4-agents/briefing-refiner.md +146 -0
  14. package/docs/en/4-agents/discovery-design-doc.md +20 -8
  15. package/docs/en/4-agents/forge-run.md +3 -2
  16. package/docs/en/5-reference/README.md +1 -0
  17. package/docs/en/5-reference/autopilot-handoff.md +286 -0
  18. package/docs/en/5-reference/cli-reference.md +57 -48
  19. package/docs/en/5-reference/executable-verification.md +10 -7
  20. package/docs/en/5-reference/parallel.md +2 -0
  21. package/docs/en/5-reference/qa-browser.md +2 -2
  22. package/docs/en/README.md +3 -3
  23. package/docs/pt/1-entender/glossario.md +5 -5
  24. package/docs/pt/1-entender/mapa-do-ecossistema.md +33 -22
  25. package/docs/pt/2-comecar/decisoes-iniciais.md +20 -18
  26. package/docs/pt/2-comecar/primeiro-projeto.md +2 -2
  27. package/docs/pt/3-receitas/README.md +1 -1
  28. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  29. package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +8 -1
  30. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +291 -338
  31. package/docs/pt/4-agentes/README.md +13 -11
  32. package/docs/pt/4-agentes/analyst.md +9 -5
  33. package/docs/pt/4-agentes/architect.md +9 -5
  34. package/docs/pt/4-agentes/briefing-refiner.md +64 -40
  35. package/docs/pt/4-agentes/briefing.md +6 -1
  36. package/docs/pt/4-agentes/dev.md +38 -8
  37. package/docs/pt/4-agentes/deyvin.md +4 -0
  38. package/docs/pt/4-agentes/discover.md +4 -0
  39. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  40. package/docs/pt/4-agentes/neo.md +4 -0
  41. package/docs/pt/4-agentes/orache.md +6 -0
  42. package/docs/pt/4-agentes/orchestrator.md +176 -118
  43. package/docs/pt/4-agentes/pentester.md +11 -5
  44. package/docs/pt/4-agentes/pm.md +15 -7
  45. package/docs/pt/4-agentes/product.md +19 -1
  46. package/docs/pt/4-agentes/qa.md +10 -2
  47. package/docs/pt/4-agentes/setup.md +3 -1
  48. package/docs/pt/4-agentes/sheldon.md +178 -99
  49. package/docs/pt/4-agentes/tester.md +6 -0
  50. package/docs/pt/4-agentes/ux-ui.md +9 -5
  51. package/docs/pt/5-referencia/README.md +1 -1
  52. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  53. package/docs/pt/5-referencia/autopilot-handoff.md +191 -74
  54. package/docs/pt/5-referencia/comandos-cli.md +16 -7
  55. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  56. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  57. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  58. package/docs/pt/5-referencia/skills.md +2 -0
  59. package/docs/pt/README.md +4 -4
  60. package/docs/pt/agentes.md +50 -50
  61. package/package.json +2 -2
  62. package/src/agents.js +1 -1
  63. package/src/artifact-kinds.js +111 -0
  64. package/src/autopilot-signal.js +71 -0
  65. package/src/cli.js +112 -81
  66. package/src/commands/agent-epilogue.js +251 -186
  67. package/src/commands/agents.js +122 -50
  68. package/src/commands/audit-code.js +344 -0
  69. package/src/commands/briefing.js +337 -1
  70. package/src/commands/classify.js +389 -389
  71. package/src/commands/feature-close.js +122 -4
  72. package/src/commands/harness-check.js +259 -175
  73. package/src/commands/live.js +71 -11
  74. package/src/commands/review-feature.js +189 -0
  75. package/src/commands/runtime.js +81 -66
  76. package/src/commands/sync-agents-copy.js +115 -0
  77. package/src/commands/update.js +5 -1
  78. package/src/commands/verification-plan.js +143 -0
  79. package/src/commands/verify-artifact.js +593 -0
  80. package/src/commands/workflow-execute.js +434 -316
  81. package/src/commands/workflow-next.js +193 -20
  82. package/src/commands/workflow-plan.js +5 -5
  83. package/src/constants.js +2 -0
  84. package/src/doctor.js +4 -2
  85. package/src/gateway-pointer-merge.js +7 -1
  86. package/src/handoff-contract.js +267 -172
  87. package/src/harness/contract-integrity-gate.js +172 -0
  88. package/src/harness/contract-integrity.js +111 -0
  89. package/src/harness/contract-schema.js +377 -332
  90. package/src/harness/detect-runtime-feature.js +90 -0
  91. package/src/harness/static-criteria.js +192 -0
  92. package/src/i18n/messages/en.js +10 -5
  93. package/src/i18n/messages/es.js +10 -5
  94. package/src/i18n/messages/fr.js +10 -5
  95. package/src/i18n/messages/pt-BR.js +10 -5
  96. package/src/install-wizard.js +8 -8
  97. package/src/installer.js +13 -6
  98. package/src/lib/briefing-refiner/apply-feedback.js +18 -4
  99. package/src/lib/briefing-refiner/feedback-schema.js +73 -4
  100. package/src/lib/briefing-refiner/refinement-report.js +11 -0
  101. package/src/lib/briefing-refiner/review-html.js +388 -68
  102. package/src/parser.js +12 -0
  103. package/src/preflight-engine.js +12 -12
  104. package/src/verification/policy-engine.js +95 -95
  105. package/src/verification-policy.js +398 -0
  106. package/src/version.js +52 -1
  107. package/template/.aioson/agents/architect.md +34 -5
  108. package/template/.aioson/agents/briefing-refiner.md +91 -48
  109. package/template/.aioson/agents/briefing.md +4 -0
  110. package/template/.aioson/agents/committer.md +2 -1
  111. package/template/.aioson/agents/copywriter.md +30 -21
  112. package/template/.aioson/agents/design-hybrid-forge.md +10 -1
  113. package/template/.aioson/agents/dev.md +35 -23
  114. package/template/.aioson/agents/deyvin.md +4 -0
  115. package/template/.aioson/agents/discover.md +16 -3
  116. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  117. package/template/.aioson/agents/genome.md +19 -10
  118. package/template/.aioson/agents/neo.md +34 -30
  119. package/template/.aioson/agents/orache.md +24 -11
  120. package/template/.aioson/agents/orchestrator.md +100 -7
  121. package/template/.aioson/agents/pentester.md +4 -0
  122. package/template/.aioson/agents/pm.md +8 -8
  123. package/template/.aioson/agents/product.md +25 -1
  124. package/template/.aioson/agents/profiler-enricher.md +20 -11
  125. package/template/.aioson/agents/profiler-forge.md +20 -11
  126. package/template/.aioson/agents/profiler-researcher.md +20 -11
  127. package/template/.aioson/agents/qa.md +96 -31
  128. package/template/.aioson/agents/setup.md +12 -1
  129. package/template/.aioson/agents/sheldon.md +103 -13
  130. package/template/.aioson/agents/site-forge.md +11 -2
  131. package/template/.aioson/agents/squad.md +20 -5
  132. package/template/.aioson/agents/tester.md +4 -0
  133. package/template/.aioson/agents/ux-ui.md +2 -1
  134. package/template/.aioson/agents/validator.md +33 -1
  135. package/template/.aioson/config/verification.json +61 -0
  136. package/template/.aioson/config.md +13 -9
  137. package/template/.aioson/docs/LAYERS.md +2 -2
  138. package/template/.aioson/docs/agent-help.md +126 -0
  139. package/template/.aioson/docs/autopilot-handoff.md +30 -20
  140. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  141. package/template/.aioson/docs/dev/phase-loop.md +50 -0
  142. package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
  143. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  144. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  145. package/template/.aioson/docs/product/prd-contract.md +12 -12
  146. package/template/.aioson/docs/prototype-contract.md +21 -4
  147. package/template/.aioson/docs/reference-identity.md +94 -0
  148. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  149. package/template/.aioson/docs/verification-config.md +82 -0
  150. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  151. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  152. package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
  153. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  154. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  155. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  156. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  157. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  158. package/template/.aioson/skills/process/prototype-forge/SKILL.md +6 -0
  159. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  160. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  161. package/template/AGENTS.md +36 -36
  162. package/template/CLAUDE.md +15 -11
@@ -257,11 +257,11 @@ async function scanArtifacts(targetDir, slug) {
257
257
  : [path.join(dir, 'readiness.md')];
258
258
 
259
259
  const results = {
260
- project_context: await check('project.context', path.join(dir, 'project.context.md')),
261
- prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
262
- sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
263
- sheldon_validation: slug ? await check('sheldon-validation', path.join(dir, `sheldon-validation-${slug}.md`)) : { exists: false },
264
- requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
260
+ project_context: await check('project.context', path.join(dir, 'project.context.md')),
261
+ prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
262
+ sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
263
+ sheldon_validation: slug ? await check('sheldon-validation', path.join(dir, `sheldon-validation-${slug}.md`)) : { exists: false },
264
+ requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
265
265
  spec: slug ? await check('spec', path.join(dir, `spec-${slug}.md`)) : await check('spec', path.join(dir, 'spec.md')),
266
266
  architecture: await check('architecture', path.join(dir, 'architecture.md')),
267
267
  design_doc: await checkFirst('design-doc', designDocCandidates),
@@ -480,11 +480,11 @@ function buildContextPackage(agent, slug, classification, artifacts, devState, m
480
480
  const downstreamAgents = ['discovery-design-doc', 'pm', 'orchestrator', 'dev', 'deyvin', 'qa'];
481
481
  const shouldCarryFullFeatureContext = downstreamAgents.includes(agent);
482
482
 
483
- if (shouldCarryFullFeatureContext && artifacts.prd.exists) pkg.push(artifacts.prd.path);
484
- if (shouldCarryFullFeatureContext && artifacts.sheldon_enrichment.exists) pkg.push(artifacts.sheldon_enrichment.path);
485
- if (shouldCarryFullFeatureContext && artifacts.sheldon_validation && artifacts.sheldon_validation.exists) {
486
- pkg.push(artifacts.sheldon_validation.path);
487
- }
483
+ if (shouldCarryFullFeatureContext && artifacts.prd.exists) pkg.push(artifacts.prd.path);
484
+ if (shouldCarryFullFeatureContext && artifacts.sheldon_enrichment.exists) pkg.push(artifacts.sheldon_enrichment.path);
485
+ if (shouldCarryFullFeatureContext && artifacts.sheldon_validation && artifacts.sheldon_validation.exists) {
486
+ pkg.push(artifacts.sheldon_validation.path);
487
+ }
488
488
  if ((shouldCarryFullFeatureContext || ['analyst', 'architect'].includes(agent)) && artifacts.requirements.exists) {
489
489
  pkg.push(artifacts.requirements.path);
490
490
  }
@@ -685,10 +685,10 @@ function evaluateReadiness(artifacts, phaseGates, classification, agent, devStat
685
685
  if (!artifacts.spec.exists) blockers.push('spec file missing');
686
686
  if (classification && classification !== 'MICRO') {
687
687
  if (!designDoc.exists) {
688
- blockers.push('design-doc.md missing — @discovery-design-doc must run before implementation');
688
+ blockers.push('design-doc.md missing — @architect (merged mode) or @sheldon must produce it before implementation');
689
689
  }
690
690
  if (!readiness.exists) {
691
- blockers.push('readiness.md missing — @discovery-design-doc must run before implementation');
691
+ blockers.push('readiness.md missing — @architect (merged mode) or @sheldon must produce it before implementation');
692
692
  }
693
693
  }
694
694
  if (classification === 'MEDIUM') {
@@ -1,95 +1,95 @@
1
- 'use strict';
2
-
3
- const { normalizePolicy } = require('./result');
4
-
5
- function ownerRoute(owner) {
6
- if (owner === 'product' || owner === 'scope-check') return 'product';
7
- if (owner === 'sheldon') return 'sheldon';
8
- if (owner === 'qa' || owner === 'tester') return 'qa';
9
- if (owner === 'pentester') return 'pentester';
10
- return owner || 'dev';
11
- }
12
-
13
- function routeForFinding(finding) {
14
- const route = finding.recommended_route || ownerRoute(finding.owner);
15
- if (finding.kind === 'scope_constraint' || route === 'product' || route === 'sheldon') {
16
- return { verdict: 'NEEDS_SCOPE_DECISION', route: route === 'sheldon' ? 'sheldon' : 'product' };
17
- }
18
- if (finding.kind === 'test_coverage' || route === 'qa' || route === 'tester') {
19
- return { verdict: 'NEEDS_QA_RECHECK', route: route === 'tester' ? 'tester' : 'qa' };
20
- }
21
- if (finding.kind === 'security_constraint' || route === 'pentester') {
22
- return { verdict: 'NEEDS_SECURITY_REVIEW', route: 'pentester' };
23
- }
24
- return { verdict: 'NEEDS_DEV_FIX', route: route === 'deyvin' ? 'deyvin' : 'dev' };
25
- }
26
-
27
- function isBlockingFinding(finding, policy) {
28
- if (!finding) return false;
29
- if (finding.severity === 'blocking' || finding.blocks === true) return true;
30
- if (policy !== 'strict') return false;
31
- return ['DOES_NOT_CONFIRM', 'PARTIAL', 'NOT_VERIFIED'].includes(finding.status)
32
- && [
33
- 'required_behavior',
34
- 'acceptance_criterion',
35
- 'prototype_contract',
36
- 'security_constraint'
37
- ].includes(finding.kind);
38
- }
39
-
40
- function commandIsMissing(command) {
41
- if (!command || !command.required) return false;
42
- const status = String(command.status || command.last_status || '').toLowerCase();
43
- return !status || ['missing', 'not_run', 'failed', 'error', 'unknown'].includes(status);
44
- }
45
-
46
- function applyPolicy(report, requestedPolicy) {
47
- const policy = normalizePolicy(requestedPolicy || report.policy) || 'standard';
48
- const findings = Array.isArray(report.findings) ? report.findings : [];
49
- const commands = Array.isArray(report.commands_run) ? report.commands_run : [];
50
-
51
- if (policy === 'strict' && commands.some(commandIsMissing)) {
52
- return {
53
- policy,
54
- verdict: 'INCONCLUSIVE',
55
- recommended_route: 'qa',
56
- blocking_findings_count: findings.filter((f) => isBlockingFinding(f, policy)).length,
57
- reason: 'missing_required_command'
58
- };
59
- }
60
-
61
- const blockingFindings = findings.filter((finding) => isBlockingFinding(finding, policy));
62
- if (blockingFindings.length > 0 && policy !== 'advisory') {
63
- const routed = routeForFinding(blockingFindings[0]);
64
- return {
65
- policy,
66
- verdict: routed.verdict,
67
- recommended_route: routed.route,
68
- blocking_findings_count: blockingFindings.length,
69
- reason: 'blocking_findings'
70
- };
71
- }
72
-
73
- if (report.verdict && report.verdict !== 'PASS') {
74
- return {
75
- policy,
76
- verdict: report.verdict,
77
- recommended_route: report.recommended_route || 'dev',
78
- blocking_findings_count: blockingFindings.length,
79
- reason: 'auditor_verdict'
80
- };
81
- }
82
-
83
- return {
84
- policy,
85
- verdict: 'PASS',
86
- recommended_route: report.recommended_route || 'qa',
87
- blocking_findings_count: blockingFindings.length,
88
- reason: 'no_blocking_findings'
89
- };
90
- }
91
-
92
- module.exports = {
93
- applyPolicy,
94
- routeForFinding
95
- };
1
+ 'use strict';
2
+
3
+ const { normalizePolicy } = require('./result');
4
+
5
+ function ownerRoute(owner) {
6
+ if (owner === 'product' || owner === 'scope-check') return 'product';
7
+ if (owner === 'sheldon') return 'sheldon';
8
+ if (owner === 'qa' || owner === 'tester') return 'qa';
9
+ if (owner === 'pentester') return 'pentester';
10
+ return owner || 'dev';
11
+ }
12
+
13
+ function routeForFinding(finding) {
14
+ const route = finding.recommended_route || ownerRoute(finding.owner);
15
+ if (finding.kind === 'scope_constraint' || route === 'product' || route === 'sheldon') {
16
+ return { verdict: 'NEEDS_SCOPE_DECISION', route: route === 'sheldon' ? 'sheldon' : 'product' };
17
+ }
18
+ if (finding.kind === 'test_coverage' || route === 'qa' || route === 'tester') {
19
+ return { verdict: 'NEEDS_QA_RECHECK', route: route === 'tester' ? 'tester' : 'qa' };
20
+ }
21
+ if (finding.kind === 'security_constraint' || route === 'pentester') {
22
+ return { verdict: 'NEEDS_SECURITY_REVIEW', route: 'pentester' };
23
+ }
24
+ return { verdict: 'NEEDS_DEV_FIX', route: route === 'deyvin' ? 'deyvin' : 'dev' };
25
+ }
26
+
27
+ function isBlockingFinding(finding, policy) {
28
+ if (!finding) return false;
29
+ if (finding.severity === 'blocking' || finding.blocks === true) return true;
30
+ if (policy !== 'strict') return false;
31
+ return ['DOES_NOT_CONFIRM', 'PARTIAL', 'NOT_VERIFIED'].includes(finding.status)
32
+ && [
33
+ 'required_behavior',
34
+ 'acceptance_criterion',
35
+ 'prototype_contract',
36
+ 'security_constraint'
37
+ ].includes(finding.kind);
38
+ }
39
+
40
+ function commandIsMissing(command) {
41
+ if (!command || !command.required) return false;
42
+ const status = String(command.status || command.last_status || '').toLowerCase();
43
+ return !status || ['missing', 'not_run', 'failed', 'error', 'unknown'].includes(status);
44
+ }
45
+
46
+ function applyPolicy(report, requestedPolicy) {
47
+ const policy = normalizePolicy(requestedPolicy || report.policy) || 'standard';
48
+ const findings = Array.isArray(report.findings) ? report.findings : [];
49
+ const commands = Array.isArray(report.commands_run) ? report.commands_run : [];
50
+
51
+ if (policy === 'strict' && commands.some(commandIsMissing)) {
52
+ return {
53
+ policy,
54
+ verdict: 'INCONCLUSIVE',
55
+ recommended_route: 'qa',
56
+ blocking_findings_count: findings.filter((f) => isBlockingFinding(f, policy)).length,
57
+ reason: 'missing_required_command'
58
+ };
59
+ }
60
+
61
+ const blockingFindings = findings.filter((finding) => isBlockingFinding(finding, policy));
62
+ if (blockingFindings.length > 0 && policy !== 'advisory') {
63
+ const routed = routeForFinding(blockingFindings[0]);
64
+ return {
65
+ policy,
66
+ verdict: routed.verdict,
67
+ recommended_route: routed.route,
68
+ blocking_findings_count: blockingFindings.length,
69
+ reason: 'blocking_findings'
70
+ };
71
+ }
72
+
73
+ if (report.verdict && report.verdict !== 'PASS') {
74
+ return {
75
+ policy,
76
+ verdict: report.verdict,
77
+ recommended_route: report.recommended_route || 'dev',
78
+ blocking_findings_count: blockingFindings.length,
79
+ reason: 'auditor_verdict'
80
+ };
81
+ }
82
+
83
+ return {
84
+ policy,
85
+ verdict: 'PASS',
86
+ recommended_route: report.recommended_route || 'qa',
87
+ blocking_findings_count: blockingFindings.length,
88
+ reason: 'no_blocking_findings'
89
+ };
90
+ }
91
+
92
+ module.exports = {
93
+ applyPolicy,
94
+ routeForFinding
95
+ };
@@ -0,0 +1,398 @@
1
+ 'use strict';
2
+
3
+ // Verification sub-agent policy (.aioson/config/verification.json).
4
+ //
5
+ // Piece 1 of the "smart harness, fewer agents doing more" redesign. This is the
6
+ // reader the @dev phase-loop (auto-continue) and the post-dev review cycle
7
+ // consult to decide WHICH verification sub-agent runs, WHEN, and on WHICH model
8
+ // — resolved per host harness.
9
+ //
10
+ // Two dispatch modes per agent, keyed by the host harness (claude|codex|opencode):
11
+ // - native: in-harness sub-agent. On Claude Code the Task tool runs a Claude
12
+ // model tier (e.g. sonnet-4.6); codex/opencode run their own
13
+ // configured model. You CANNOT run a codex/gpt model as a native
14
+ // Claude Code sub-agent — that is what `external` is for.
15
+ // - external: spawn a different vendor CLI as a read-only auditor
16
+ // (`aioson verify:implementation --tool=...`). The only way to bring
17
+ // a cross-vendor model in while hosted elsewhere — see `cross_check`.
18
+ //
19
+ // The file is auto-generated from template/.aioson/config/verification.json by the
20
+ // installer (isConfigMergePath additive merge) and is hand-editable: user values
21
+ // survive `aioson update`, only `version` is template-owned. A missing or
22
+ // malformed file degrades to buildDefaultVerificationConfig() — never throws.
23
+
24
+ const fs = require('node:fs/promises');
25
+ const path = require('node:path');
26
+ const { exists } = require('./utils');
27
+
28
+ const VERIFICATION_CONFIG_RELATIVE_PATH = '.aioson/config/verification.json';
29
+ const KNOWN_HOSTS = ['claude', 'codex', 'opencode'];
30
+ const DISPATCH_MODES = ['native', 'external'];
31
+ const VERIFICATION_AGENTS = ['qa', 'tester', 'pentester', 'validator'];
32
+ const TRIGGERS = ['per-phase', 'end-of-feature', 'sensitive-surface'];
33
+ const DEFAULT_HOST = 'claude';
34
+ const DEFAULT_MODEL = 'configured-default';
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Defaults
38
+ // ---------------------------------------------------------------------------
39
+
40
+ function defaultTriggers(agentId) {
41
+ switch (agentId) {
42
+ case 'qa': return ['per-phase', 'end-of-feature'];
43
+ case 'pentester': return ['sensitive-surface', 'end-of-feature'];
44
+ default: return ['end-of-feature'];
45
+ }
46
+ }
47
+
48
+ function defaultReport(agentId) {
49
+ switch (agentId) {
50
+ case 'qa': return 'qa-report-{slug}.md';
51
+ case 'tester': return 'test-report-{slug}.md';
52
+ case 'pentester': return 'security-findings-{slug}.json';
53
+ case 'validator': return 'validator-report-{slug}.json';
54
+ default: return `${agentId}-report-{slug}.md`;
55
+ }
56
+ }
57
+
58
+ function buildDefaultAgent(agentId) {
59
+ // pentester/validator default to the strongest tier; qa/tester to a cheaper
60
+ // tier — the per-phase verifier should be cheap, the security/contract
61
+ // verdicts should not be.
62
+ const claudeModel = (agentId === 'pentester' || agentId === 'validator') ? 'opus-4.8' : 'sonnet-4.6';
63
+ const agent = {
64
+ enabled: (agentId === 'qa' || agentId === 'validator') ? true : 'auto',
65
+ triggers: defaultTriggers(agentId),
66
+ dispatch: {
67
+ claude: { mode: 'native', model: claudeModel },
68
+ codex: { mode: 'native', model: DEFAULT_MODEL },
69
+ opencode: { mode: 'native', model: DEFAULT_MODEL }
70
+ },
71
+ report: defaultReport(agentId)
72
+ };
73
+ if (agentId === 'validator') {
74
+ agent.cross_check = { enabled: false, mode: 'external', tool: 'codex', model: DEFAULT_MODEL };
75
+ }
76
+ return agent;
77
+ }
78
+
79
+ function buildDefaultVerificationConfig() {
80
+ const agents = {};
81
+ for (const id of VERIFICATION_AGENTS) agents[id] = buildDefaultAgent(id);
82
+ return {
83
+ version: '1.0',
84
+ host: 'auto',
85
+ agents,
86
+ budget: {
87
+ max_subagents_per_phase: 1,
88
+ skip_on_micro: true,
89
+ full_smoke: 'end-of-feature-only'
90
+ },
91
+ phase_loop: {
92
+ auto_continue: true,
93
+ compact_between_phases: true,
94
+ max_fix_retries_per_phase: 2
95
+ },
96
+ // Deterministic build-free code-quality scan (`aioson audit:code`) wired into
97
+ // the tracked workflow:next dev/qa done-gate. `tracked_gate`: 'advisory'
98
+ // records + emits but never blocks (default — audit:code is a heuristic
99
+ // opinion, not the feature's declared contract); 'block' makes a HIGH finding
100
+ // in scope a hard gate; 'off' skips. `scope`: 'changed' = the git diff (fast),
101
+ // 'full' = the whole tree.
102
+ audit_code: {
103
+ tracked_gate: 'advisory',
104
+ scope: 'changed'
105
+ }
106
+ };
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Normalization
111
+ // ---------------------------------------------------------------------------
112
+
113
+ // Collapse an arbitrary value to a known host, falling back when unrecognized.
114
+ function normalizeHost(host, fallback = DEFAULT_HOST) {
115
+ const safe = String(host || '').trim().toLowerCase();
116
+ return KNOWN_HOSTS.includes(safe) ? safe : fallback;
117
+ }
118
+
119
+ // The top-level `host` setting additionally allows the literal 'auto'.
120
+ function normalizeHostSetting(host) {
121
+ const safe = String(host || 'auto').trim().toLowerCase();
122
+ if (safe === 'auto') return 'auto';
123
+ return KNOWN_HOSTS.includes(safe) ? safe : 'auto';
124
+ }
125
+
126
+ function normalizeEnabled(value, fallback = 'auto') {
127
+ if (value === true || value === false) return value;
128
+ if (String(value).trim().toLowerCase() === 'auto') return 'auto';
129
+ return fallback;
130
+ }
131
+
132
+ function normalizeDispatchMode(mode, fallback = 'native') {
133
+ const safe = String(mode || '').trim().toLowerCase();
134
+ return DISPATCH_MODES.includes(safe) ? safe : fallback;
135
+ }
136
+
137
+ function normalizeModel(model, fallback = DEFAULT_MODEL) {
138
+ return typeof model === 'string' && model.trim() ? model.trim() : fallback;
139
+ }
140
+
141
+ function normalizeDispatchEntry(entry, fallbackEntry) {
142
+ const safe = entry && typeof entry === 'object' ? entry : {};
143
+ return {
144
+ mode: normalizeDispatchMode(safe.mode, fallbackEntry.mode),
145
+ model: normalizeModel(safe.model, fallbackEntry.model)
146
+ };
147
+ }
148
+
149
+ function normalizeTriggers(triggers, fallback) {
150
+ if (!Array.isArray(triggers)) return [...fallback];
151
+ const cleaned = triggers
152
+ .map((t) => String(t || '').trim().toLowerCase())
153
+ .filter((t) => TRIGGERS.includes(t));
154
+ return cleaned.length ? Array.from(new Set(cleaned)) : [...fallback];
155
+ }
156
+
157
+ function normalizeAgent(parsed, agentId) {
158
+ const def = buildDefaultAgent(agentId);
159
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
160
+ const rawDispatch = raw.dispatch && typeof raw.dispatch === 'object' ? raw.dispatch : {};
161
+ const dispatch = {};
162
+ for (const host of KNOWN_HOSTS) {
163
+ dispatch[host] = normalizeDispatchEntry(rawDispatch[host], def.dispatch[host]);
164
+ }
165
+ const agent = {
166
+ enabled: normalizeEnabled(raw.enabled, def.enabled),
167
+ triggers: normalizeTriggers(raw.triggers, def.triggers),
168
+ dispatch,
169
+ report: normalizeModel(raw.report, def.report)
170
+ };
171
+ if (def.cross_check) {
172
+ const rawCc = raw.cross_check && typeof raw.cross_check === 'object' ? raw.cross_check : {};
173
+ agent.cross_check = {
174
+ enabled: typeof rawCc.enabled === 'boolean' ? rawCc.enabled : def.cross_check.enabled,
175
+ mode: normalizeDispatchMode(rawCc.mode, def.cross_check.mode),
176
+ tool: normalizeHost(rawCc.tool, def.cross_check.tool),
177
+ model: normalizeModel(rawCc.model, def.cross_check.model)
178
+ };
179
+ }
180
+ return agent;
181
+ }
182
+
183
+ function normalizeBudget(parsed) {
184
+ const def = buildDefaultVerificationConfig().budget;
185
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
186
+ return {
187
+ max_subagents_per_phase: Number.isInteger(raw.max_subagents_per_phase) && raw.max_subagents_per_phase >= 0
188
+ ? raw.max_subagents_per_phase
189
+ : def.max_subagents_per_phase,
190
+ skip_on_micro: typeof raw.skip_on_micro === 'boolean' ? raw.skip_on_micro : def.skip_on_micro,
191
+ full_smoke: normalizeModel(raw.full_smoke, def.full_smoke)
192
+ };
193
+ }
194
+
195
+ function normalizePhaseLoop(parsed) {
196
+ const def = buildDefaultVerificationConfig().phase_loop;
197
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
198
+ return {
199
+ auto_continue: typeof raw.auto_continue === 'boolean' ? raw.auto_continue : def.auto_continue,
200
+ compact_between_phases: typeof raw.compact_between_phases === 'boolean'
201
+ ? raw.compact_between_phases
202
+ : def.compact_between_phases,
203
+ max_fix_retries_per_phase: Number.isInteger(raw.max_fix_retries_per_phase) && raw.max_fix_retries_per_phase >= 0
204
+ ? raw.max_fix_retries_per_phase
205
+ : def.max_fix_retries_per_phase
206
+ };
207
+ }
208
+
209
+ function normalizeAuditCode(parsed) {
210
+ const def = buildDefaultVerificationConfig().audit_code;
211
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
212
+ const gate = String(raw.tracked_gate || '').trim().toLowerCase();
213
+ const scope = String(raw.scope || '').trim().toLowerCase();
214
+ return {
215
+ tracked_gate: ['block', 'advisory', 'off'].includes(gate) ? gate : def.tracked_gate,
216
+ scope: ['changed', 'full'].includes(scope) ? scope : def.scope
217
+ };
218
+ }
219
+
220
+ function normalizeVerificationConfig(parsed) {
221
+ const def = buildDefaultVerificationConfig();
222
+ const raw = parsed && typeof parsed === 'object' ? parsed : {};
223
+ const rawAgents = raw.agents && typeof raw.agents === 'object' ? raw.agents : {};
224
+ const agents = {};
225
+ // Always materialize the four known agents (defaults fill any gaps)...
226
+ for (const id of VERIFICATION_AGENTS) agents[id] = normalizeAgent(rawAgents[id], id);
227
+ // ...and preserve any user-defined extra agents (best-effort, generic shape).
228
+ for (const id of Object.keys(rawAgents)) {
229
+ if (!VERIFICATION_AGENTS.includes(id)) agents[id] = normalizeAgent(rawAgents[id], id);
230
+ }
231
+ return {
232
+ version: typeof raw.version === 'string' ? raw.version : def.version,
233
+ host: normalizeHostSetting(raw.host),
234
+ agents,
235
+ budget: normalizeBudget(raw.budget),
236
+ phase_loop: normalizePhaseLoop(raw.phase_loop),
237
+ audit_code: normalizeAuditCode(raw.audit_code)
238
+ };
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // Read
243
+ // ---------------------------------------------------------------------------
244
+
245
+ async function readVerificationConfig(targetDir) {
246
+ const configPath = path.join(targetDir, VERIFICATION_CONFIG_RELATIVE_PATH);
247
+ if (!(await exists(configPath))) return buildDefaultVerificationConfig();
248
+ try {
249
+ const rawText = await fs.readFile(configPath, 'utf8');
250
+ return normalizeVerificationConfig(JSON.parse(rawText));
251
+ } catch {
252
+ return buildDefaultVerificationConfig();
253
+ }
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // Resolution / getters
258
+ // ---------------------------------------------------------------------------
259
+
260
+ // Precedence: explicit arg > config.host (when not 'auto') > env > default.
261
+ function resolveHost(config, explicitHost = null, env = process.env) {
262
+ const explicit = String(explicitHost || '').trim().toLowerCase();
263
+ if (KNOWN_HOSTS.includes(explicit)) return explicit;
264
+
265
+ const configured = config && typeof config.host === 'string' ? config.host.trim().toLowerCase() : 'auto';
266
+ if (KNOWN_HOSTS.includes(configured)) return configured;
267
+
268
+ const envTool = String((env && (env.AIOSON_RUNNER_TOOL || env.AIOSON_TOOL)) || '').trim().toLowerCase();
269
+ if (KNOWN_HOSTS.includes(envTool)) return envTool;
270
+
271
+ return DEFAULT_HOST;
272
+ }
273
+
274
+ function getAgentConfig(config, agentId) {
275
+ const id = String(agentId || '').trim();
276
+ if (config && config.agents && typeof config.agents === 'object' && config.agents[id]) {
277
+ return config.agents[id];
278
+ }
279
+ if (VERIFICATION_AGENTS.includes(id)) return buildDefaultAgent(id);
280
+ return null;
281
+ }
282
+
283
+ // Resolve the {mode, model, host} a verification sub-agent should run on for the
284
+ // active host harness. Falls back to the default-host entry, then to built-ins.
285
+ function getAgentDispatch(config, agentId, host = null) {
286
+ const agent = getAgentConfig(config, agentId);
287
+ if (!agent) return null;
288
+ const resolvedHost = resolveHost(config, host);
289
+ const dispatch = agent.dispatch && typeof agent.dispatch === 'object' ? agent.dispatch : {};
290
+ const entry = dispatch[resolvedHost] || dispatch[DEFAULT_HOST];
291
+ if (entry && typeof entry === 'object') {
292
+ return { host: resolvedHost, mode: normalizeDispatchMode(entry.mode), model: normalizeModel(entry.model) };
293
+ }
294
+ const def = buildDefaultAgent(agentId);
295
+ const defEntry = def.dispatch[resolvedHost] || def.dispatch[DEFAULT_HOST];
296
+ return { host: resolvedHost, mode: defEntry.mode, model: defEntry.model };
297
+ }
298
+
299
+ // Resolve enabled:true|false|'auto' to a concrete boolean given run context.
300
+ // 'auto' lets the framework decide: pentester only on a sensitive surface,
301
+ // tester on anything above MICRO, everything else on.
302
+ function resolveAgentEnabled(config, agentId, context = {}) {
303
+ const agent = getAgentConfig(config, agentId);
304
+ if (!agent) return false;
305
+ if (agent.enabled === true) return true;
306
+ if (agent.enabled === false) return false;
307
+ const classification = String(context.classification || '').trim().toUpperCase();
308
+ const sensitiveSurface = Boolean(context.sensitiveSurface);
309
+ switch (String(agentId).trim()) {
310
+ case 'pentester': return sensitiveSurface;
311
+ case 'tester': return classification !== 'MICRO';
312
+ default: return true;
313
+ }
314
+ }
315
+
316
+ function getAgentTriggers(config, agentId) {
317
+ const agent = getAgentConfig(config, agentId);
318
+ return agent && Array.isArray(agent.triggers) ? [...agent.triggers] : [];
319
+ }
320
+
321
+ function agentHasTrigger(config, agentId, trigger) {
322
+ return getAgentTriggers(config, agentId).includes(String(trigger || '').trim().toLowerCase());
323
+ }
324
+
325
+ function getAgentReportTemplate(config, agentId) {
326
+ const agent = getAgentConfig(config, agentId);
327
+ return agent && typeof agent.report === 'string' ? agent.report : '';
328
+ }
329
+
330
+ function resolveAgentReportPath(config, agentId, slug) {
331
+ return getAgentReportTemplate(config, agentId).replace(/\{slug\}/g, String(slug || '').trim());
332
+ }
333
+
334
+ function getCrossCheck(config, agentId) {
335
+ const agent = getAgentConfig(config, agentId);
336
+ if (!agent || !agent.cross_check || typeof agent.cross_check !== 'object') return null;
337
+ const cc = agent.cross_check;
338
+ return {
339
+ enabled: Boolean(cc.enabled),
340
+ mode: normalizeDispatchMode(cc.mode, 'external'),
341
+ tool: normalizeHost(cc.tool, 'codex'),
342
+ model: normalizeModel(cc.model)
343
+ };
344
+ }
345
+
346
+ function getBudget(config) {
347
+ return normalizeBudget(config && config.budget);
348
+ }
349
+
350
+ // Phase-loop behavior: whether @dev auto-continues across phases, compacts
351
+ // between them, and how many in-phase fix retries are allowed before stopping.
352
+ function getPhaseLoop(config) {
353
+ return normalizePhaseLoop(config && config.phase_loop);
354
+ }
355
+
356
+ // audit:code tracked-gate policy: 'block' | 'advisory' | 'off' + 'changed' | 'full'.
357
+ function getAuditCodePolicy(config) {
358
+ return normalizeAuditCode(config && config.audit_code);
359
+ }
360
+
361
+ // Composed decision: should `agentId` run for `context.trigger` right now?
362
+ // Combines trigger membership + enabled resolution + the skip-on-micro budget
363
+ // guard (per-phase verification is suppressed on MICRO to save tokens).
364
+ function shouldRunForTrigger(config, agentId, context = {}) {
365
+ const trigger = String(context.trigger || '').trim().toLowerCase();
366
+ if (!agentHasTrigger(config, agentId, trigger)) return false;
367
+ if (!resolveAgentEnabled(config, agentId, context)) return false;
368
+ const classification = String(context.classification || '').trim().toUpperCase();
369
+ if (getBudget(config).skip_on_micro && classification === 'MICRO' && trigger === 'per-phase') return false;
370
+ return true;
371
+ }
372
+
373
+ module.exports = {
374
+ VERIFICATION_CONFIG_RELATIVE_PATH,
375
+ KNOWN_HOSTS,
376
+ DISPATCH_MODES,
377
+ VERIFICATION_AGENTS,
378
+ TRIGGERS,
379
+ DEFAULT_HOST,
380
+ DEFAULT_MODEL,
381
+ buildDefaultVerificationConfig,
382
+ buildDefaultAgent,
383
+ normalizeVerificationConfig,
384
+ readVerificationConfig,
385
+ resolveHost,
386
+ getAgentConfig,
387
+ getAgentDispatch,
388
+ resolveAgentEnabled,
389
+ getAgentTriggers,
390
+ agentHasTrigger,
391
+ getAgentReportTemplate,
392
+ resolveAgentReportPath,
393
+ getCrossCheck,
394
+ getBudget,
395
+ getPhaseLoop,
396
+ getAuditCodePolicy,
397
+ shouldRunForTrigger
398
+ };