@jaimevalasek/aioson 1.30.2 → 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 +75 -0
- package/README.md +19 -6
- 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/comandos-cli.md +1 -1
- package/docs/pt/5-referencia/harness-retro.md +2 -1
- 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 +82 -42
- 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 +75 -13
- package/src/commands/feature-close.js +43 -18
- package/src/commands/harness-check.js +259 -175
- package/src/commands/harness-retro-promote.js +387 -0
- package/src/commands/live.js +24 -0
- package/src/commands/prototype-check.js +163 -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/verify-implementation.js +428 -0
- package/src/commands/workflow-execute.js +309 -309
- package/src/commands/workflow-next.js +361 -19
- package/src/commands/workflow-plan.js +5 -5
- package/src/constants.js +4 -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/lib/retro/retro-render.js +10 -1
- package/src/lib/retro/retro-sources.js +45 -27
- package/src/lib/retro/verification-reports.js +230 -0
- package/src/parser.js +6 -0
- package/src/preflight-engine.js +12 -12
- package/src/runtime-store.js +13 -9
- package/src/verification/evidence-bundle.js +251 -0
- package/src/verification/ledger-store.js +221 -0
- package/src/verification/path-policy.js +74 -0
- package/src/verification/policy-engine.js +95 -0
- package/src/verification/prompt-package.js +314 -0
- package/src/verification/redaction.js +77 -0
- package/src/verification/report-parser.js +132 -0
- package/src/verification/report-store.js +97 -0
- package/src/verification/result.js +16 -0
- package/src/verification/runners/index.js +319 -0
- package/src/verification/runtime-telemetry.js +144 -0
- package/src/verification/schema.js +276 -0
- package/src/verification/source-discovery.js +153 -0
- package/src/verification-policy.js +398 -0
- package/src/version.js +52 -1
- package/template/.aioson/agents/analyst.md +9 -6
- package/template/.aioson/agents/architect.md +34 -5
- package/template/.aioson/agents/briefing-refiner.md +25 -0
- package/template/.aioson/agents/briefing.md +69 -12
- package/template/.aioson/agents/committer.md +2 -1
- package/template/.aioson/agents/copywriter.md +30 -21
- package/template/.aioson/agents/design-hybrid-forge.md +28 -15
- package/template/.aioson/agents/dev.md +28 -10
- package/template/.aioson/agents/deyvin.md +3 -2
- 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/product.md +15 -11
- 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 +87 -17
- package/template/.aioson/agents/scope-check.md +19 -5
- package/template/.aioson/agents/setup.md +12 -1
- package/template/.aioson/agents/sheldon.md +92 -9
- package/template/.aioson/agents/site-forge.md +11 -2
- package/template/.aioson/agents/squad.md +20 -5
- package/template/.aioson/agents/ux-ui.md +4 -2
- 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/feature-expansion-taxonomy.md +31 -3
- 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 +98 -0
- 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/briefing-expansion-scout/SKILL.md +25 -3
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +5 -3
- package/template/.aioson/skills/process/design-hybrid-forge/references/external-source-ingestion.md +89 -0
- package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +29 -2
- package/template/.aioson/skills/process/prototype-forge/SKILL.md +98 -0
- package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
- package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +23 -2
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
- package/template/CLAUDE.md +15 -11
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SECRET_KEY_RE = /\b([A-Za-z0-9_.-]*(?:token|secret|password|passwd|pwd|api[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|auth)[A-Za-z0-9_.-]*)(\s*[:=]\s*)(["']?)([^"'\s,;}]+)/gi;
|
|
4
|
+
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/g;
|
|
5
|
+
const COMMON_SECRET_RE = /\b(?:sk-[A-Za-z0-9_-]{12,}|sk_(?:live|test)_[A-Za-z0-9_-]{12,}|pk_[A-Za-z0-9_-]{12,}|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16})\b/g;
|
|
6
|
+
const PEM_BLOCK_RE = /-----BEGIN [^-]+-----[\s\S]*?-----END [^-]+-----/g;
|
|
7
|
+
const BASIC_AUTH_URL_RE = /(https?:\/\/)([^/\s:@]+):([^/\s@]+)@/g;
|
|
8
|
+
|
|
9
|
+
function createCounter() {
|
|
10
|
+
return {
|
|
11
|
+
secret_assignments: 0,
|
|
12
|
+
bearer_tokens: 0,
|
|
13
|
+
common_tokens: 0,
|
|
14
|
+
pem_blocks: 0,
|
|
15
|
+
credential_urls: 0
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function redactText(input, counter = createCounter()) {
|
|
20
|
+
let text = String(input || '');
|
|
21
|
+
text = text.replace(PEM_BLOCK_RE, () => {
|
|
22
|
+
counter.pem_blocks += 1;
|
|
23
|
+
return '[REDACTED_PEM_BLOCK]';
|
|
24
|
+
});
|
|
25
|
+
text = text.replace(BASIC_AUTH_URL_RE, (_match, protocol) => {
|
|
26
|
+
counter.credential_urls += 1;
|
|
27
|
+
return `${protocol}[REDACTED_CREDENTIALS]@`;
|
|
28
|
+
});
|
|
29
|
+
text = text.replace(BEARER_RE, () => {
|
|
30
|
+
counter.bearer_tokens += 1;
|
|
31
|
+
return 'Bearer [REDACTED_SECRET]';
|
|
32
|
+
});
|
|
33
|
+
text = text.replace(SECRET_KEY_RE, (_match, key, sep, quote) => {
|
|
34
|
+
counter.secret_assignments += 1;
|
|
35
|
+
return `${key}${sep}${quote}[REDACTED_SECRET]`;
|
|
36
|
+
});
|
|
37
|
+
text = text.replace(COMMON_SECRET_RE, () => {
|
|
38
|
+
counter.common_tokens += 1;
|
|
39
|
+
return '[REDACTED_SECRET]';
|
|
40
|
+
});
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isSecretKey(key) {
|
|
45
|
+
return /(?:token|secret|password|passwd|pwd|api[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|auth)/i.test(String(key || ''));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function redactJson(value, counter = createCounter()) {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value.map((item) => redactJson(item, counter));
|
|
51
|
+
}
|
|
52
|
+
if (value && typeof value === 'object') {
|
|
53
|
+
const output = {};
|
|
54
|
+
for (const [key, item] of Object.entries(value)) {
|
|
55
|
+
if (isSecretKey(key)) {
|
|
56
|
+
counter.secret_assignments += 1;
|
|
57
|
+
output[key] = '[REDACTED_SECRET]';
|
|
58
|
+
} else {
|
|
59
|
+
output[key] = redactJson(item, counter);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return output;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === 'string') return redactText(value, counter);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function totalRedactions(counter) {
|
|
69
|
+
return Object.values(counter || {}).reduce((sum, value) => sum + Number(value || 0), 0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
createCounter,
|
|
74
|
+
redactText,
|
|
75
|
+
redactJson,
|
|
76
|
+
totalRedactions
|
|
77
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
VERDICTS,
|
|
7
|
+
validateVerificationReport,
|
|
8
|
+
makeError
|
|
9
|
+
} = require('./result');
|
|
10
|
+
const { resolveInsideRoot } = require('./path-policy');
|
|
11
|
+
const { extractJsonBlock } = require('./ledger-store');
|
|
12
|
+
|
|
13
|
+
const REQUIRED_REPORT_SECTIONS = [
|
|
14
|
+
{ id: 'verdict', heading: 'Verdict' },
|
|
15
|
+
{ id: 'commands_run', heading: 'Commands Run' },
|
|
16
|
+
{ id: 'findings', heading: 'Findings' },
|
|
17
|
+
{ id: 'before_and_now', heading: 'Before And Now' },
|
|
18
|
+
{ id: 'residual_risk', heading: 'Residual Risk' },
|
|
19
|
+
{ id: 'recommended_route', heading: 'Recommended Route' },
|
|
20
|
+
{ id: 'machine_report', heading: 'Machine Report' }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function sectionPattern(heading) {
|
|
24
|
+
return new RegExp(`^##\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'im');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function missingReportSections(content) {
|
|
28
|
+
return REQUIRED_REPORT_SECTIONS
|
|
29
|
+
.filter((section) => !sectionPattern(section.heading).test(content))
|
|
30
|
+
.map((section) => section.id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractSection(content, heading) {
|
|
34
|
+
const text = String(content || '');
|
|
35
|
+
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, 'im');
|
|
36
|
+
const headingMatch = headingRe.exec(text);
|
|
37
|
+
if (!headingMatch) return '';
|
|
38
|
+
const start = headingMatch.index + headingMatch[0].length;
|
|
39
|
+
const rest = text.slice(start);
|
|
40
|
+
const nextHeading = rest.search(/^##\s+/m);
|
|
41
|
+
return (nextHeading === -1 ? rest : rest.slice(0, nextHeading)).trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function proseVerdictTokens(content) {
|
|
45
|
+
const verdictSection = extractSection(content, 'Verdict');
|
|
46
|
+
if (!verdictSection) return [];
|
|
47
|
+
const pattern = new RegExp(`\\b(${Array.from(VERDICTS).join('|')})\\b`, 'g');
|
|
48
|
+
return [...verdictSection.matchAll(pattern)].map((match) => match[1]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function proseVerdict(content) {
|
|
52
|
+
const tokens = proseVerdictTokens(content);
|
|
53
|
+
return tokens.length > 0 ? tokens[0] : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function parseVerificationReport(rootDir, slug, reportPath, requestedPolicy) {
|
|
57
|
+
const safe = resolveInsideRoot(rootDir, reportPath);
|
|
58
|
+
if (!safe.ok) return safe;
|
|
59
|
+
|
|
60
|
+
let content;
|
|
61
|
+
try {
|
|
62
|
+
content = await fs.readFile(safe.path, 'utf8');
|
|
63
|
+
} catch {
|
|
64
|
+
return makeError('report_not_found', { report_path: safe.relative_path });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const missingSections = missingReportSections(content);
|
|
68
|
+
if (missingSections.length > 0) {
|
|
69
|
+
return makeError('missing_report_sections', {
|
|
70
|
+
feature_slug: slug,
|
|
71
|
+
report_path: safe.relative_path,
|
|
72
|
+
missing_sections: missingSections
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const rawJson = extractJsonBlock(content, 'Machine Report');
|
|
77
|
+
if (!rawJson) {
|
|
78
|
+
return makeError('missing_machine_report', {
|
|
79
|
+
feature_slug: slug,
|
|
80
|
+
report_path: safe.relative_path
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let report;
|
|
85
|
+
try {
|
|
86
|
+
report = JSON.parse(rawJson);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return makeError('invalid_machine_report_json', {
|
|
89
|
+
feature_slug: slug,
|
|
90
|
+
report_path: safe.relative_path,
|
|
91
|
+
detail: error.message
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Conflict only when the Verdict prose names verdict token(s) and NONE of them
|
|
96
|
+
// match the machine verdict. Collecting all tokens (not just the first) avoids a
|
|
97
|
+
// false conflict when the auditor writes a negated mention, e.g.
|
|
98
|
+
// "Not a PASS — NEEDS_DEV_FIX" alongside a machine verdict of NEEDS_DEV_FIX.
|
|
99
|
+
const proseTokens = proseVerdictTokens(content);
|
|
100
|
+
if (proseTokens.length > 0 && report.verdict && !proseTokens.includes(report.verdict)) {
|
|
101
|
+
return makeError('report_conflict', {
|
|
102
|
+
feature_slug: slug,
|
|
103
|
+
report_path: safe.relative_path,
|
|
104
|
+
prose_verdict: proseTokens[0],
|
|
105
|
+
machine_verdict: report.verdict
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const errors = validateVerificationReport(report, { slug, requestedPolicy });
|
|
110
|
+
if (errors.length > 0) {
|
|
111
|
+
return makeError('invalid_machine_report', {
|
|
112
|
+
feature_slug: slug,
|
|
113
|
+
report_path: safe.relative_path,
|
|
114
|
+
errors
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
ok: true,
|
|
120
|
+
feature_slug: slug,
|
|
121
|
+
report_path: safe.relative_path,
|
|
122
|
+
report
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
parseVerificationReport,
|
|
128
|
+
REQUIRED_REPORT_SECTIONS,
|
|
129
|
+
missingReportSections,
|
|
130
|
+
proseVerdict,
|
|
131
|
+
proseVerdictTokens
|
|
132
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { REPORT_SCHEMA_VERSION } = require('./schema');
|
|
7
|
+
const { featureContextDir, verificationRunsDir, relativeFromRoot } = require('./path-policy');
|
|
8
|
+
const { timestampForFile } = require('./prompt-package');
|
|
9
|
+
|
|
10
|
+
function safeSegment(value, fallback = 'default') {
|
|
11
|
+
const segment = String(value || fallback)
|
|
12
|
+
.trim()
|
|
13
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
14
|
+
.replace(/^-+|-+$/g, '')
|
|
15
|
+
.slice(0, 80);
|
|
16
|
+
return segment || fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runnerRunStem({ timestamp = timestampForFile(), tool, model }) {
|
|
20
|
+
return `${timestamp}-${safeSegment(tool, 'tool')}-${safeSegment(model || 'configured-default', 'configured-default')}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function writeVerificationRunFile(rootDir, slug, stem, suffix, content) {
|
|
24
|
+
const runsDir = verificationRunsDir(rootDir, slug);
|
|
25
|
+
await fs.mkdir(runsDir, { recursive: true });
|
|
26
|
+
const targetPath = path.join(runsDir, `${stem}-${suffix}`);
|
|
27
|
+
await fs.writeFile(targetPath, String(content || ''), 'utf8');
|
|
28
|
+
return {
|
|
29
|
+
path: targetPath,
|
|
30
|
+
relative_path: relativeFromRoot(rootDir, targetPath)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function promoteLatestReport(rootDir, slug, markdown) {
|
|
35
|
+
const targetPath = path.join(featureContextDir(rootDir, slug), 'verification-report.md');
|
|
36
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
37
|
+
await fs.writeFile(targetPath, String(markdown || ''), 'utf8');
|
|
38
|
+
return {
|
|
39
|
+
path: targetPath,
|
|
40
|
+
relative_path: relativeFromRoot(rootDir, targetPath)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function systemInconclusiveReport({ slug, policy, summary, route = 'qa', command, status, evidence }) {
|
|
45
|
+
const report = {
|
|
46
|
+
schema_version: REPORT_SCHEMA_VERSION,
|
|
47
|
+
feature_slug: slug,
|
|
48
|
+
policy,
|
|
49
|
+
verdict: 'INCONCLUSIVE',
|
|
50
|
+
summary,
|
|
51
|
+
commands_run: [
|
|
52
|
+
{
|
|
53
|
+
command: command || 'auditor runner',
|
|
54
|
+
status: status || 'failed',
|
|
55
|
+
evidence: evidence || summary
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
findings: [],
|
|
59
|
+
recommended_route: route,
|
|
60
|
+
blocking_findings_count: 0
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return `# Verification Report - ${slug}
|
|
64
|
+
|
|
65
|
+
## Verdict
|
|
66
|
+
INCONCLUSIVE
|
|
67
|
+
|
|
68
|
+
## Commands Run
|
|
69
|
+
${command || 'auditor runner'} — ${status || 'failed'}.
|
|
70
|
+
|
|
71
|
+
## Findings
|
|
72
|
+
No auditor findings were trusted because the runner did not produce a valid report.
|
|
73
|
+
|
|
74
|
+
## Before And Now
|
|
75
|
+
Not evaluated by a valid auditor report.
|
|
76
|
+
|
|
77
|
+
## Residual Risk
|
|
78
|
+
${summary}
|
|
79
|
+
|
|
80
|
+
## Recommended Route
|
|
81
|
+
${route}
|
|
82
|
+
|
|
83
|
+
## Machine Report
|
|
84
|
+
|
|
85
|
+
\`\`\`json
|
|
86
|
+
${JSON.stringify(report, null, 2)}
|
|
87
|
+
\`\`\`
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
safeSegment,
|
|
93
|
+
runnerRunStem,
|
|
94
|
+
writeVerificationRunFile,
|
|
95
|
+
promoteLatestReport,
|
|
96
|
+
systemInconclusiveReport
|
|
97
|
+
};
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_RUNNER_TOOLS = new Set(['codex', 'claude', 'opencode']);
|
|
6
|
+
const DEFAULT_RUNNER_MODEL = 'configured-default';
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
8
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 256 * 1024;
|
|
9
|
+
const DETECTION_TIMEOUT_MS = 5000;
|
|
10
|
+
const MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,119}$/;
|
|
11
|
+
|
|
12
|
+
function normalizeRunnerTool(tool) {
|
|
13
|
+
const value = String(tool || '').trim().toLowerCase();
|
|
14
|
+
if (!value) return { ok: false, reason: 'missing_tool' };
|
|
15
|
+
if (!SUPPORTED_RUNNER_TOOLS.has(value)) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
reason: 'unsupported_tool',
|
|
19
|
+
tool: value,
|
|
20
|
+
supported_tools: Array.from(SUPPORTED_RUNNER_TOOLS)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, tool: value };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeRunnerModel(model) {
|
|
27
|
+
const value = String(model || DEFAULT_RUNNER_MODEL).trim();
|
|
28
|
+
if (!value || value === DEFAULT_RUNNER_MODEL) {
|
|
29
|
+
return { ok: true, model: DEFAULT_RUNNER_MODEL, uses_configured_default: true };
|
|
30
|
+
}
|
|
31
|
+
if (!MODEL_RE.test(value) || value.includes('..')) {
|
|
32
|
+
return { ok: false, reason: 'invalid_model', model: value };
|
|
33
|
+
}
|
|
34
|
+
return { ok: true, model: value, uses_configured_default: false };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function positiveInt(value, fallback, min, max) {
|
|
38
|
+
const parsed = Number(value);
|
|
39
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
40
|
+
const integer = Math.floor(parsed);
|
|
41
|
+
if (integer < min) return min;
|
|
42
|
+
if (integer > max) return max;
|
|
43
|
+
return integer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runnerLimits(options = {}) {
|
|
47
|
+
return {
|
|
48
|
+
timeout_ms: positiveInt(
|
|
49
|
+
options['timeout-ms'] || options.timeoutMs || options.timeout,
|
|
50
|
+
DEFAULT_TIMEOUT_MS,
|
|
51
|
+
100,
|
|
52
|
+
60 * 60 * 1000
|
|
53
|
+
),
|
|
54
|
+
max_output_bytes: positiveInt(
|
|
55
|
+
options['max-output-bytes'] || options.maxOutputBytes || options['max-output'],
|
|
56
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
57
|
+
1024,
|
|
58
|
+
5 * 1024 * 1024
|
|
59
|
+
)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function adapterInvocation({ tool, model, rootDir, promptPath, promptText }) {
|
|
64
|
+
const usesDefault = model === DEFAULT_RUNNER_MODEL;
|
|
65
|
+
if (tool === 'codex') {
|
|
66
|
+
const args = [
|
|
67
|
+
'exec',
|
|
68
|
+
'--cd', rootDir,
|
|
69
|
+
'--sandbox', 'read-only',
|
|
70
|
+
'--ephemeral',
|
|
71
|
+
'--color', 'never'
|
|
72
|
+
];
|
|
73
|
+
if (!usesDefault) args.push('--model', model);
|
|
74
|
+
args.push('-');
|
|
75
|
+
return {
|
|
76
|
+
command: 'codex',
|
|
77
|
+
args,
|
|
78
|
+
stdin: promptText,
|
|
79
|
+
permission_mode: 'read-only',
|
|
80
|
+
destructive_commands_allowed: false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (tool === 'claude') {
|
|
85
|
+
const args = [
|
|
86
|
+
'--print',
|
|
87
|
+
'--permission-mode', 'plan',
|
|
88
|
+
'--tools', ''
|
|
89
|
+
];
|
|
90
|
+
if (!usesDefault) args.push('--model', model);
|
|
91
|
+
return {
|
|
92
|
+
command: 'claude',
|
|
93
|
+
args,
|
|
94
|
+
stdin: promptText,
|
|
95
|
+
permission_mode: 'plan',
|
|
96
|
+
destructive_commands_allowed: false
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const args = [
|
|
101
|
+
'run',
|
|
102
|
+
'--dir', rootDir,
|
|
103
|
+
'--pure',
|
|
104
|
+
'--file', promptPath
|
|
105
|
+
];
|
|
106
|
+
if (!usesDefault) args.push('--model', model);
|
|
107
|
+
args.push('Run the implementation audit described in the attached prompt package. Return only the requested verification report with the Machine Report JSON block.');
|
|
108
|
+
return {
|
|
109
|
+
command: 'opencode',
|
|
110
|
+
args,
|
|
111
|
+
stdin: null,
|
|
112
|
+
permission_mode: 'default',
|
|
113
|
+
destructive_commands_allowed: false
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function commandLabel(invocation) {
|
|
118
|
+
return [invocation.command, ...invocation.args].join(' ');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function runProcessWithLimits(invocation, { cwd, timeoutMs, maxOutputBytes, spawnImpl = spawn }) {
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
const startedAt = Date.now();
|
|
124
|
+
let settled = false;
|
|
125
|
+
let timedOut = false;
|
|
126
|
+
let outputLimited = false;
|
|
127
|
+
let stdout = '';
|
|
128
|
+
let stderr = '';
|
|
129
|
+
let outputBytes = 0;
|
|
130
|
+
let timer = null;
|
|
131
|
+
|
|
132
|
+
function finish(result) {
|
|
133
|
+
if (settled) return;
|
|
134
|
+
settled = true;
|
|
135
|
+
if (timer) clearTimeout(timer);
|
|
136
|
+
resolve({
|
|
137
|
+
...result,
|
|
138
|
+
duration_ms: Date.now() - startedAt,
|
|
139
|
+
stdout,
|
|
140
|
+
stderr,
|
|
141
|
+
output_bytes: outputBytes,
|
|
142
|
+
output_truncated: outputLimited
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let child;
|
|
147
|
+
try {
|
|
148
|
+
child = spawnImpl(invocation.command, invocation.args, {
|
|
149
|
+
cwd,
|
|
150
|
+
windowsHide: true,
|
|
151
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
152
|
+
env: {
|
|
153
|
+
...process.env,
|
|
154
|
+
NO_COLOR: '1',
|
|
155
|
+
CI: process.env.CI || '1'
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
finish({
|
|
160
|
+
status: 'spawn_error',
|
|
161
|
+
exit_code: null,
|
|
162
|
+
signal: null,
|
|
163
|
+
error: error.message
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
timer = setTimeout(() => {
|
|
169
|
+
timedOut = true;
|
|
170
|
+
if (child && child.kill) child.kill('SIGTERM');
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
if (!settled && child && child.kill) child.kill('SIGKILL');
|
|
173
|
+
}, 1000).unref?.();
|
|
174
|
+
}, timeoutMs);
|
|
175
|
+
|
|
176
|
+
function capture(kind, chunk) {
|
|
177
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk || '');
|
|
178
|
+
outputBytes += Buffer.byteLength(text, 'utf8');
|
|
179
|
+
if (outputBytes <= maxOutputBytes) {
|
|
180
|
+
if (kind === 'stdout') stdout += text;
|
|
181
|
+
else stderr += text;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
outputLimited = true;
|
|
185
|
+
const remaining = Math.max(0, maxOutputBytes - Buffer.byteLength(stdout + stderr, 'utf8'));
|
|
186
|
+
if (remaining > 0) {
|
|
187
|
+
const slice = Buffer.from(text, 'utf8').subarray(0, remaining).toString('utf8');
|
|
188
|
+
if (kind === 'stdout') stdout += slice;
|
|
189
|
+
else stderr += slice;
|
|
190
|
+
}
|
|
191
|
+
if (child && child.kill) child.kill('SIGTERM');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (child.stdout && child.stdout.on) child.stdout.on('data', (chunk) => capture('stdout', chunk));
|
|
195
|
+
if (child.stderr && child.stderr.on) child.stderr.on('data', (chunk) => capture('stderr', chunk));
|
|
196
|
+
if (child.on) {
|
|
197
|
+
child.on('error', (error) => {
|
|
198
|
+
finish({
|
|
199
|
+
status: 'spawn_error',
|
|
200
|
+
exit_code: null,
|
|
201
|
+
signal: null,
|
|
202
|
+
error: error.message
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
child.on('close', (code, signal) => {
|
|
206
|
+
if (timedOut) {
|
|
207
|
+
finish({ status: 'timeout', exit_code: code, signal });
|
|
208
|
+
} else if (outputLimited) {
|
|
209
|
+
finish({ status: 'output_limit', exit_code: code, signal });
|
|
210
|
+
} else {
|
|
211
|
+
finish({
|
|
212
|
+
status: code === 0 ? 'completed' : 'failed',
|
|
213
|
+
exit_code: code,
|
|
214
|
+
signal
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (child.stdin) {
|
|
221
|
+
if (invocation.stdin) child.stdin.write(invocation.stdin);
|
|
222
|
+
child.stdin.end();
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function detectRunnerTool(tool, { rootDir, spawnImpl = spawn }) {
|
|
228
|
+
const invocation = {
|
|
229
|
+
command: tool,
|
|
230
|
+
args: ['--version'],
|
|
231
|
+
stdin: null
|
|
232
|
+
};
|
|
233
|
+
const result = await runProcessWithLimits(invocation, {
|
|
234
|
+
cwd: rootDir,
|
|
235
|
+
timeoutMs: DETECTION_TIMEOUT_MS,
|
|
236
|
+
maxOutputBytes: 16 * 1024,
|
|
237
|
+
spawnImpl
|
|
238
|
+
});
|
|
239
|
+
if (result.status !== 'completed') {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
reason: result.status === 'spawn_error' ? 'tool_not_found' : `tool_detection_${result.status}`,
|
|
243
|
+
tool,
|
|
244
|
+
detail: result.error || result.stderr || result.stdout || null
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ok: true,
|
|
249
|
+
tool,
|
|
250
|
+
version_output: (result.stdout || result.stderr || '').trim().slice(0, 500)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function runAuditorTool({
|
|
255
|
+
rootDir,
|
|
256
|
+
tool,
|
|
257
|
+
model,
|
|
258
|
+
promptPath,
|
|
259
|
+
promptText,
|
|
260
|
+
limits = {},
|
|
261
|
+
spawnImpl = spawn
|
|
262
|
+
}) {
|
|
263
|
+
const toolResult = normalizeRunnerTool(tool);
|
|
264
|
+
if (!toolResult.ok) return toolResult;
|
|
265
|
+
const modelResult = normalizeRunnerModel(model);
|
|
266
|
+
if (!modelResult.ok) return modelResult;
|
|
267
|
+
|
|
268
|
+
const detected = await detectRunnerTool(toolResult.tool, { rootDir, spawnImpl });
|
|
269
|
+
if (!detected.ok) return detected;
|
|
270
|
+
|
|
271
|
+
const invocation = adapterInvocation({
|
|
272
|
+
tool: toolResult.tool,
|
|
273
|
+
model: modelResult.model,
|
|
274
|
+
rootDir,
|
|
275
|
+
promptPath,
|
|
276
|
+
promptText
|
|
277
|
+
});
|
|
278
|
+
const run = await runProcessWithLimits(invocation, {
|
|
279
|
+
cwd: rootDir,
|
|
280
|
+
timeoutMs: limits.timeout_ms || DEFAULT_TIMEOUT_MS,
|
|
281
|
+
maxOutputBytes: limits.max_output_bytes || DEFAULT_MAX_OUTPUT_BYTES,
|
|
282
|
+
spawnImpl
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
ok: run.status === 'completed',
|
|
287
|
+
status: run.status,
|
|
288
|
+
tool: toolResult.tool,
|
|
289
|
+
model: modelResult.model,
|
|
290
|
+
command: commandLabel(invocation),
|
|
291
|
+
permission_mode: invocation.permission_mode,
|
|
292
|
+
destructive_commands_allowed: invocation.destructive_commands_allowed,
|
|
293
|
+
timeout_ms: limits.timeout_ms || DEFAULT_TIMEOUT_MS,
|
|
294
|
+
max_output_bytes: limits.max_output_bytes || DEFAULT_MAX_OUTPUT_BYTES,
|
|
295
|
+
duration_ms: run.duration_ms,
|
|
296
|
+
exit_code: run.exit_code,
|
|
297
|
+
signal: run.signal,
|
|
298
|
+
stdout: run.stdout,
|
|
299
|
+
stderr: run.stderr,
|
|
300
|
+
output_bytes: run.output_bytes,
|
|
301
|
+
output_truncated: run.output_truncated,
|
|
302
|
+
error: run.error || null,
|
|
303
|
+
detected
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
SUPPORTED_RUNNER_TOOLS,
|
|
309
|
+
DEFAULT_RUNNER_MODEL,
|
|
310
|
+
DEFAULT_TIMEOUT_MS,
|
|
311
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
312
|
+
normalizeRunnerTool,
|
|
313
|
+
normalizeRunnerModel,
|
|
314
|
+
runnerLimits,
|
|
315
|
+
adapterInvocation,
|
|
316
|
+
runProcessWithLimits,
|
|
317
|
+
detectRunnerTool,
|
|
318
|
+
runAuditorTool
|
|
319
|
+
};
|