@jaimevalasek/aioson 1.33.1 → 1.36.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 +48 -0
- package/docs/en/1-understand/ecosystem-map.md +45 -29
- package/docs/en/1-understand/glossary.md +5 -5
- package/docs/en/1-understand/what-is-aioson.md +5 -5
- package/docs/en/2-start/existing-project.md +7 -7
- package/docs/en/2-start/first-project.md +32 -38
- package/docs/en/2-start/initial-decisions.md +18 -17
- package/docs/en/3-recipes/README.md +2 -2
- package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
- package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
- package/docs/en/4-agents/README.md +28 -14
- package/docs/en/4-agents/discovery-design-doc.md +20 -8
- package/docs/en/4-agents/forge-run.md +3 -2
- package/docs/en/5-reference/cli-reference.md +51 -48
- package/docs/en/5-reference/executable-verification.md +10 -7
- package/docs/en/5-reference/parallel.md +2 -0
- package/docs/en/5-reference/qa-browser.md +2 -2
- package/docs/en/README.md +3 -3
- package/docs/pt/1-entender/glossario.md +4 -4
- package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
- package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
- package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
- package/docs/pt/3-receitas/README.md +1 -1
- package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
- package/docs/pt/4-agentes/analyst.md +9 -5
- package/docs/pt/4-agentes/architect.md +9 -5
- package/docs/pt/4-agentes/dev.md +19 -7
- package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
- package/docs/pt/4-agentes/orchestrator.md +164 -118
- package/docs/pt/4-agentes/pentester.md +5 -5
- package/docs/pt/4-agentes/pm.md +15 -7
- package/docs/pt/4-agentes/sheldon.md +166 -99
- package/docs/pt/4-agentes/ux-ui.md +7 -4
- package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
- package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
- package/docs/pt/5-referencia/sdd-framework.md +53 -16
- package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
- package/docs/pt/README.md +4 -4
- package/docs/pt/agentes.md +48 -50
- package/package.json +2 -2
- package/src/artifact-kinds.js +110 -0
- package/src/cli.js +103 -80
- package/src/commands/agent-epilogue.js +251 -186
- package/src/commands/agents.js +104 -48
- package/src/commands/audit-code.js +344 -0
- package/src/commands/classify.js +389 -389
- package/src/commands/feature-close.js +43 -18
- package/src/commands/harness-check.js +259 -175
- package/src/commands/live.js +24 -0
- package/src/commands/review-feature.js +189 -0
- package/src/commands/runtime.js +81 -66
- package/src/commands/sync-agents-copy.js +115 -0
- package/src/commands/verification-plan.js +116 -0
- package/src/commands/verify-artifact.js +530 -0
- package/src/commands/workflow-execute.js +309 -309
- package/src/commands/workflow-next.js +139 -10
- package/src/commands/workflow-plan.js +5 -5
- package/src/constants.js +2 -0
- package/src/gateway-pointer-merge.js +7 -1
- package/src/handoff-contract.js +267 -172
- package/src/harness/contract-integrity-gate.js +172 -0
- package/src/harness/contract-integrity.js +111 -0
- package/src/harness/contract-schema.js +377 -332
- package/src/harness/detect-runtime-feature.js +90 -0
- package/src/harness/static-criteria.js +192 -0
- package/src/i18n/messages/en.js +8 -4
- package/src/i18n/messages/es.js +8 -4
- package/src/i18n/messages/fr.js +8 -4
- package/src/i18n/messages/pt-BR.js +8 -4
- package/src/install-wizard.js +8 -8
- package/src/installer.js +13 -6
- package/src/parser.js +6 -0
- package/src/preflight-engine.js +12 -12
- package/src/verification/policy-engine.js +95 -95
- package/src/verification-policy.js +398 -0
- package/src/version.js +52 -1
- package/template/.aioson/agents/architect.md +34 -5
- package/template/.aioson/agents/briefing-refiner.md +4 -1
- package/template/.aioson/agents/committer.md +2 -1
- package/template/.aioson/agents/copywriter.md +30 -21
- package/template/.aioson/agents/design-hybrid-forge.md +10 -1
- package/template/.aioson/agents/dev.md +27 -22
- package/template/.aioson/agents/discover.md +12 -3
- package/template/.aioson/agents/discovery-design-doc.md +7 -2
- package/template/.aioson/agents/genome.md +19 -10
- package/template/.aioson/agents/neo.md +30 -30
- package/template/.aioson/agents/orache.md +20 -11
- package/template/.aioson/agents/orchestrator.md +84 -7
- package/template/.aioson/agents/pm.md +8 -8
- package/template/.aioson/agents/profiler-enricher.md +20 -11
- package/template/.aioson/agents/profiler-forge.md +20 -11
- package/template/.aioson/agents/profiler-researcher.md +20 -11
- package/template/.aioson/agents/qa.md +91 -30
- package/template/.aioson/agents/setup.md +12 -1
- package/template/.aioson/agents/sheldon.md +94 -12
- package/template/.aioson/agents/site-forge.md +11 -2
- package/template/.aioson/agents/squad.md +20 -5
- package/template/.aioson/agents/ux-ui.md +1 -0
- package/template/.aioson/agents/validator.md +33 -1
- package/template/.aioson/config/verification.json +61 -0
- package/template/.aioson/config.md +13 -9
- package/template/.aioson/docs/LAYERS.md +2 -2
- package/template/.aioson/docs/autopilot-handoff.md +10 -10
- package/template/.aioson/docs/dev/execution-discipline.md +41 -0
- package/template/.aioson/docs/dev/phase-loop.md +47 -0
- package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
- package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
- package/template/.aioson/docs/product/prd-contract.md +12 -12
- package/template/.aioson/docs/prototype-contract.md +21 -4
- package/template/.aioson/docs/reference-identity.md +94 -0
- package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
- package/template/.aioson/docs/verification-config.md +82 -0
- package/template/.aioson/docs/verify-artifact-gates.md +91 -0
- package/template/.aioson/docs/workflow-lean-lane.md +129 -0
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
- package/template/.aioson/skills/process/prototype-forge/SKILL.md +6 -0
- package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
- package/template/CLAUDE.md +15 -11
|
@@ -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
|
+
};
|
package/src/version.js
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const fsPromises = require('node:fs/promises');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
+
const { execFileSync } = require('node:child_process');
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
9
|
+
const PACKAGE_JSON_PATH = path.join(ROOT_DIR, 'package.json');
|
|
8
10
|
const DEFAULT_VERSION = '0.0.0';
|
|
9
11
|
|
|
10
12
|
let cachedVersion = null;
|
|
13
|
+
let cachedGitInfo; // undefined = not computed yet; null = no git build info
|
|
11
14
|
|
|
12
15
|
function parseVersionFromPackageJson(text) {
|
|
13
16
|
try {
|
|
@@ -43,8 +46,56 @@ async function getCliVersion() {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
function runGitSync(args) {
|
|
50
|
+
return execFileSync('git', ['-C', ROOT_DIR, ...args], {
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
53
|
+
}).trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Short git build info for the package's OWN checkout (e.g. an `npm link`ed dev
|
|
58
|
+
* install). Lets `aioson --version` and the per-project install stamp report the
|
|
59
|
+
* exact commit a linked framework is running, so you don't need a semver bump
|
|
60
|
+
* per commit to know what's installed. Returns null when the package is not a
|
|
61
|
+
* git checkout (a normal npm install) or git is unavailable. Best-effort.
|
|
62
|
+
*/
|
|
63
|
+
function getGitBuildInfoSync() {
|
|
64
|
+
if (cachedGitInfo !== undefined) return cachedGitInfo;
|
|
65
|
+
try {
|
|
66
|
+
const sha = runGitSync(['rev-parse', '--short', 'HEAD']);
|
|
67
|
+
if (!sha) {
|
|
68
|
+
cachedGitInfo = null;
|
|
69
|
+
return cachedGitInfo;
|
|
70
|
+
}
|
|
71
|
+
let date = null;
|
|
72
|
+
try {
|
|
73
|
+
date = runGitSync(['show', '-s', '--format=%cs', 'HEAD']) || null;
|
|
74
|
+
} catch {
|
|
75
|
+
/* date is optional */
|
|
76
|
+
}
|
|
77
|
+
cachedGitInfo = { sha, date };
|
|
78
|
+
} catch {
|
|
79
|
+
cachedGitInfo = null;
|
|
80
|
+
}
|
|
81
|
+
return cachedGitInfo;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Display label: the semver, plus `(sha, date)` when running from a git checkout.
|
|
86
|
+
* e.g. "1.35.0 (<sha>, <date>)" linked, or "1.35.0" from an npm install.
|
|
87
|
+
*/
|
|
88
|
+
function getCliVersionLabelSync() {
|
|
89
|
+
const version = getCliVersionSync();
|
|
90
|
+
const git = getGitBuildInfoSync();
|
|
91
|
+
if (!git) return version;
|
|
92
|
+
return git.date ? `${version} (${git.sha}, ${git.date})` : `${version} (${git.sha})`;
|
|
93
|
+
}
|
|
94
|
+
|
|
46
95
|
module.exports = {
|
|
47
96
|
getCliVersion,
|
|
48
97
|
getCliVersionSync,
|
|
98
|
+
getGitBuildInfoSync,
|
|
99
|
+
getCliVersionLabelSync,
|
|
49
100
|
parseVersionFromPackageJson
|
|
50
101
|
};
|