@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,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
|
|
7
|
+
const { validateVerificationReport } = require('../../verification/schema');
|
|
8
|
+
|
|
9
|
+
function isFileSafe(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
return fs.lstatSync(filePath).isFile();
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function listFilesSafe(dir) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readTextSafe(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectReportFiles(featureDirs) {
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const files = [];
|
|
36
|
+
|
|
37
|
+
for (const featureDir of featureDirs || []) {
|
|
38
|
+
const latest = path.join(featureDir, 'verification-report.md');
|
|
39
|
+
if (isFileSafe(latest)) {
|
|
40
|
+
seen.add(latest);
|
|
41
|
+
files.push(latest);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const runsDir = path.join(featureDir, 'verification-runs');
|
|
45
|
+
for (const entry of listFilesSafe(runsDir)) {
|
|
46
|
+
if (!entry.isFile()) continue;
|
|
47
|
+
if (!/-report\.md$/i.test(entry.name)) continue;
|
|
48
|
+
const full = path.join(runsDir, entry.name);
|
|
49
|
+
if (seen.has(full)) continue;
|
|
50
|
+
seen.add(full);
|
|
51
|
+
files.push(full);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
files.sort();
|
|
56
|
+
return files;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractMachineReport(content) {
|
|
60
|
+
const text = String(content || '');
|
|
61
|
+
const heading = /^##\s+Machine Report\s*$/im.exec(text);
|
|
62
|
+
if (!heading) return { ok: false, reason: 'missing_machine_report' };
|
|
63
|
+
const afterHeading = text.slice(heading.index + heading[0].length);
|
|
64
|
+
const block = afterHeading.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
65
|
+
if (!block) return { ok: false, reason: 'missing_machine_report_json' };
|
|
66
|
+
try {
|
|
67
|
+
return { ok: true, report: JSON.parse(block[1]) };
|
|
68
|
+
} catch {
|
|
69
|
+
return { ok: false, reason: 'invalid_machine_report_json' };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stableStringify(value) {
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
76
|
+
}
|
|
77
|
+
if (value && typeof value === 'object') {
|
|
78
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
|
|
79
|
+
}
|
|
80
|
+
return JSON.stringify(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function reportIdentity(report) {
|
|
84
|
+
return crypto.createHash('sha1').update(stableStringify(report)).digest('hex');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isLatestReportPath(filePath) {
|
|
88
|
+
return path.basename(filePath) === 'verification-report.md';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function shouldSkipPromotedDuplicate(seenReports, identity, filePath) {
|
|
92
|
+
const seen = seenReports.get(identity);
|
|
93
|
+
if (!seen) {
|
|
94
|
+
seenReports.set(identity, {
|
|
95
|
+
paths: [filePath],
|
|
96
|
+
skippedPromotedDuplicate: false
|
|
97
|
+
});
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const involvesLatest = isLatestReportPath(filePath) || seen.paths.some(isLatestReportPath);
|
|
102
|
+
seen.paths.push(filePath);
|
|
103
|
+
if (involvesLatest && !seen.skippedPromotedDuplicate) {
|
|
104
|
+
seen.skippedPromotedDuplicate = true;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function timestampFromPath(filePath) {
|
|
111
|
+
const name = path.basename(filePath);
|
|
112
|
+
const match = name.match(/(\d{8}T\d{6}Z)/);
|
|
113
|
+
if (!match) return null;
|
|
114
|
+
return match[1].replace(
|
|
115
|
+
/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/,
|
|
116
|
+
'$1-$2-$3T$4:$5:$6Z'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function severityFromVerification(severity) {
|
|
121
|
+
const value = String(severity || '').toLowerCase();
|
|
122
|
+
if (value === 'blocking') return 'high';
|
|
123
|
+
if (value === 'warning') return 'medium';
|
|
124
|
+
if (value === 'info') return 'info';
|
|
125
|
+
return 'unknown';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function statusFromVerification(status) {
|
|
129
|
+
const value = String(status || '').toUpperCase();
|
|
130
|
+
if (value === 'CONFIRMS' || value === 'NOT_APPLICABLE') return 'fixed';
|
|
131
|
+
if (value === 'DOES_NOT_CONFIRM' || value === 'PARTIAL' || value === 'NOT_VERIFIED') return 'open';
|
|
132
|
+
return 'unknown';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function fileRefFromFinding(finding) {
|
|
136
|
+
if (!finding || !finding.file) return null;
|
|
137
|
+
return finding.line ? `${finding.file}:${finding.line}` : String(finding.file);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function titleFromFinding(report, finding) {
|
|
141
|
+
const parts = [
|
|
142
|
+
finding.kind || 'verification',
|
|
143
|
+
finding.status || report.verdict || 'UNKNOWN'
|
|
144
|
+
];
|
|
145
|
+
if (finding.claim_id) parts.push(`claim ${finding.claim_id}`);
|
|
146
|
+
return parts.join(' ');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isRetroRelevantFinding(finding) {
|
|
150
|
+
const status = String((finding && finding.status) || '').toUpperCase();
|
|
151
|
+
return status === 'DOES_NOT_CONFIRM' || status === 'PARTIAL' || status === 'NOT_VERIFIED';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function reportFindings({ report, slug, sourcePath, sourceDate, makeFinding }) {
|
|
155
|
+
const findings = Array.isArray(report.findings) ? report.findings : [];
|
|
156
|
+
return findings.filter(isRetroRelevantFinding).map((finding, index) => makeFinding({
|
|
157
|
+
source_type: 'verification_report',
|
|
158
|
+
feature_slug: slug,
|
|
159
|
+
finding_id: finding.id ? String(finding.id).slice(0, 80) : `VR-${index + 1}`,
|
|
160
|
+
severity: severityFromVerification(finding.severity),
|
|
161
|
+
title: titleFromFinding(report, finding),
|
|
162
|
+
file_ref: fileRefFromFinding(finding),
|
|
163
|
+
date: sourceDate,
|
|
164
|
+
status: statusFromVerification(finding.status),
|
|
165
|
+
source_path: sourcePath,
|
|
166
|
+
signature: null
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readVerificationReports({ rootDir, slug, locations, makeFinding, relPath }) {
|
|
171
|
+
const warnings = [];
|
|
172
|
+
const findings = [];
|
|
173
|
+
const files = collectReportFiles(locations.featureDirs);
|
|
174
|
+
const countedFiles = [];
|
|
175
|
+
const seenReports = new Map();
|
|
176
|
+
let count = 0;
|
|
177
|
+
|
|
178
|
+
for (const full of files) {
|
|
179
|
+
const sourcePath = relPath(rootDir, full);
|
|
180
|
+
const text = readTextSafe(full);
|
|
181
|
+
if (text === null) {
|
|
182
|
+
warnings.push(`verification_report ilegível: ${sourcePath}`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const parsed = extractMachineReport(text);
|
|
187
|
+
if (!parsed.ok) {
|
|
188
|
+
warnings.push(`verification_report ignorado (${parsed.reason}): ${sourcePath}`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const errors = validateVerificationReport(parsed.report, {
|
|
193
|
+
slug,
|
|
194
|
+
requestedPolicy: parsed.report.policy || 'standard'
|
|
195
|
+
});
|
|
196
|
+
if (errors.length > 0) {
|
|
197
|
+
const compact = errors.map((error) => `${error.field}:${error.reason}`).join(', ');
|
|
198
|
+
warnings.push(`verification_report inválido (${compact}): ${sourcePath}`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const identity = reportIdentity(parsed.report);
|
|
203
|
+
if (shouldSkipPromotedDuplicate(seenReports, identity, full)) continue;
|
|
204
|
+
countedFiles.push(full);
|
|
205
|
+
count += 1;
|
|
206
|
+
findings.push(...reportFindings({
|
|
207
|
+
report: parsed.report,
|
|
208
|
+
slug,
|
|
209
|
+
sourcePath,
|
|
210
|
+
sourceDate: timestampFromPath(full),
|
|
211
|
+
makeFinding
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { findings, warnings, count, files: countedFiles };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
readVerificationReports,
|
|
220
|
+
_internal: {
|
|
221
|
+
collectReportFiles,
|
|
222
|
+
extractMachineReport,
|
|
223
|
+
reportIdentity,
|
|
224
|
+
shouldSkipPromotedDuplicate,
|
|
225
|
+
severityFromVerification,
|
|
226
|
+
statusFromVerification,
|
|
227
|
+
titleFromFinding,
|
|
228
|
+
isRetroRelevantFinding
|
|
229
|
+
}
|
|
230
|
+
};
|
package/src/parser.js
CHANGED
|
@@ -38,6 +38,12 @@ function parseArgv(argv) {
|
|
|
38
38
|
'flatten', 'no-index',
|
|
39
39
|
// harness:validate — pure boolean; `--no-diff .` must not swallow the path.
|
|
40
40
|
'no-diff',
|
|
41
|
+
// audit:code / harness:check — pure booleans; `--changed .` / `--strict .`
|
|
42
|
+
// must not swallow the trailing path positional.
|
|
43
|
+
'changed', 'strict',
|
|
44
|
+
// verify:artifact — pure booleans; `--advisory .` / `--no-build .` must
|
|
45
|
+
// not swallow the path positional.
|
|
46
|
+
'advisory', 'no-build',
|
|
41
47
|
// `--resume` alone means "resume last"; `--resume=<id>` carries a value
|
|
42
48
|
// and is handled by the `=` branch above. Without this entry, `--resume`
|
|
43
49
|
// followed by `--tool=claude` would swallow the next token as its value.
|
package/src/preflight-engine.js
CHANGED
|
@@ -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 — @
|
|
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 — @
|
|
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') {
|
package/src/runtime-store.js
CHANGED
|
@@ -1567,15 +1567,19 @@ function startRun(db, options) {
|
|
|
1567
1567
|
finished_at: status === 'completed' || status === 'failed' ? now : null
|
|
1568
1568
|
});
|
|
1569
1569
|
|
|
1570
|
-
appendRunEvent(db, {
|
|
1571
|
-
runKey,
|
|
1572
|
-
eventType: String(options.eventType || 'start'),
|
|
1573
|
-
phase: options.phase || 'run',
|
|
1574
|
-
status,
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1570
|
+
appendRunEvent(db, {
|
|
1571
|
+
runKey,
|
|
1572
|
+
eventType: String(options.eventType || 'start'),
|
|
1573
|
+
phase: options.phase || 'run',
|
|
1574
|
+
status,
|
|
1575
|
+
toolName: options.toolName,
|
|
1576
|
+
message: String(options.message || options.title || 'Agent started'),
|
|
1577
|
+
payload: options.payload,
|
|
1578
|
+
verdict: options.verdict,
|
|
1579
|
+
tokenCount: options.tokenCount,
|
|
1580
|
+
progressPct: options.progressPct,
|
|
1581
|
+
createdAt: now
|
|
1582
|
+
});
|
|
1579
1583
|
|
|
1580
1584
|
return runKey;
|
|
1581
1585
|
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { execFileSync } = require('node:child_process');
|
|
6
|
+
|
|
7
|
+
const { resolveInsideRoot } = require('./path-policy');
|
|
8
|
+
const { createCounter, redactText, redactJson, totalRedactions } = require('./redaction');
|
|
9
|
+
|
|
10
|
+
const ARTIFACT_PREVIEW_CHARS = 700;
|
|
11
|
+
const ARTIFACT_PREVIEW_TOTAL_CHARS = 8000;
|
|
12
|
+
const ARTIFACT_SUMMARY_LIMIT = 60;
|
|
13
|
+
|
|
14
|
+
function runGit(rootDir, args) {
|
|
15
|
+
try {
|
|
16
|
+
return {
|
|
17
|
+
ok: true,
|
|
18
|
+
output: execFileSync('git', args, {
|
|
19
|
+
cwd: rootDir,
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
22
|
+
}).trim()
|
|
23
|
+
};
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
output: '',
|
|
28
|
+
error: error.message
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fileExists(rootDir, relPath) {
|
|
34
|
+
const safe = resolveInsideRoot(rootDir, relPath);
|
|
35
|
+
if (!safe.ok) return false;
|
|
36
|
+
try {
|
|
37
|
+
const stat = await fs.stat(safe.path);
|
|
38
|
+
return stat.isFile() || stat.isDirectory();
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function fileStat(rootDir, relPath) {
|
|
45
|
+
const safe = resolveInsideRoot(rootDir, relPath);
|
|
46
|
+
if (!safe.ok) return { ok: false, reason: safe.reason };
|
|
47
|
+
try {
|
|
48
|
+
const stat = await fs.stat(safe.path);
|
|
49
|
+
return { ok: true, path: safe.path, stat };
|
|
50
|
+
} catch {
|
|
51
|
+
return { ok: false, reason: 'not_found' };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function evidencePathsFromLedger(ledger) {
|
|
56
|
+
if (!ledger || !Array.isArray(ledger.claims)) return [];
|
|
57
|
+
const paths = [];
|
|
58
|
+
for (const claim of ledger.claims) {
|
|
59
|
+
if (!claim || !Array.isArray(claim.evidence)) continue;
|
|
60
|
+
for (const evidence of claim.evidence) {
|
|
61
|
+
if (evidence && evidence.path) paths.push(evidence.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...new Set(paths)];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function knownChecks(rootDir, slug, ledger, policy = 'standard') {
|
|
68
|
+
const checks = [];
|
|
69
|
+
const addCheck = (candidate) => {
|
|
70
|
+
if (!candidate || !candidate.command) return;
|
|
71
|
+
const existing = checks.find((check) => check.command === candidate.command);
|
|
72
|
+
if (!existing) {
|
|
73
|
+
checks.push(candidate);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
existing.required = Boolean(existing.required || candidate.required);
|
|
77
|
+
existing.source = existing.source === candidate.source
|
|
78
|
+
? existing.source
|
|
79
|
+
: `${existing.source},${candidate.source}`;
|
|
80
|
+
existing.last_status = candidate.last_status || existing.last_status || null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (await fileExists(rootDir, 'package.json')) {
|
|
84
|
+
addCheck({ command: 'npm test', source: 'package.json', required: false });
|
|
85
|
+
}
|
|
86
|
+
if (await fileExists(rootDir, 'scripts/check-js.js')) {
|
|
87
|
+
addCheck({ command: 'node scripts/check-js.js', source: 'scripts/check-js.js', required: false });
|
|
88
|
+
}
|
|
89
|
+
if (await fileExists(rootDir, `.aioson/context/prd-${slug}.md`)) {
|
|
90
|
+
const strictFlag = policy === 'strict' ? ' --strict' : '';
|
|
91
|
+
addCheck({ command: `aioson prototype:check . --feature=${slug}${strictFlag}`, source: 'prototype_contract', required: false });
|
|
92
|
+
}
|
|
93
|
+
if (await fileExists(rootDir, `.aioson/plans/${slug}/harness-contract.json`)) {
|
|
94
|
+
addCheck({ command: `aioson harness:check . --slug=${slug}`, source: 'harness_contract', required: false });
|
|
95
|
+
}
|
|
96
|
+
if (ledger && Array.isArray(ledger.verification_commands)) {
|
|
97
|
+
for (const item of ledger.verification_commands) {
|
|
98
|
+
if (item && item.command) {
|
|
99
|
+
addCheck({
|
|
100
|
+
command: item.command,
|
|
101
|
+
source: 'ledger',
|
|
102
|
+
required: Boolean(item.required),
|
|
103
|
+
last_status: item.last_status || null
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return checks;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function commandPlan(commands) {
|
|
112
|
+
return (commands || []).map((command, index) => ({
|
|
113
|
+
order: index + 1,
|
|
114
|
+
command: command.command,
|
|
115
|
+
required: Boolean(command.required),
|
|
116
|
+
source: command.source || 'unknown',
|
|
117
|
+
last_status: command.last_status || null,
|
|
118
|
+
run_by: 'developer_or_clean_auditor',
|
|
119
|
+
expectation: command.required ? 'must be run or explicitly justified' : 'run when relevant to touched surface'
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function dirtyWorktree(statusOutput) {
|
|
124
|
+
return Boolean(String(statusOutput || '').trim());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function artifactCanPreview(relPath) {
|
|
128
|
+
return /\.(md|txt|json|js|ts|tsx|jsx|html|css|yaml|yml)$/i.test(String(relPath || ''));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function artifactSummaries(rootDir, artifacts, redactionCounter) {
|
|
132
|
+
const summaries = [];
|
|
133
|
+
let usedPreviewChars = 0;
|
|
134
|
+
|
|
135
|
+
for (const artifact of (artifacts || []).slice(0, ARTIFACT_SUMMARY_LIMIT)) {
|
|
136
|
+
const relPath = artifact.path;
|
|
137
|
+
const stat = await fileStat(rootDir, relPath);
|
|
138
|
+
const summary = {
|
|
139
|
+
type: artifact.type,
|
|
140
|
+
role: artifact.role,
|
|
141
|
+
path: relPath,
|
|
142
|
+
exists: stat.ok,
|
|
143
|
+
size_bytes: stat.ok ? stat.stat.size : null
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (!stat.ok) {
|
|
147
|
+
summary.omitted_reason = stat.reason;
|
|
148
|
+
summaries.push(summary);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!stat.stat.isFile()) {
|
|
152
|
+
summary.omitted_reason = 'not_file';
|
|
153
|
+
summaries.push(summary);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!artifactCanPreview(relPath)) {
|
|
157
|
+
summary.omitted_reason = 'unsupported_preview_type';
|
|
158
|
+
summaries.push(summary);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (usedPreviewChars >= ARTIFACT_PREVIEW_TOTAL_CHARS) {
|
|
162
|
+
summary.omitted_reason = 'preview_budget_exhausted';
|
|
163
|
+
summaries.push(summary);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const remaining = ARTIFACT_PREVIEW_TOTAL_CHARS - usedPreviewChars;
|
|
168
|
+
const limit = Math.min(ARTIFACT_PREVIEW_CHARS, remaining);
|
|
169
|
+
const handle = await fs.open(stat.path, 'r');
|
|
170
|
+
try {
|
|
171
|
+
const buffer = Buffer.alloc(limit);
|
|
172
|
+
const { bytesRead } = await handle.read(buffer, 0, limit, 0);
|
|
173
|
+
const preview = buffer.subarray(0, bytesRead).toString('utf8');
|
|
174
|
+
usedPreviewChars += preview.length;
|
|
175
|
+
summary.preview = redactText(preview, redactionCounter);
|
|
176
|
+
summary.preview_truncated = stat.stat.size > bytesRead;
|
|
177
|
+
} finally {
|
|
178
|
+
await handle.close();
|
|
179
|
+
}
|
|
180
|
+
summaries.push(summary);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if ((artifacts || []).length > ARTIFACT_SUMMARY_LIMIT) {
|
|
184
|
+
summaries.push({
|
|
185
|
+
type: 'budget_notice',
|
|
186
|
+
role: 'artifact_summary',
|
|
187
|
+
path: null,
|
|
188
|
+
exists: false,
|
|
189
|
+
omitted_reason: `artifact_summary_limit_${ARTIFACT_SUMMARY_LIMIT}`,
|
|
190
|
+
omitted_count: artifacts.length - ARTIFACT_SUMMARY_LIMIT
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return summaries;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function buildEvidenceBundle(rootDir, slug, ledger, sourceArtifacts, policy) {
|
|
198
|
+
const redactionCounter = createCounter();
|
|
199
|
+
const branch = runGit(rootDir, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
200
|
+
const status = runGit(rootDir, ['status', '--short']);
|
|
201
|
+
const diffStat = runGit(rootDir, ['diff', '--stat']);
|
|
202
|
+
const artifactPaths = (sourceArtifacts || []).map((artifact) => artifact.path);
|
|
203
|
+
const evidencePaths = evidencePathsFromLedger(ledger);
|
|
204
|
+
const touchedPaths = [...new Set([...artifactPaths, ...evidencePaths])].filter(Boolean).slice(0, 25);
|
|
205
|
+
const recentCommits = touchedPaths.length > 0
|
|
206
|
+
? runGit(rootDir, ['log', '--oneline', '-n', '5', '--', ...touchedPaths])
|
|
207
|
+
: { ok: true, output: '' };
|
|
208
|
+
|
|
209
|
+
const verificationCommands = redactJson(await knownChecks(rootDir, slug, ledger, policy), redactionCounter);
|
|
210
|
+
const sanitizedLedgerClaims = redactJson(ledger && Array.isArray(ledger.claims) ? ledger.claims : [], redactionCounter);
|
|
211
|
+
const sanitizedKnownGaps = redactJson(ledger && Array.isArray(ledger.known_gaps) ? ledger.known_gaps : [], redactionCounter);
|
|
212
|
+
const summaries = await artifactSummaries(rootDir, sourceArtifacts || [], redactionCounter);
|
|
213
|
+
const git = {
|
|
214
|
+
branch: branch.ok ? redactText(branch.output, redactionCounter) : null,
|
|
215
|
+
branch_error: branch.ok ? null : branch.error,
|
|
216
|
+
status: status.ok ? redactText(status.output, redactionCounter) : null,
|
|
217
|
+
dirty_worktree: status.ok ? dirtyWorktree(status.output) : null,
|
|
218
|
+
status_error: status.ok ? null : status.error,
|
|
219
|
+
diff_stat: diffStat.ok ? redactText(diffStat.output, redactionCounter) : null,
|
|
220
|
+
diff_stat_error: diffStat.ok ? null : diffStat.error,
|
|
221
|
+
recent_commits: recentCommits.ok ? redactText(recentCommits.output, redactionCounter) : null,
|
|
222
|
+
recent_commits_error: recentCommits.ok ? null : recentCommits.error
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
feature_slug: slug,
|
|
227
|
+
policy,
|
|
228
|
+
git,
|
|
229
|
+
source_artifacts: sourceArtifacts || [],
|
|
230
|
+
artifact_summaries: summaries,
|
|
231
|
+
ledger_claims: sanitizedLedgerClaims,
|
|
232
|
+
known_gaps: sanitizedKnownGaps,
|
|
233
|
+
verification_commands: verificationCommands,
|
|
234
|
+
command_plan: commandPlan(verificationCommands),
|
|
235
|
+
redactions: {
|
|
236
|
+
...redactionCounter,
|
|
237
|
+
total: totalRedactions(redactionCounter)
|
|
238
|
+
},
|
|
239
|
+
preview_budget: {
|
|
240
|
+
artifact_preview_chars: ARTIFACT_PREVIEW_CHARS,
|
|
241
|
+
artifact_preview_total_chars: ARTIFACT_PREVIEW_TOTAL_CHARS,
|
|
242
|
+
artifact_summary_limit: ARTIFACT_SUMMARY_LIMIT
|
|
243
|
+
},
|
|
244
|
+
generated_at: new Date().toISOString(),
|
|
245
|
+
project_root: path.resolve(rootDir)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
buildEvidenceBundle
|
|
251
|
+
};
|