@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.
Files changed (158) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +19 -6
  3. package/docs/en/1-understand/ecosystem-map.md +45 -29
  4. package/docs/en/1-understand/glossary.md +5 -5
  5. package/docs/en/1-understand/what-is-aioson.md +5 -5
  6. package/docs/en/2-start/existing-project.md +7 -7
  7. package/docs/en/2-start/first-project.md +32 -38
  8. package/docs/en/2-start/initial-decisions.md +18 -17
  9. package/docs/en/3-recipes/README.md +2 -2
  10. package/docs/en/3-recipes/continuity-between-sessions.md +2 -2
  11. package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +5 -2
  12. package/docs/en/3-recipes/full-feature-with-sheldon.md +327 -338
  13. package/docs/en/4-agents/README.md +28 -14
  14. package/docs/en/4-agents/discovery-design-doc.md +20 -8
  15. package/docs/en/4-agents/forge-run.md +3 -2
  16. package/docs/en/5-reference/cli-reference.md +51 -48
  17. package/docs/en/5-reference/executable-verification.md +10 -7
  18. package/docs/en/5-reference/parallel.md +2 -0
  19. package/docs/en/5-reference/qa-browser.md +2 -2
  20. package/docs/en/README.md +3 -3
  21. package/docs/pt/1-entender/glossario.md +4 -4
  22. package/docs/pt/1-entender/mapa-do-ecossistema.md +32 -21
  23. package/docs/pt/2-comecar/decisoes-iniciais.md +16 -16
  24. package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
  25. package/docs/pt/3-receitas/README.md +1 -1
  26. package/docs/pt/3-receitas/app-saas-do-zero.md +35 -122
  27. package/docs/pt/3-receitas/feature-completa-com-sheldon.md +289 -338
  28. package/docs/pt/4-agentes/analyst.md +9 -5
  29. package/docs/pt/4-agentes/architect.md +9 -5
  30. package/docs/pt/4-agentes/dev.md +19 -7
  31. package/docs/pt/4-agentes/discovery-design-doc.md +25 -23
  32. package/docs/pt/4-agentes/orchestrator.md +164 -118
  33. package/docs/pt/4-agentes/pentester.md +5 -5
  34. package/docs/pt/4-agentes/pm.md +15 -7
  35. package/docs/pt/4-agentes/sheldon.md +166 -99
  36. package/docs/pt/4-agentes/ux-ui.md +7 -4
  37. package/docs/pt/5-referencia/agent-chain-continuity.md +14 -5
  38. package/docs/pt/5-referencia/comandos-cli.md +1 -1
  39. package/docs/pt/5-referencia/harness-retro.md +2 -1
  40. package/docs/pt/5-referencia/sdd-automation-scripts.md +6 -6
  41. package/docs/pt/5-referencia/sdd-framework.md +53 -16
  42. package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +27 -15
  43. package/docs/pt/README.md +4 -4
  44. package/docs/pt/agentes.md +48 -50
  45. package/package.json +2 -2
  46. package/src/artifact-kinds.js +110 -0
  47. package/src/cli.js +82 -42
  48. package/src/commands/agent-epilogue.js +251 -186
  49. package/src/commands/agents.js +104 -48
  50. package/src/commands/audit-code.js +344 -0
  51. package/src/commands/classify.js +75 -13
  52. package/src/commands/feature-close.js +43 -18
  53. package/src/commands/harness-check.js +259 -175
  54. package/src/commands/harness-retro-promote.js +387 -0
  55. package/src/commands/live.js +24 -0
  56. package/src/commands/prototype-check.js +163 -0
  57. package/src/commands/review-feature.js +189 -0
  58. package/src/commands/runtime.js +81 -66
  59. package/src/commands/sync-agents-copy.js +115 -0
  60. package/src/commands/verification-plan.js +116 -0
  61. package/src/commands/verify-artifact.js +530 -0
  62. package/src/commands/verify-implementation.js +428 -0
  63. package/src/commands/workflow-execute.js +309 -309
  64. package/src/commands/workflow-next.js +361 -19
  65. package/src/commands/workflow-plan.js +5 -5
  66. package/src/constants.js +4 -0
  67. package/src/gateway-pointer-merge.js +7 -1
  68. package/src/handoff-contract.js +267 -172
  69. package/src/harness/contract-integrity-gate.js +172 -0
  70. package/src/harness/contract-integrity.js +111 -0
  71. package/src/harness/contract-schema.js +377 -332
  72. package/src/harness/detect-runtime-feature.js +90 -0
  73. package/src/harness/static-criteria.js +192 -0
  74. package/src/i18n/messages/en.js +8 -4
  75. package/src/i18n/messages/es.js +8 -4
  76. package/src/i18n/messages/fr.js +8 -4
  77. package/src/i18n/messages/pt-BR.js +8 -4
  78. package/src/install-wizard.js +8 -8
  79. package/src/installer.js +13 -6
  80. package/src/lib/retro/retro-render.js +10 -1
  81. package/src/lib/retro/retro-sources.js +45 -27
  82. package/src/lib/retro/verification-reports.js +230 -0
  83. package/src/parser.js +6 -0
  84. package/src/preflight-engine.js +12 -12
  85. package/src/runtime-store.js +13 -9
  86. package/src/verification/evidence-bundle.js +251 -0
  87. package/src/verification/ledger-store.js +221 -0
  88. package/src/verification/path-policy.js +74 -0
  89. package/src/verification/policy-engine.js +95 -0
  90. package/src/verification/prompt-package.js +314 -0
  91. package/src/verification/redaction.js +77 -0
  92. package/src/verification/report-parser.js +132 -0
  93. package/src/verification/report-store.js +97 -0
  94. package/src/verification/result.js +16 -0
  95. package/src/verification/runners/index.js +319 -0
  96. package/src/verification/runtime-telemetry.js +144 -0
  97. package/src/verification/schema.js +276 -0
  98. package/src/verification/source-discovery.js +153 -0
  99. package/src/verification-policy.js +398 -0
  100. package/src/version.js +52 -1
  101. package/template/.aioson/agents/analyst.md +9 -6
  102. package/template/.aioson/agents/architect.md +34 -5
  103. package/template/.aioson/agents/briefing-refiner.md +25 -0
  104. package/template/.aioson/agents/briefing.md +69 -12
  105. package/template/.aioson/agents/committer.md +2 -1
  106. package/template/.aioson/agents/copywriter.md +30 -21
  107. package/template/.aioson/agents/design-hybrid-forge.md +28 -15
  108. package/template/.aioson/agents/dev.md +28 -10
  109. package/template/.aioson/agents/deyvin.md +3 -2
  110. package/template/.aioson/agents/discover.md +12 -3
  111. package/template/.aioson/agents/discovery-design-doc.md +7 -2
  112. package/template/.aioson/agents/genome.md +19 -10
  113. package/template/.aioson/agents/neo.md +30 -30
  114. package/template/.aioson/agents/orache.md +20 -11
  115. package/template/.aioson/agents/orchestrator.md +84 -7
  116. package/template/.aioson/agents/pm.md +8 -8
  117. package/template/.aioson/agents/product.md +15 -11
  118. package/template/.aioson/agents/profiler-enricher.md +20 -11
  119. package/template/.aioson/agents/profiler-forge.md +20 -11
  120. package/template/.aioson/agents/profiler-researcher.md +20 -11
  121. package/template/.aioson/agents/qa.md +87 -17
  122. package/template/.aioson/agents/scope-check.md +19 -5
  123. package/template/.aioson/agents/setup.md +12 -1
  124. package/template/.aioson/agents/sheldon.md +92 -9
  125. package/template/.aioson/agents/site-forge.md +11 -2
  126. package/template/.aioson/agents/squad.md +20 -5
  127. package/template/.aioson/agents/ux-ui.md +4 -2
  128. package/template/.aioson/agents/validator.md +33 -1
  129. package/template/.aioson/config/verification.json +61 -0
  130. package/template/.aioson/config.md +13 -9
  131. package/template/.aioson/docs/LAYERS.md +2 -2
  132. package/template/.aioson/docs/autopilot-handoff.md +10 -10
  133. package/template/.aioson/docs/dev/execution-discipline.md +41 -0
  134. package/template/.aioson/docs/dev/phase-loop.md +47 -0
  135. package/template/.aioson/docs/feature-expansion-taxonomy.md +31 -3
  136. package/template/.aioson/docs/presets/workflow.config.full-merged.json +15 -0
  137. package/template/.aioson/docs/presets/workflow.config.lean.json +15 -0
  138. package/template/.aioson/docs/product/prd-contract.md +12 -12
  139. package/template/.aioson/docs/prototype-contract.md +98 -0
  140. package/template/.aioson/docs/reference-identity.md +94 -0
  141. package/template/.aioson/docs/sheldon/harness-contract.md +155 -28
  142. package/template/.aioson/docs/verification-config.md +82 -0
  143. package/template/.aioson/docs/verify-artifact-gates.md +91 -0
  144. package/template/.aioson/docs/workflow-lean-lane.md +129 -0
  145. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +2 -0
  146. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +16 -14
  147. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +2 -2
  148. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -4
  149. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +15 -15
  150. package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +25 -3
  151. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +5 -3
  152. package/template/.aioson/skills/process/design-hybrid-forge/references/external-source-ingestion.md +89 -0
  153. package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +29 -2
  154. package/template/.aioson/skills/process/prototype-forge/SKILL.md +98 -0
  155. package/template/.aioson/skills/process/reference-identity-extract/SKILL.md +164 -0
  156. package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +23 -2
  157. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -5
  158. package/template/CLAUDE.md +15 -11
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Best-effort runtime telemetry for `verify:implementation`.
5
+ *
6
+ * The verifier already stores durable artifacts under
7
+ * `.aioson/context/features/{slug}/verification-runs/`. Runtime telemetry is a
8
+ * query/index layer only: it must never store raw auditor output, stderr,
9
+ * prompt text, or finding evidence.
10
+ */
11
+
12
+ const { openRuntimeDb, startTask, startRun } = require('../runtime-store');
13
+
14
+ const SOURCE = 'verify_implementation';
15
+ const EVENT_TYPE = 'implementation_verification_completed';
16
+ const PHASE = 'implementation_verification';
17
+
18
+ function bool(value) {
19
+ return Boolean(value);
20
+ }
21
+
22
+ function safeString(value) {
23
+ if (value === null || value === undefined) return null;
24
+ const text = String(value).trim();
25
+ return text || null;
26
+ }
27
+
28
+ function safeNumber(value) {
29
+ if (value === null || value === undefined) return null;
30
+ const number = Number(value);
31
+ return Number.isFinite(number) ? number : null;
32
+ }
33
+
34
+ function pathOrNull(value) {
35
+ const text = safeString(value);
36
+ return text ? text.replace(/\\/g, '/') : null;
37
+ }
38
+
39
+ function summarizeRunner(runner) {
40
+ if (!runner || typeof runner !== 'object') return null;
41
+ return {
42
+ status: safeString(runner.status),
43
+ tool_detected: bool(runner.detected),
44
+ permission_mode: safeString(runner.permission_mode),
45
+ destructive_commands_allowed: bool(runner.destructive_commands_allowed),
46
+ timeout_ms: safeNumber(runner.timeout_ms),
47
+ max_output_bytes: safeNumber(runner.max_output_bytes),
48
+ duration_ms: safeNumber(runner.duration_ms),
49
+ exit_code: safeNumber(runner.exit_code),
50
+ signal: safeString(runner.signal),
51
+ output_bytes: safeNumber(runner.output_bytes),
52
+ output_truncated: bool(runner.output_truncated)
53
+ };
54
+ }
55
+
56
+ function buildVerificationTelemetryPayload(result, { durationMs = null } = {}) {
57
+ const runner = summarizeRunner(result.runner);
58
+ const payload = {
59
+ feature_slug: safeString(result.feature_slug),
60
+ mode: safeString(result.mode),
61
+ policy: safeString(result.policy),
62
+ ok: bool(result.ok),
63
+ verdict: safeString(result.verdict),
64
+ recommended_route: safeString(result.recommended_route),
65
+ blocking_findings_count: safeNumber(result.blocking_findings_count),
66
+ reason: safeString(result.reason),
67
+ tool: safeString(result.tool),
68
+ model: safeString(result.model),
69
+ ledger_path: pathOrNull(result.ledger_path),
70
+ prompt_path: pathOrNull(result.prompt_path),
71
+ report_path: pathOrNull(result.report_path),
72
+ run_report_path: pathOrNull(result.run_report_path),
73
+ report_json_path: pathOrNull(result.report_json_path),
74
+ raw_output_stored: bool(result.raw_report_path),
75
+ stderr_stored: bool(result.stderr_path),
76
+ duration_ms: safeNumber(durationMs)
77
+ };
78
+
79
+ if (runner) payload.runner = runner;
80
+ return payload;
81
+ }
82
+
83
+ function runtimeStatus(result) {
84
+ if (!result || result.mode === 'verify-implementation') return 'failed';
85
+ return 'completed';
86
+ }
87
+
88
+ async function recordVerificationTelemetry(rootDir, result, { startedAt = null } = {}) {
89
+ if (!rootDir || !result || typeof result !== 'object') {
90
+ return { emitted: false, reason: 'invalid_input' };
91
+ }
92
+
93
+ try {
94
+ const durationMs = startedAt ? Math.max(0, Date.now() - Number(startedAt)) : null;
95
+ const payload = buildVerificationTelemetryPayload(result, { durationMs });
96
+ const { db } = await openRuntimeDb(rootDir);
97
+ try {
98
+ const status = runtimeStatus(result);
99
+ const title = `Implementation verification: ${payload.feature_slug || 'unknown'}`;
100
+ const taskKey = startTask(db, {
101
+ title,
102
+ taskKind: 'implementation_verification',
103
+ status,
104
+ createdBy: 'verify:implementation',
105
+ metaJson: {
106
+ feature_slug: payload.feature_slug,
107
+ mode: payload.mode,
108
+ policy: payload.policy,
109
+ source: SOURCE
110
+ }
111
+ });
112
+ const runKey = startRun(db, {
113
+ taskKey,
114
+ agentName: 'verify:implementation',
115
+ agentKind: 'system',
116
+ source: SOURCE,
117
+ title: `verify:implementation ${payload.mode || 'unknown'} ${payload.feature_slug || 'unknown'}`,
118
+ status,
119
+ summary: `${payload.verdict || (result.ok ? 'PASS' : 'INCONCLUSIVE')} via ${payload.mode || 'unknown'}`,
120
+ outputPath: payload.report_path || payload.prompt_path || payload.ledger_path || null,
121
+ eventType: EVENT_TYPE,
122
+ phase: PHASE,
123
+ toolName: payload.tool,
124
+ verdict: payload.verdict,
125
+ message: `Implementation verification completed: ${payload.verdict || (result.ok ? 'ok' : 'blocked')}`,
126
+ payload
127
+ });
128
+ return { emitted: true, task_key: taskKey, run_key: runKey, event_type: EVENT_TYPE };
129
+ } finally {
130
+ db.close();
131
+ }
132
+ } catch (err) {
133
+ return { emitted: false, reason: 'telemetry_error', message: err && err.message ? err.message : String(err) };
134
+ }
135
+ }
136
+
137
+ module.exports = {
138
+ SOURCE,
139
+ EVENT_TYPE,
140
+ PHASE,
141
+ buildVerificationTelemetryPayload,
142
+ recordVerificationTelemetry
143
+ };
144
+
@@ -0,0 +1,276 @@
1
+ 'use strict';
2
+
3
+ const { resolveInsideRoot } = require('./path-policy');
4
+
5
+ const LEDGER_SCHEMA_VERSION = 'implementation-ledger/v1';
6
+ const REPORT_SCHEMA_VERSION = 'verification-report/v1';
7
+
8
+ const POLICIES = new Set(['advisory', 'standard', 'strict']);
9
+
10
+ const CLAIM_STATUSES = new Set([
11
+ 'planned',
12
+ 'implemented',
13
+ 'partial',
14
+ 'blocked',
15
+ 'not_applicable'
16
+ ]);
17
+
18
+ const CLAIM_KINDS = new Set([
19
+ 'required_behavior',
20
+ 'acceptance_criterion',
21
+ 'test_coverage',
22
+ 'scope_constraint',
23
+ 'security_constraint',
24
+ 'prototype_contract',
25
+ 'migration_or_data'
26
+ ]);
27
+
28
+ const OWNERS = new Set([
29
+ 'dev',
30
+ 'deyvin',
31
+ 'product',
32
+ 'sheldon',
33
+ 'architect',
34
+ 'ux-ui',
35
+ 'qa',
36
+ 'tester',
37
+ 'pentester',
38
+ 'scope-check'
39
+ ]);
40
+
41
+ const VERDICTS = new Set([
42
+ 'PASS',
43
+ 'NEEDS_DEV_FIX',
44
+ 'NEEDS_SCOPE_DECISION',
45
+ 'NEEDS_QA_RECHECK',
46
+ 'NEEDS_SECURITY_REVIEW',
47
+ 'INCONCLUSIVE'
48
+ ]);
49
+
50
+ const FINDING_STATUSES = new Set([
51
+ 'CONFIRMS',
52
+ 'DOES_NOT_CONFIRM',
53
+ 'PARTIAL',
54
+ 'NOT_VERIFIED',
55
+ 'NOT_APPLICABLE'
56
+ ]);
57
+
58
+ const SEVERITIES = new Set([
59
+ 'info',
60
+ 'warning',
61
+ 'blocking'
62
+ ]);
63
+
64
+ function normalizePolicy(policy) {
65
+ const value = String(policy || 'standard').toLowerCase();
66
+ return POLICIES.has(value) ? value : null;
67
+ }
68
+
69
+ function validatePathInsideRoot(rootDir, relPath, field, errors) {
70
+ if (!relPath) {
71
+ errors.push({ field, reason: 'missing' });
72
+ return;
73
+ }
74
+ const safe = resolveInsideRoot(rootDir, relPath);
75
+ if (!safe.ok) errors.push({ field, reason: safe.reason });
76
+ }
77
+
78
+ function validateSourceArtifacts(rootDir, artifacts, errors) {
79
+ if (!Array.isArray(artifacts)) {
80
+ errors.push({ field: 'source_artifacts', reason: 'must_be_array' });
81
+ return;
82
+ }
83
+ for (const [index, artifact] of artifacts.entries()) {
84
+ if (!artifact || typeof artifact !== 'object') {
85
+ errors.push({ field: `source_artifacts[${index}]`, reason: 'must_be_object' });
86
+ continue;
87
+ }
88
+ if (!artifact.type) errors.push({ field: `source_artifacts[${index}].type`, reason: 'missing' });
89
+ validatePathInsideRoot(rootDir, artifact.path, `source_artifacts[${index}].path`, errors);
90
+ }
91
+ }
92
+
93
+ function validateClaims(rootDir, claims, errors) {
94
+ if (!Array.isArray(claims)) {
95
+ errors.push({ field: 'claims', reason: 'must_be_array' });
96
+ return;
97
+ }
98
+
99
+ for (const [index, claim] of claims.entries()) {
100
+ if (!claim || typeof claim !== 'object') {
101
+ errors.push({ field: `claims[${index}]`, reason: 'must_be_object' });
102
+ continue;
103
+ }
104
+ if (!claim.id) errors.push({ field: `claims[${index}].id`, reason: 'missing' });
105
+ if (!claim.summary) errors.push({ field: `claims[${index}].summary`, reason: 'missing' });
106
+ if (!CLAIM_KINDS.has(claim.kind)) errors.push({ field: `claims[${index}].kind`, reason: 'invalid' });
107
+ if (!OWNERS.has(claim.owner)) errors.push({ field: `claims[${index}].owner`, reason: 'invalid' });
108
+ if (!CLAIM_STATUSES.has(claim.status)) errors.push({ field: `claims[${index}].status`, reason: 'invalid' });
109
+ if (!Array.isArray(claim.evidence)) {
110
+ errors.push({ field: `claims[${index}].evidence`, reason: 'must_be_array' });
111
+ continue;
112
+ }
113
+ for (const [evidenceIndex, evidence] of claim.evidence.entries()) {
114
+ if (evidence && evidence.path) {
115
+ validatePathInsideRoot(
116
+ rootDir,
117
+ evidence.path,
118
+ `claims[${index}].evidence[${evidenceIndex}].path`,
119
+ errors
120
+ );
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ function validateLedgerVerificationCommands(commands, errors) {
127
+ if (!Array.isArray(commands)) {
128
+ errors.push({ field: 'verification_commands', reason: 'must_be_array' });
129
+ return;
130
+ }
131
+ for (const [index, command] of commands.entries()) {
132
+ if (!command || typeof command !== 'object') {
133
+ errors.push({ field: `verification_commands[${index}]`, reason: 'must_be_object' });
134
+ continue;
135
+ }
136
+ if (!command.command) errors.push({ field: `verification_commands[${index}].command`, reason: 'missing' });
137
+ }
138
+ }
139
+
140
+ function validateImplementationLedger(ledger, { rootDir, slug }) {
141
+ const errors = [];
142
+ if (!ledger || typeof ledger !== 'object') {
143
+ return [{ field: 'machine_ledger', reason: 'must_be_object' }];
144
+ }
145
+ if (ledger.schema_version !== LEDGER_SCHEMA_VERSION) {
146
+ errors.push({ field: 'schema_version', reason: 'invalid' });
147
+ }
148
+ if (ledger.feature_slug !== slug) {
149
+ errors.push({ field: 'feature_slug', reason: 'mismatch' });
150
+ }
151
+ validateSourceArtifacts(rootDir, ledger.source_artifacts, errors);
152
+ validateClaims(rootDir, ledger.claims, errors);
153
+ validateLedgerVerificationCommands(ledger.verification_commands, errors);
154
+ if (!Array.isArray(ledger.known_gaps)) {
155
+ errors.push({ field: 'known_gaps', reason: 'must_be_array' });
156
+ }
157
+ return errors;
158
+ }
159
+
160
+ function missingLedgerEvidence(ledger) {
161
+ const claims = Array.isArray(ledger && ledger.claims) ? ledger.claims : [];
162
+ return claims
163
+ .filter((claim) => claim && ['implemented', 'partial'].includes(claim.status))
164
+ .filter((claim) => !Array.isArray(claim.evidence) || claim.evidence.length === 0)
165
+ .map((claim) => claim.id || '(missing id)');
166
+ }
167
+
168
+ function validateReportCommands(commands, errors) {
169
+ if (!Array.isArray(commands)) {
170
+ errors.push({ field: 'commands_run', reason: 'must_be_array' });
171
+ return;
172
+ }
173
+ for (const [index, command] of commands.entries()) {
174
+ if (!command || typeof command !== 'object') {
175
+ errors.push({ field: `commands_run[${index}]`, reason: 'must_be_object' });
176
+ continue;
177
+ }
178
+ if (!command.command) errors.push({ field: `commands_run[${index}].command`, reason: 'missing' });
179
+ }
180
+ }
181
+
182
+ function validateFindings(findings, errors) {
183
+ if (!Array.isArray(findings)) {
184
+ errors.push({ field: 'findings', reason: 'must_be_array' });
185
+ return;
186
+ }
187
+ for (const [index, finding] of findings.entries()) {
188
+ if (!finding || typeof finding !== 'object') {
189
+ errors.push({ field: `findings[${index}]`, reason: 'must_be_object' });
190
+ continue;
191
+ }
192
+ if (!finding.id) errors.push({ field: `findings[${index}].id`, reason: 'missing' });
193
+ if (!FINDING_STATUSES.has(finding.status)) errors.push({ field: `findings[${index}].status`, reason: 'invalid' });
194
+ if (!SEVERITIES.has(finding.severity)) errors.push({ field: `findings[${index}].severity`, reason: 'invalid' });
195
+ if (!OWNERS.has(finding.owner)) errors.push({ field: `findings[${index}].owner`, reason: 'invalid' });
196
+ if (finding.kind && !CLAIM_KINDS.has(finding.kind)) errors.push({ field: `findings[${index}].kind`, reason: 'invalid' });
197
+ if (finding.recommended_route && !OWNERS.has(finding.recommended_route)) {
198
+ errors.push({ field: `findings[${index}].recommended_route`, reason: 'invalid' });
199
+ }
200
+ }
201
+ }
202
+
203
+ function validateVerificationReport(report, { slug, requestedPolicy }) {
204
+ const errors = [];
205
+ if (!report || typeof report !== 'object') {
206
+ return [{ field: 'machine_report', reason: 'must_be_object' }];
207
+ }
208
+ if (report.schema_version !== REPORT_SCHEMA_VERSION) {
209
+ errors.push({ field: 'schema_version', reason: 'invalid' });
210
+ }
211
+ if (report.feature_slug !== slug) {
212
+ errors.push({ field: 'feature_slug', reason: 'mismatch' });
213
+ }
214
+ if (!normalizePolicy(requestedPolicy || report.policy)) {
215
+ errors.push({ field: 'policy', reason: 'invalid' });
216
+ }
217
+ if (!VERDICTS.has(report.verdict)) {
218
+ errors.push({ field: 'verdict', reason: 'invalid' });
219
+ }
220
+ if (report.recommended_route && !OWNERS.has(report.recommended_route)) {
221
+ errors.push({ field: 'recommended_route', reason: 'invalid' });
222
+ }
223
+ validateReportCommands(report.commands_run, errors);
224
+ validateFindings(report.findings, errors);
225
+ return errors;
226
+ }
227
+
228
+ function machineReportSchemaExample(slug, policy) {
229
+ return {
230
+ schema_version: REPORT_SCHEMA_VERSION,
231
+ feature_slug: slug,
232
+ policy,
233
+ verdict: 'PASS|NEEDS_DEV_FIX|NEEDS_SCOPE_DECISION|NEEDS_QA_RECHECK|NEEDS_SECURITY_REVIEW|INCONCLUSIVE',
234
+ summary: 'Short evidence-grounded summary.',
235
+ commands_run: [
236
+ {
237
+ command: 'npm test',
238
+ status: 'passed|failed|not_run',
239
+ evidence: 'Short result summary'
240
+ }
241
+ ],
242
+ findings: [
243
+ {
244
+ id: 'FIND-001',
245
+ claim_id: 'CLAIM-001',
246
+ kind: 'required_behavior',
247
+ status: 'CONFIRMS|DOES_NOT_CONFIRM|PARTIAL|NOT_VERIFIED|NOT_APPLICABLE',
248
+ severity: 'info|warning|blocking',
249
+ owner: 'dev',
250
+ file: 'src/example.js',
251
+ line: 1,
252
+ evidence: 'Exact reason with file:line when available.',
253
+ recommended_route: 'dev'
254
+ }
255
+ ],
256
+ recommended_route: 'qa',
257
+ blocking_findings_count: 0
258
+ };
259
+ }
260
+
261
+ module.exports = {
262
+ LEDGER_SCHEMA_VERSION,
263
+ REPORT_SCHEMA_VERSION,
264
+ POLICIES,
265
+ CLAIM_STATUSES,
266
+ CLAIM_KINDS,
267
+ OWNERS,
268
+ VERDICTS,
269
+ FINDING_STATUSES,
270
+ SEVERITIES,
271
+ normalizePolicy,
272
+ validateImplementationLedger,
273
+ missingLedgerEvidence,
274
+ validateVerificationReport,
275
+ machineReportSchemaExample
276
+ };
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const { resolveInsideRoot, relativeFromRoot, toPosixPath } = require('./path-policy');
7
+
8
+ async function fileExists(filePath) {
9
+ try {
10
+ const stat = await fs.stat(filePath);
11
+ return stat.isFile() || stat.isDirectory();
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ async function listDirSafe(dir) {
18
+ try {
19
+ return await fs.readdir(dir, { withFileTypes: true });
20
+ } catch {
21
+ return [];
22
+ }
23
+ }
24
+
25
+ async function walkFiles(rootDir, relativeDir, predicate, limit = 100) {
26
+ const start = path.join(rootDir, relativeDir);
27
+ const found = [];
28
+
29
+ async function walk(absDir) {
30
+ if (found.length >= limit) return;
31
+ const entries = await listDirSafe(absDir);
32
+ for (const entry of entries) {
33
+ if (found.length >= limit) break;
34
+ const full = path.join(absDir, entry.name);
35
+ if (entry.isDirectory()) {
36
+ await walk(full);
37
+ } else if (entry.isFile() && predicate(entry.name, full)) {
38
+ found.push(relativeFromRoot(rootDir, full));
39
+ }
40
+ }
41
+ }
42
+
43
+ if (await fileExists(start)) {
44
+ await walk(start);
45
+ }
46
+ return found;
47
+ }
48
+
49
+ function addArtifact(artifacts, seen, artifact) {
50
+ if (!artifact || !artifact.path) return;
51
+ const key = `${artifact.type}:${toPosixPath(artifact.path)}`;
52
+ if (seen.has(key)) return;
53
+ seen.add(key);
54
+ artifacts.push({
55
+ type: artifact.type,
56
+ path: toPosixPath(artifact.path),
57
+ role: artifact.role || 'source'
58
+ });
59
+ }
60
+
61
+ async function addIfExists(rootDir, artifacts, seen, type, relPath, role = 'source') {
62
+ const safe = resolveInsideRoot(rootDir, relPath);
63
+ if (!safe.ok) return;
64
+ if (await fileExists(safe.path)) {
65
+ addArtifact(artifacts, seen, { type, path: safe.relative_path, role });
66
+ }
67
+ }
68
+
69
+ function ledgerSourceArtifacts(ledger) {
70
+ if (!ledger || !Array.isArray(ledger.source_artifacts)) return [];
71
+ return ledger.source_artifacts
72
+ .filter((artifact) => artifact && artifact.path)
73
+ .map((artifact) => ({
74
+ type: artifact.type || 'ledger_source',
75
+ path: artifact.path,
76
+ role: artifact.role || 'ledger_declared'
77
+ }));
78
+ }
79
+
80
+ function ledgerEvidenceFiles(ledger) {
81
+ if (!ledger || !Array.isArray(ledger.claims)) return [];
82
+ const files = [];
83
+ for (const claim of ledger.claims) {
84
+ if (!claim || !Array.isArray(claim.evidence)) continue;
85
+ for (const evidence of claim.evidence) {
86
+ if (evidence && evidence.path) {
87
+ files.push({
88
+ type: 'implementation_evidence',
89
+ path: evidence.path,
90
+ role: claim.id ? `claim:${claim.id}` : 'claim'
91
+ });
92
+ }
93
+ }
94
+ }
95
+ return files;
96
+ }
97
+
98
+ async function discoverSourceArtifacts(rootDir, slug, ledger = null) {
99
+ const artifacts = [];
100
+ const seen = new Set();
101
+
102
+ await addIfExists(rootDir, artifacts, seen, 'prd', `.aioson/context/prd-${slug}.md`, 'product_authority');
103
+ await addIfExists(rootDir, artifacts, seen, 'requirements', `.aioson/context/requirements-${slug}.md`, 'acceptance_criteria');
104
+ await addIfExists(rootDir, artifacts, seen, 'spec', `.aioson/context/spec-${slug}.md`, 'living_memory');
105
+ await addIfExists(rootDir, artifacts, seen, 'sheldon_enrichment', `.aioson/context/sheldon-enrichment-${slug}.md`, 'product_review');
106
+ await addIfExists(rootDir, artifacts, seen, 'sheldon_validation', `.aioson/context/sheldon-validation-${slug}.md`, 'product_review');
107
+ await addIfExists(rootDir, artifacts, seen, 'design_doc', `.aioson/context/design-doc-${slug}.md`, 'design_authority');
108
+ await addIfExists(rootDir, artifacts, seen, 'readiness', `.aioson/context/readiness-${slug}.md`, 'implementation_readiness');
109
+ await addIfExists(rootDir, artifacts, seen, 'ui_spec', `.aioson/context/ui-spec-${slug}.md`, 'ui_contract');
110
+ await addIfExists(rootDir, artifacts, seen, 'implementation_plan', `.aioson/context/implementation-plan-${slug}.md`, 'execution_plan');
111
+ await addIfExists(rootDir, artifacts, seen, 'simple_plan', `.aioson/context/simple-plans/${slug}.md`, 'simple_plan');
112
+ await addIfExists(rootDir, artifacts, seen, 'scope_check', `.aioson/context/scope-check-${slug}.md`, 'scope_alignment');
113
+ await addIfExists(rootDir, artifacts, seen, 'qa_report', `.aioson/context/qa-report-${slug}.md`, 'qa_findings');
114
+ await addIfExists(rootDir, artifacts, seen, 'security_findings', `.aioson/context/security-findings-${slug}.json`, 'security_findings');
115
+ await addIfExists(rootDir, artifacts, seen, 'dossier', `.aioson/context/features/${slug}/dossier.md`, 'feature_dossier');
116
+ await addIfExists(rootDir, artifacts, seen, 'sheldon_plan', `.aioson/plans/${slug}/manifest.md`, 'phased_plan');
117
+ await addIfExists(rootDir, artifacts, seen, 'harness', `.aioson/plans/${slug}/harness-contract.json`, 'binary_criteria');
118
+ await addIfExists(rootDir, artifacts, seen, 'harness_progress', `.aioson/plans/${slug}/progress.json`, 'binary_criteria_state');
119
+ await addIfExists(rootDir, artifacts, seen, 'harness_output', `.aioson/plans/${slug}/last-check-output.json`, 'binary_criteria_output');
120
+ await addIfExists(rootDir, artifacts, seen, 'prototype', `.aioson/briefings/${slug}/prototype.html`, 'prototype_contract');
121
+ await addIfExists(rootDir, artifacts, seen, 'prototype_manifest', `.aioson/briefings/${slug}/prototype-manifest.md`, 'prototype_contract');
122
+ await addIfExists(rootDir, artifacts, seen, 'prototype_report', `.aioson/briefings/${slug}/prototype-report.md`, 'prototype_contract');
123
+
124
+ const prdFiles = await walkFiles(rootDir, 'prds', (name) => name.includes(slug) && name.endsWith('.md'), 25);
125
+ for (const rel of prdFiles) addArtifact(artifacts, seen, { type: 'source_prd', path: rel, role: 'draft_source' });
126
+
127
+ const rootPlanFiles = await walkFiles(rootDir, 'plans', (name, full) => {
128
+ const rel = relativeFromRoot(rootDir, full);
129
+ return rel.includes(slug);
130
+ }, 50);
131
+ for (const rel of rootPlanFiles) addArtifact(artifacts, seen, { type: 'source_plan', path: rel, role: 'preproduction_source' });
132
+
133
+ const testFiles = await walkFiles(rootDir, 'tests', (name) => name.includes(slug), 50);
134
+ for (const rel of testFiles) addArtifact(artifacts, seen, { type: 'test', path: rel, role: 'verification' });
135
+
136
+ const contextReviewFiles = await walkFiles(rootDir, '.aioson/context', (name) => (
137
+ /^qa-report-/.test(name) && name.includes(slug) && name.endsWith('.md')
138
+ ), 25);
139
+ for (const rel of contextReviewFiles) addArtifact(artifacts, seen, { type: 'qa_report', path: rel, role: 'qa_findings' });
140
+
141
+ for (const artifact of [...ledgerSourceArtifacts(ledger), ...ledgerEvidenceFiles(ledger)]) {
142
+ const safe = resolveInsideRoot(rootDir, artifact.path);
143
+ if (safe.ok && await fileExists(safe.path)) {
144
+ addArtifact(artifacts, seen, { ...artifact, path: safe.relative_path });
145
+ }
146
+ }
147
+
148
+ return artifacts;
149
+ }
150
+
151
+ module.exports = {
152
+ discoverSourceArtifacts
153
+ };