@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,221 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
LEDGER_SCHEMA_VERSION,
|
|
8
|
+
validateImplementationLedger,
|
|
9
|
+
missingLedgerEvidence,
|
|
10
|
+
makeError
|
|
11
|
+
} = require('./result');
|
|
12
|
+
const {
|
|
13
|
+
featureContextDir,
|
|
14
|
+
verificationRunsDir,
|
|
15
|
+
resolveInsideRoot,
|
|
16
|
+
relativeFromRoot
|
|
17
|
+
} = require('./path-policy');
|
|
18
|
+
|
|
19
|
+
const REQUIRED_LEDGER_SECTIONS = [
|
|
20
|
+
{ id: 'source_of_truth', heading: 'Source Of Truth' },
|
|
21
|
+
{ id: 'intended_behavior_claims', heading: 'Intended Behavior Claims' },
|
|
22
|
+
{ id: 'implementation_evidence', heading: 'Implementation Evidence' },
|
|
23
|
+
{ id: 'verification_commands', heading: 'Verification Commands' },
|
|
24
|
+
{ id: 'known_gaps', heading: 'Known Gaps' },
|
|
25
|
+
{ id: 'handoff_notes', heading: 'Handoff Notes' },
|
|
26
|
+
{ id: 'machine_ledger', heading: 'Machine Ledger' }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
async function exists(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
await fs.access(filePath);
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sectionPattern(heading) {
|
|
39
|
+
return new RegExp(`^##\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'im');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findMissingSections(content) {
|
|
43
|
+
return REQUIRED_LEDGER_SECTIONS
|
|
44
|
+
.filter((section) => !sectionPattern(section.heading).test(content))
|
|
45
|
+
.map((section) => section.id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractJsonBlock(content, heading) {
|
|
49
|
+
const text = String(content || '');
|
|
50
|
+
const headingRe = new RegExp(`^##\\s+${heading}\\s*$`, 'im');
|
|
51
|
+
const headingMatch = headingRe.exec(text);
|
|
52
|
+
let scope = text;
|
|
53
|
+
if (headingMatch) {
|
|
54
|
+
const start = headingMatch.index + headingMatch[0].length;
|
|
55
|
+
const rest = text.slice(start);
|
|
56
|
+
const nextHeading = rest.search(/^##\s+/m);
|
|
57
|
+
scope = nextHeading === -1 ? rest : rest.slice(0, nextHeading);
|
|
58
|
+
}
|
|
59
|
+
const block = scope.match(/```json\s*([\s\S]*?)```/i);
|
|
60
|
+
return block ? block[1].trim() : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createLedgerTemplate(slug, sourceArtifacts = []) {
|
|
64
|
+
const machine = {
|
|
65
|
+
schema_version: LEDGER_SCHEMA_VERSION,
|
|
66
|
+
feature_slug: slug,
|
|
67
|
+
source_artifacts: sourceArtifacts.map((artifact) => ({
|
|
68
|
+
type: artifact.type,
|
|
69
|
+
path: artifact.path,
|
|
70
|
+
role: artifact.role || 'source'
|
|
71
|
+
})),
|
|
72
|
+
claims: [],
|
|
73
|
+
known_gaps: [],
|
|
74
|
+
verification_commands: []
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return `# Implementation Ledger - ${slug}
|
|
78
|
+
|
|
79
|
+
## Source Of Truth
|
|
80
|
+
|
|
81
|
+
List the PRD, requirements, spec, plan, prototype, harness, or source plan that defines this implementation.
|
|
82
|
+
|
|
83
|
+
## Intended Behavior Claims
|
|
84
|
+
|
|
85
|
+
List every behavior the implementation claims to satisfy. Use stable claim IDs such as \`CLAIM-001\`.
|
|
86
|
+
|
|
87
|
+
## Implementation Evidence
|
|
88
|
+
|
|
89
|
+
Link each claim to files, line ranges, tests, commands, or explicit N/A rationale.
|
|
90
|
+
|
|
91
|
+
## Verification Commands
|
|
92
|
+
|
|
93
|
+
Record commands that should be run by the developer or clean auditor. Include last known status only when actually run.
|
|
94
|
+
|
|
95
|
+
## Known Gaps
|
|
96
|
+
|
|
97
|
+
Record deferred, blocked, or not-applicable items with an owner and rationale.
|
|
98
|
+
|
|
99
|
+
## Handoff Notes
|
|
100
|
+
|
|
101
|
+
Notes for @scope-check, @qa, or a clean auditor. This ledger is evidence, not proof.
|
|
102
|
+
|
|
103
|
+
## Machine Ledger
|
|
104
|
+
|
|
105
|
+
\`\`\`json
|
|
106
|
+
${JSON.stringify(machine, null, 2)}
|
|
107
|
+
\`\`\`
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function prepareLedger(rootDir, slug, sourceArtifacts = []) {
|
|
112
|
+
const featureDir = featureContextDir(rootDir, slug);
|
|
113
|
+
const runsDir = verificationRunsDir(rootDir, slug);
|
|
114
|
+
const ledgerPath = path.join(featureDir, 'implementation-ledger.md');
|
|
115
|
+
|
|
116
|
+
await fs.mkdir(runsDir, { recursive: true });
|
|
117
|
+
const alreadyExists = await exists(ledgerPath);
|
|
118
|
+
if (!alreadyExists) {
|
|
119
|
+
await fs.writeFile(ledgerPath, createLedgerTemplate(slug, sourceArtifacts), 'utf8');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
feature_slug: slug,
|
|
125
|
+
ledger_path: relativeFromRoot(rootDir, ledgerPath),
|
|
126
|
+
created: !alreadyExists,
|
|
127
|
+
source_artifacts_found: sourceArtifacts.length > 0,
|
|
128
|
+
warnings: sourceArtifacts.length > 0 ? [] : ['source_artifacts_not_found']
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function readLedger(rootDir, slug) {
|
|
133
|
+
const ledgerPath = path.join(featureContextDir(rootDir, slug), 'implementation-ledger.md');
|
|
134
|
+
const resolved = resolveInsideRoot(rootDir, ledgerPath);
|
|
135
|
+
if (!resolved.ok) return resolved;
|
|
136
|
+
try {
|
|
137
|
+
const content = await fs.readFile(resolved.path, 'utf8');
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
path: resolved.path,
|
|
141
|
+
relative_path: resolved.relative_path,
|
|
142
|
+
content
|
|
143
|
+
};
|
|
144
|
+
} catch {
|
|
145
|
+
return makeError('ledger_not_found', {
|
|
146
|
+
feature_slug: slug,
|
|
147
|
+
ledger_path: resolved.relative_path
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function validateLedgerContent(content, rootDir, slug) {
|
|
153
|
+
const missingSections = findMissingSections(content);
|
|
154
|
+
if (missingSections.length > 0) {
|
|
155
|
+
return makeError('missing_ledger_sections', {
|
|
156
|
+
feature_slug: slug,
|
|
157
|
+
missing_sections: missingSections,
|
|
158
|
+
ready_for_prompt: false
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const rawJson = extractJsonBlock(content, 'Machine Ledger');
|
|
163
|
+
if (!rawJson) {
|
|
164
|
+
return makeError('missing_machine_ledger', {
|
|
165
|
+
feature_slug: slug,
|
|
166
|
+
ready_for_prompt: false
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let ledger;
|
|
171
|
+
try {
|
|
172
|
+
ledger = JSON.parse(rawJson);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return makeError('invalid_machine_ledger_json', {
|
|
175
|
+
feature_slug: slug,
|
|
176
|
+
detail: error.message,
|
|
177
|
+
ready_for_prompt: false
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const errors = validateImplementationLedger(ledger, { rootDir, slug });
|
|
182
|
+
const claims = Array.isArray(ledger.claims) ? ledger.claims : [];
|
|
183
|
+
const missingEvidence = missingLedgerEvidence(ledger);
|
|
184
|
+
|
|
185
|
+
if (errors.length > 0) {
|
|
186
|
+
return makeError('invalid_machine_ledger', {
|
|
187
|
+
feature_slug: slug,
|
|
188
|
+
errors,
|
|
189
|
+
ready_for_prompt: false
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
feature_slug: slug,
|
|
196
|
+
ledger,
|
|
197
|
+
missing_sections: [],
|
|
198
|
+
missing_evidence: missingEvidence,
|
|
199
|
+
ready_for_prompt: claims.length > 0 && missingEvidence.length === 0
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function checkLedger(rootDir, slug) {
|
|
204
|
+
const loaded = await readLedger(rootDir, slug);
|
|
205
|
+
if (!loaded.ok) return loaded;
|
|
206
|
+
const validation = validateLedgerContent(loaded.content, rootDir, slug);
|
|
207
|
+
return {
|
|
208
|
+
...validation,
|
|
209
|
+
ledger_path: loaded.relative_path
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
REQUIRED_LEDGER_SECTIONS,
|
|
215
|
+
createLedgerTemplate,
|
|
216
|
+
prepareLedger,
|
|
217
|
+
readLedger,
|
|
218
|
+
checkLedger,
|
|
219
|
+
validateLedgerContent,
|
|
220
|
+
extractJsonBlock
|
|
221
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6
|
+
|
|
7
|
+
function toPosixPath(value) {
|
|
8
|
+
return String(value || '').replace(/\\/g, '/');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function validateFeatureSlug(slug) {
|
|
12
|
+
const value = String(slug || '').trim();
|
|
13
|
+
if (!value) {
|
|
14
|
+
return { ok: false, reason: 'missing_feature' };
|
|
15
|
+
}
|
|
16
|
+
if (!SLUG_RE.test(value)) {
|
|
17
|
+
return { ok: false, reason: 'invalid_feature_slug', feature_slug: value };
|
|
18
|
+
}
|
|
19
|
+
return { ok: true, feature_slug: value };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isInsideRoot(rootDir, candidatePath) {
|
|
23
|
+
const root = path.resolve(rootDir);
|
|
24
|
+
const candidate = path.resolve(candidatePath);
|
|
25
|
+
const relative = path.relative(root, candidate);
|
|
26
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveProjectRoot(cwd, targetArg) {
|
|
30
|
+
return path.resolve(cwd || process.cwd(), targetArg || '.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveInsideRoot(rootDir, inputPath) {
|
|
34
|
+
if (!inputPath) {
|
|
35
|
+
return { ok: false, reason: 'missing_path' };
|
|
36
|
+
}
|
|
37
|
+
const root = path.resolve(rootDir);
|
|
38
|
+
const resolved = path.resolve(root, String(inputPath));
|
|
39
|
+
if (!isInsideRoot(root, resolved)) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
reason: 'path_outside_root',
|
|
43
|
+
path: String(inputPath)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
path: resolved,
|
|
49
|
+
relative_path: toPosixPath(path.relative(root, resolved))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function relativeFromRoot(rootDir, absolutePath) {
|
|
54
|
+
return toPosixPath(path.relative(path.resolve(rootDir), path.resolve(absolutePath)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function featureContextDir(rootDir, slug) {
|
|
58
|
+
return path.join(path.resolve(rootDir), '.aioson', 'context', 'features', slug);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function verificationRunsDir(rootDir, slug) {
|
|
62
|
+
return path.join(featureContextDir(rootDir, slug), 'verification-runs');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
validateFeatureSlug,
|
|
67
|
+
resolveProjectRoot,
|
|
68
|
+
resolveInsideRoot,
|
|
69
|
+
relativeFromRoot,
|
|
70
|
+
featureContextDir,
|
|
71
|
+
verificationRunsDir,
|
|
72
|
+
isInsideRoot,
|
|
73
|
+
toPosixPath
|
|
74
|
+
};
|
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { machineReportSchemaExample } = require('./schema');
|
|
7
|
+
const { verificationRunsDir, relativeFromRoot, resolveInsideRoot } = require('./path-policy');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_PROMPT_BUDGET_CHARS = 24000;
|
|
10
|
+
|
|
11
|
+
function timestampForFile(date = new Date()) {
|
|
12
|
+
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function asJson(value) {
|
|
16
|
+
return JSON.stringify(value, null, 2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function compactBundleForBudget(evidenceBundle) {
|
|
20
|
+
return {
|
|
21
|
+
...evidenceBundle,
|
|
22
|
+
artifact_summaries: (evidenceBundle.artifact_summaries || []).map((artifact) => ({
|
|
23
|
+
type: artifact.type,
|
|
24
|
+
role: artifact.role,
|
|
25
|
+
path: artifact.path,
|
|
26
|
+
exists: artifact.exists,
|
|
27
|
+
size_bytes: artifact.size_bytes,
|
|
28
|
+
omitted_reason: artifact.omitted_reason || 'prompt_budget_compact_mode'
|
|
29
|
+
}))
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function truncateText(value, maxChars) {
|
|
34
|
+
const text = String(value || '');
|
|
35
|
+
if (!maxChars || text.length <= maxChars) return text;
|
|
36
|
+
return `${text.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function compactCommandPlan(commands, limit = 25) {
|
|
40
|
+
const list = Array.isArray(commands) ? commands : [];
|
|
41
|
+
const selected = list.slice(0, limit).map((command) => ({
|
|
42
|
+
order: command.order,
|
|
43
|
+
command: command.command,
|
|
44
|
+
required: command.required,
|
|
45
|
+
source: command.source,
|
|
46
|
+
last_status: command.last_status || command.status
|
|
47
|
+
}));
|
|
48
|
+
if (list.length > selected.length) {
|
|
49
|
+
selected.push({
|
|
50
|
+
omitted_count: list.length - selected.length,
|
|
51
|
+
omitted_reason: 'prompt_budget_command_plan_limit'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return selected;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function minimalBundleForBudget(evidenceBundle, limits = {}) {
|
|
58
|
+
const sourceLimit = limits.sourceLimit || 40;
|
|
59
|
+
const claimLimit = limits.claimLimit || 80;
|
|
60
|
+
const gapLimit = limits.gapLimit || 20;
|
|
61
|
+
const commandLimit = limits.commandLimit || 25;
|
|
62
|
+
const summaryMax = limits.summaryMax || 220;
|
|
63
|
+
return {
|
|
64
|
+
...compactBundleForBudget(evidenceBundle),
|
|
65
|
+
source_artifacts: (evidenceBundle.source_artifacts || []).slice(0, sourceLimit).map((artifact) => ({
|
|
66
|
+
type: artifact.type,
|
|
67
|
+
role: artifact.role,
|
|
68
|
+
path: artifact.path
|
|
69
|
+
})),
|
|
70
|
+
artifact_summaries: [],
|
|
71
|
+
ledger_claims: compactLedgerClaims(evidenceBundle.ledger_claims || [], { summaryMax }).slice(0, claimLimit),
|
|
72
|
+
known_gaps: (evidenceBundle.known_gaps || []).slice(0, gapLimit).map((gap) => ({
|
|
73
|
+
id: gap.id,
|
|
74
|
+
owner: gap.owner,
|
|
75
|
+
blocks: gap.blocks,
|
|
76
|
+
gap: truncateText(gap.gap, summaryMax)
|
|
77
|
+
})),
|
|
78
|
+
command_plan: compactCommandPlan(evidenceBundle.command_plan || [], commandLimit),
|
|
79
|
+
verification_commands: compactCommandPlan(evidenceBundle.verification_commands || [], commandLimit)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function compactLedgerClaims(claims, options = {}) {
|
|
84
|
+
const summaryMax = options.summaryMax || 220;
|
|
85
|
+
return (claims || []).map((claim) => ({
|
|
86
|
+
id: claim.id,
|
|
87
|
+
kind: claim.kind,
|
|
88
|
+
summary: truncateText(claim.summary, summaryMax),
|
|
89
|
+
owner: claim.owner,
|
|
90
|
+
status: claim.status,
|
|
91
|
+
evidence_count: Array.isArray(claim.evidence) ? claim.evidence.length : 0
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildPromptMarkdown({ slug, policy, ledger, evidenceBundle, promptBudget = {} }) {
|
|
96
|
+
const budget = {
|
|
97
|
+
max_chars: promptBudget.max_chars || DEFAULT_PROMPT_BUDGET_CHARS,
|
|
98
|
+
compact_mode: Boolean(promptBudget.compact_mode),
|
|
99
|
+
artifact_summary_mode: promptBudget.artifact_summary_mode || 'preview'
|
|
100
|
+
};
|
|
101
|
+
const claims = budget.compact_mode
|
|
102
|
+
? compactLedgerClaims(evidenceBundle.ledger_claims || ledger.claims || [])
|
|
103
|
+
: (evidenceBundle.ledger_claims || ledger.claims || []);
|
|
104
|
+
return `# Clean Auditor Prompt - ${slug}
|
|
105
|
+
|
|
106
|
+
You are an independent adversarial implementation auditor. Do not trust implementation summaries. Verify each claim against real files, git state, tests, and source artifacts.
|
|
107
|
+
|
|
108
|
+
## Mission
|
|
109
|
+
|
|
110
|
+
Judge whether the implementation evidence confirms the approved behavior for \`${slug}\`. Your job is not to be nice; your job is to find mismatches without inventing false positives.
|
|
111
|
+
|
|
112
|
+
## Hard Rules
|
|
113
|
+
|
|
114
|
+
- Read the ledger claims before judging.
|
|
115
|
+
- Treat tests passed as evidence, not proof of full behavior.
|
|
116
|
+
- Distinguish "not found" from "false".
|
|
117
|
+
- Report exact \`file:line\` for divergences when available.
|
|
118
|
+
- Respect explicit false-positive exceptions from source artifacts or the ledger.
|
|
119
|
+
- Do not execute destructive commands.
|
|
120
|
+
- Do not propose broad unrelated refactors.
|
|
121
|
+
- No courtesy PASS.
|
|
122
|
+
- Produce the \`Machine Report\` JSON block exactly.
|
|
123
|
+
|
|
124
|
+
## PASS Criteria
|
|
125
|
+
|
|
126
|
+
PASS only when every implemented or partial required claim is confirmed against files/tests/artifacts, required commands are passed or explicitly justified, scope gaps are owned, and no blocking finding remains. A green test suite alone is not enough.
|
|
127
|
+
|
|
128
|
+
## Owner Routing
|
|
129
|
+
|
|
130
|
+
- Implementation infidelity, missing behavior, broken wiring -> \`NEEDS_DEV_FIX\` / \`dev\`.
|
|
131
|
+
- Product or scope contradiction -> \`NEEDS_SCOPE_DECISION\` / \`product\` or \`sheldon\`.
|
|
132
|
+
- Tests not rerun, weak coverage, stale verification -> \`NEEDS_QA_RECHECK\` / \`qa\` or \`tester\`.
|
|
133
|
+
- Security-sensitive finding -> \`NEEDS_SECURITY_REVIEW\` / \`pentester\`.
|
|
134
|
+
- Missing required evidence -> \`INCONCLUSIVE\` with the missing command or artifact named.
|
|
135
|
+
|
|
136
|
+
## Prompt Package Controls
|
|
137
|
+
|
|
138
|
+
\`\`\`json
|
|
139
|
+
${asJson({
|
|
140
|
+
prompt_budget: budget,
|
|
141
|
+
redactions: evidenceBundle.redactions || {},
|
|
142
|
+
preview_budget: evidenceBundle.preview_budget || {}
|
|
143
|
+
})}
|
|
144
|
+
\`\`\`
|
|
145
|
+
|
|
146
|
+
## Policy
|
|
147
|
+
|
|
148
|
+
\`${policy}\`
|
|
149
|
+
|
|
150
|
+
## Source Artifacts
|
|
151
|
+
|
|
152
|
+
\`\`\`json
|
|
153
|
+
${asJson(evidenceBundle.source_artifacts || [])}
|
|
154
|
+
\`\`\`
|
|
155
|
+
|
|
156
|
+
## Artifact Summaries
|
|
157
|
+
|
|
158
|
+
\`\`\`json
|
|
159
|
+
${asJson(evidenceBundle.artifact_summaries || [])}
|
|
160
|
+
\`\`\`
|
|
161
|
+
|
|
162
|
+
## Git State
|
|
163
|
+
|
|
164
|
+
\`\`\`json
|
|
165
|
+
${asJson(evidenceBundle.git || {})}
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
## Ledger Claims
|
|
169
|
+
|
|
170
|
+
\`\`\`json
|
|
171
|
+
${asJson(claims)}
|
|
172
|
+
\`\`\`
|
|
173
|
+
|
|
174
|
+
## Known Gaps
|
|
175
|
+
|
|
176
|
+
\`\`\`json
|
|
177
|
+
${asJson(evidenceBundle.known_gaps || ledger.known_gaps || [])}
|
|
178
|
+
\`\`\`
|
|
179
|
+
|
|
180
|
+
## Required Verification Command Plan
|
|
181
|
+
|
|
182
|
+
\`\`\`json
|
|
183
|
+
${asJson(evidenceBundle.command_plan || evidenceBundle.verification_commands || [])}
|
|
184
|
+
\`\`\`
|
|
185
|
+
|
|
186
|
+
## Required Output
|
|
187
|
+
|
|
188
|
+
Return Markdown with these sections:
|
|
189
|
+
|
|
190
|
+
## Verdict
|
|
191
|
+
## Commands Run
|
|
192
|
+
## Findings
|
|
193
|
+
## Before And Now
|
|
194
|
+
## Residual Risk
|
|
195
|
+
## Recommended Route
|
|
196
|
+
## Machine Report
|
|
197
|
+
|
|
198
|
+
The \`Machine Report\` section must contain one fenced \`json\` block matching:
|
|
199
|
+
|
|
200
|
+
\`\`\`json
|
|
201
|
+
${asJson(machineReportSchemaExample(slug, policy))}
|
|
202
|
+
\`\`\`
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildWithinBudget({ slug, policy, ledger, evidenceBundle, maxChars = DEFAULT_PROMPT_BUDGET_CHARS }) {
|
|
207
|
+
let promptBudget = {
|
|
208
|
+
max_chars: maxChars,
|
|
209
|
+
compact_mode: false,
|
|
210
|
+
artifact_summary_mode: 'preview'
|
|
211
|
+
};
|
|
212
|
+
let markdown = buildPromptMarkdown({ slug, policy, ledger, evidenceBundle, promptBudget });
|
|
213
|
+
if (markdown.length <= maxChars) {
|
|
214
|
+
return { markdown, prompt_budget: { ...promptBudget, actual_chars: markdown.length, over_budget: false } };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const compactBundle = compactBundleForBudget(evidenceBundle);
|
|
218
|
+
promptBudget = {
|
|
219
|
+
max_chars: maxChars,
|
|
220
|
+
compact_mode: true,
|
|
221
|
+
artifact_summary_mode: 'path_only'
|
|
222
|
+
};
|
|
223
|
+
markdown = buildPromptMarkdown({ slug, policy, ledger, evidenceBundle: compactBundle, promptBudget });
|
|
224
|
+
if (markdown.length <= maxChars) {
|
|
225
|
+
return {
|
|
226
|
+
markdown,
|
|
227
|
+
prompt_budget: {
|
|
228
|
+
...promptBudget,
|
|
229
|
+
actual_chars: markdown.length,
|
|
230
|
+
over_budget: false
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const tiers = [
|
|
236
|
+
{ artifactSummaryMode: 'minimal_path_only', sourceLimit: 40, claimLimit: 80, gapLimit: 20, commandLimit: 25, summaryMax: 220 },
|
|
237
|
+
{ artifactSummaryMode: 'minimal_tight', sourceLimit: 30, claimLimit: 50, gapLimit: 12, commandLimit: 20, summaryMax: 140 },
|
|
238
|
+
{ artifactSummaryMode: 'minimal_floor', sourceLimit: 20, claimLimit: 35, gapLimit: 8, commandLimit: 14, summaryMax: 100 },
|
|
239
|
+
{ artifactSummaryMode: 'minimal_core', sourceLimit: 12, claimLimit: 24, gapLimit: 5, commandLimit: 10, summaryMax: 80 }
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
for (const tier of tiers) {
|
|
243
|
+
const minimalBundle = minimalBundleForBudget(evidenceBundle, tier);
|
|
244
|
+
promptBudget = {
|
|
245
|
+
max_chars: maxChars,
|
|
246
|
+
compact_mode: true,
|
|
247
|
+
artifact_summary_mode: tier.artifactSummaryMode,
|
|
248
|
+
claim_summary_chars: tier.summaryMax
|
|
249
|
+
};
|
|
250
|
+
markdown = buildPromptMarkdown({ slug, policy, ledger, evidenceBundle: minimalBundle, promptBudget });
|
|
251
|
+
if (markdown.length <= maxChars) {
|
|
252
|
+
return {
|
|
253
|
+
markdown,
|
|
254
|
+
prompt_budget: {
|
|
255
|
+
...promptBudget,
|
|
256
|
+
actual_chars: markdown.length,
|
|
257
|
+
over_budget: false
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
markdown,
|
|
265
|
+
prompt_budget: {
|
|
266
|
+
...promptBudget,
|
|
267
|
+
actual_chars: markdown.length,
|
|
268
|
+
over_budget: markdown.length > maxChars
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function writePromptPackage(rootDir, slug, markdown, outPath = null) {
|
|
274
|
+
let targetPath;
|
|
275
|
+
if (outPath) {
|
|
276
|
+
const safe = resolveInsideRoot(rootDir, outPath);
|
|
277
|
+
if (!safe.ok) return safe;
|
|
278
|
+
targetPath = safe.path;
|
|
279
|
+
} else {
|
|
280
|
+
const runsDir = verificationRunsDir(rootDir, slug);
|
|
281
|
+
await fs.mkdir(runsDir, { recursive: true });
|
|
282
|
+
targetPath = path.join(runsDir, `${timestampForFile()}-prompt.md`);
|
|
283
|
+
}
|
|
284
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
285
|
+
await fs.writeFile(targetPath, markdown, 'utf8');
|
|
286
|
+
return { ok: true, prompt_path: relativeFromRoot(rootDir, targetPath) };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function buildAndWritePromptPackage({ rootDir, slug, policy, ledger, evidenceBundle, outPath }) {
|
|
290
|
+
const { markdown, prompt_budget: promptBudget } = buildWithinBudget({ slug, policy, ledger, evidenceBundle });
|
|
291
|
+
const written = await writePromptPackage(rootDir, slug, markdown, outPath);
|
|
292
|
+
if (!written.ok) return written;
|
|
293
|
+
return {
|
|
294
|
+
ok: true,
|
|
295
|
+
feature_slug: slug,
|
|
296
|
+
policy,
|
|
297
|
+
prompt_path: written.prompt_path,
|
|
298
|
+
prompt_chars: markdown.length,
|
|
299
|
+
prompt_budget: promptBudget,
|
|
300
|
+
redactions: evidenceBundle.redactions || {},
|
|
301
|
+
source_artifacts: evidenceBundle.source_artifacts,
|
|
302
|
+
artifact_summaries: evidenceBundle.artifact_summaries,
|
|
303
|
+
verification_commands: evidenceBundle.verification_commands,
|
|
304
|
+
command_plan: evidenceBundle.command_plan
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
module.exports = {
|
|
309
|
+
buildPromptMarkdown,
|
|
310
|
+
buildAndWritePromptPackage,
|
|
311
|
+
timestampForFile,
|
|
312
|
+
machineReportSchemaExample,
|
|
313
|
+
DEFAULT_PROMPT_BUDGET_CHARS
|
|
314
|
+
};
|