@veraxhq/verax 0.3.0 → 0.4.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/README.md +28 -20
- package/bin/verax.js +11 -18
- package/package.json +28 -7
- package/src/cli/commands/baseline.js +1 -2
- package/src/cli/commands/default.js +72 -81
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +3 -0
- package/src/cli/commands/gates.js +1 -1
- package/src/cli/commands/inspect.js +6 -133
- package/src/cli/commands/release-check.js +2 -0
- package/src/cli/commands/run.js +74 -246
- package/src/cli/commands/security-check.js +2 -1
- package/src/cli/commands/truth.js +0 -1
- package/src/cli/entry.js +82 -309
- package/src/cli/util/angular-component-extractor.js +2 -2
- package/src/cli/util/angular-navigation-detector.js +2 -2
- package/src/cli/util/ast-interactive-detector.js +4 -6
- package/src/cli/util/ast-network-detector.js +3 -3
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +3 -3
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +2 -1
- package/src/cli/util/determinism-writer.js +1 -1
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/env-url.js +0 -4
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +217 -367
- package/src/cli/util/findings-writer.js +19 -126
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -2
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -2
- package/src/cli/util/paths.js +12 -3
- package/src/cli/util/project-discovery.js +284 -3
- package/src/cli/util/project-writer.js +2 -2
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/summary-writer.js +2 -1
- package/src/cli/util/svelte-navigation-detector.js +3 -3
- package/src/cli/util/svelte-sfc-extractor.js +0 -1
- package/src/cli/util/svelte-state-detector.js +1 -2
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +4 -3
- package/src/cli/util/vue-sfc-extractor.js +1 -2
- package/src/cli/util/vue-state-detector.js +1 -1
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/finding-explainer.js +3 -56
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +0 -15
- package/src/verax/core/artifacts/verifier.js +18 -8
- package/src/verax/core/baseline/baseline.snapshot.js +2 -0
- package/src/verax/core/capabilities/gates.js +7 -1
- package/src/verax/core/confidence/confidence-compute.js +14 -7
- package/src/verax/core/confidence/confidence.loader.js +1 -0
- package/src/verax/core/confidence-engine-refactor.js +8 -3
- package/src/verax/core/confidence-engine.js +162 -23
- package/src/verax/core/contracts/types.js +1 -0
- package/src/verax/core/contracts/validators.js +79 -4
- package/src/verax/core/decision-snapshot.js +3 -30
- package/src/verax/core/decisions/decision.trace.js +2 -0
- package/src/verax/core/determinism/contract-writer.js +2 -2
- package/src/verax/core/determinism/contract.js +1 -1
- package/src/verax/core/determinism/diff.js +42 -1
- package/src/verax/core/determinism/engine.js +7 -6
- package/src/verax/core/determinism/finding-identity.js +3 -2
- package/src/verax/core/determinism/normalize.js +32 -4
- package/src/verax/core/determinism/report-writer.js +1 -0
- package/src/verax/core/determinism/run-fingerprint.js +7 -2
- package/src/verax/core/dynamic-route-intelligence.js +8 -7
- package/src/verax/core/evidence/evidence-capture-service.js +1 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +2 -1
- package/src/verax/core/evidence-builder.js +2 -2
- package/src/verax/core/execution-mode-context.js +1 -1
- package/src/verax/core/execution-mode-detector.js +5 -3
- package/src/verax/core/failures/exit-codes.js +39 -37
- package/src/verax/core/failures/failure-summary.js +1 -1
- package/src/verax/core/failures/failure.factory.js +3 -3
- package/src/verax/core/failures/failure.ledger.js +3 -2
- package/src/verax/core/ga/ga.artifact.js +1 -1
- package/src/verax/core/ga/ga.contract.js +3 -2
- package/src/verax/core/ga/ga.enforcer.js +1 -0
- package/src/verax/core/guardrails/policy.loader.js +1 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +1 -1
- package/src/verax/core/guardrails-engine.js +2 -2
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +2 -0
- package/src/verax/core/perf/perf.report.js +2 -0
- package/src/verax/core/pipeline-tracker.js +5 -0
- package/src/verax/core/release/provenance.builder.js +73 -214
- package/src/verax/core/release/release.enforcer.js +14 -9
- package/src/verax/core/release/reproducibility.check.js +1 -0
- package/src/verax/core/release/sbom.builder.js +32 -23
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +6 -3
- package/src/verax/core/report/human-summary.js +141 -1
- package/src/verax/core/route-intelligence.js +4 -3
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +10 -7
- package/src/verax/core/security/security.enforcer.js +4 -0
- package/src/verax/core/security/supplychain.policy.js +9 -1
- package/src/verax/core/security/vuln.scan.js +2 -2
- package/src/verax/core/truth/truth.certificate.js +3 -1
- package/src/verax/core/ui-feedback-intelligence.js +12 -46
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +100 -660
- package/src/verax/detect/confidence-helper.js +1 -0
- package/src/verax/detect/detection-engine.js +1 -18
- package/src/verax/detect/dynamic-route-findings.js +17 -14
- package/src/verax/detect/expectation-chain-detector.js +1 -1
- package/src/verax/detect/expectation-model.js +3 -5
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +126 -166
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +51 -234
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +4 -4
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +7 -6
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +18 -18
- package/src/verax/detect/verdict-engine.js +3 -57
- package/src/verax/detect/view-switch-correlator.js +2 -2
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +48 -412
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +67 -682
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/route-validator.js +1 -4
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +735 -84
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -530
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +1 -1
- package/src/verax/observe/observe-helpers.js +2 -1
- package/src/verax/observe/observe-runner.js +28 -24
- package/src/verax/observe/observers/budget-observer.js +3 -3
- package/src/verax/observe/observers/console-observer.js +4 -4
- package/src/verax/observe/observers/coverage-observer.js +4 -4
- package/src/verax/observe/observers/interaction-observer.js +3 -3
- package/src/verax/observe/observers/navigation-observer.js +4 -4
- package/src/verax/observe/observers/network-observer.js +4 -4
- package/src/verax/observe/observers/safety-observer.js +1 -1
- package/src/verax/observe/observers/ui-feedback-observer.js +4 -4
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-signal-sensor.js +2 -148
- package/src/verax/scan-summary-writer.js +10 -42
- package/src/verax/shared/artifact-manager.js +30 -13
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/core/confidence-engine.js.backup +0 -471
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { writeFileSync, readdirSync, existsSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { aggregateProblems } from './problem-aggregator.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate human-readable SUMMARY.md for a scan run
|
|
7
|
+
*
|
|
8
|
+
* @param {string} runDir - Run directory path
|
|
9
|
+
* @param {string} url - URL that was scanned
|
|
10
|
+
* @param {string} srcDir - Source directory used
|
|
11
|
+
* @param {Object} findings - Findings from detect phase
|
|
12
|
+
* @param {Object} learnData - Learn phase output
|
|
13
|
+
* @param {Object} observeData - Observe phase output
|
|
14
|
+
* @returns {string} Path to written SUMMARY.md file
|
|
15
|
+
*/
|
|
16
|
+
export function writeSummaryMarkdown(runDir, url, srcDir, findings, learnData, observeData) {
|
|
17
|
+
const summaryPath = resolve(runDir, 'SUMMARY.md');
|
|
18
|
+
const evidenceDir = resolve(runDir, 'EVIDENCE');
|
|
19
|
+
const hasBefore = existsSync(resolve(evidenceDir, 'before.png'));
|
|
20
|
+
const hasAfter = existsSync(resolve(evidenceDir, 'after.png'));
|
|
21
|
+
const hasElement = existsSync(resolve(evidenceDir, 'element.png'));
|
|
22
|
+
const hasDomDiff = existsSync(resolve(evidenceDir, 'dom_diff.json'));
|
|
23
|
+
const evidenceLines = [];
|
|
24
|
+
if (hasBefore) evidenceLines.push('- **Before:** [before.png](EVIDENCE/before.png)');
|
|
25
|
+
if (hasAfter) evidenceLines.push('- **After:** [after.png](EVIDENCE/after.png)');
|
|
26
|
+
if (hasElement) evidenceLines.push('- **Element (optional):** [element.png](EVIDENCE/element.png)');
|
|
27
|
+
if (hasDomDiff) evidenceLines.push('- **DOM diff:** [dom_diff.json](EVIDENCE/dom_diff.json)');
|
|
28
|
+
const evidenceSection = evidenceLines.length > 0 ? evidenceLines.join('\n') : '*No evidence files captured.*';
|
|
29
|
+
|
|
30
|
+
// Determine framework if possible
|
|
31
|
+
const framework = detectFramework(learnData);
|
|
32
|
+
|
|
33
|
+
// Aggregate problems from findings
|
|
34
|
+
const problems = aggregateProblems(findings.findings || [], { projectType: framework });
|
|
35
|
+
|
|
36
|
+
// Build findings list
|
|
37
|
+
const findingsList = buildFindingsList(findings, observeData);
|
|
38
|
+
|
|
39
|
+
// Determine result status
|
|
40
|
+
const resultStatus = determineResultStatus(findings);
|
|
41
|
+
|
|
42
|
+
// Detect fallback debug artifacts
|
|
43
|
+
let fallbackEvidenceNote = '';
|
|
44
|
+
try {
|
|
45
|
+
const debugDir = resolve(runDir, 'DEBUG');
|
|
46
|
+
if (existsSync(debugDir)) {
|
|
47
|
+
const files = readdirSync(debugDir);
|
|
48
|
+
const hasPage = files.includes('page.html');
|
|
49
|
+
const hasConsole = files.includes('console.log');
|
|
50
|
+
if (hasPage || hasConsole) {
|
|
51
|
+
fallbackEvidenceNote = '\n**Evidence warning:** Some screenshots may have appeared blank during capture. We saved fallback artifacts for review in the `DEBUG/` folder (non-blocking):\n- Page HTML: [page.html](DEBUG/page.html)\n- Console log: [console.log](DEBUG/console.log)\n';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (e) { /* noop */ }
|
|
55
|
+
|
|
56
|
+
// Build markdown content
|
|
57
|
+
const now = new Date();
|
|
58
|
+
const isoDateTime = now.toISOString();
|
|
59
|
+
|
|
60
|
+
const markdown = `# VERAX Scan Summary
|
|
61
|
+
|
|
62
|
+
**Date:** ${isoDateTime}
|
|
63
|
+
**URL scanned:** ${url}
|
|
64
|
+
**Source directory:** ${srcDir}
|
|
65
|
+
${framework ? `**Framework detected:** ${framework}\n` : ''}
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Result
|
|
70
|
+
|
|
71
|
+
${resultStatus}
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Decision-Level Problems
|
|
76
|
+
|
|
77
|
+
${problems.length > 0 ? buildProblemsMarkdown(problems, findingsList) : '*No problems detected.*'}
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Detailed Findings
|
|
82
|
+
|
|
83
|
+
${
|
|
84
|
+
findingsList.length === 0
|
|
85
|
+
? '*No silent failures detected.*'
|
|
86
|
+
: buildFindingsByCategoryMarkdown(findingsList)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Evidence
|
|
92
|
+
|
|
93
|
+
Key evidence for this run is in the \`EVIDENCE/\` folder:
|
|
94
|
+
${evidenceSection}
|
|
95
|
+
|
|
96
|
+
${
|
|
97
|
+
(findingsList.some(f => f.noElementScreenshot) || findingsList.some(f => f.whiteScreenshot))
|
|
98
|
+
? '\n**Evidence notes:** Some element screenshots may be missing or blank. This can happen if:\n- The element was not visible at interaction time\n- Screenshots captured before visual render stabilized\n- The clicked element was outside the viewport\n\nPage-level screenshots are still available for review.\n'
|
|
99
|
+
: ''
|
|
100
|
+
}
|
|
101
|
+
${fallbackEvidenceNote}
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Capabilities & Limitations
|
|
106
|
+
|
|
107
|
+
### ✅ VERAX Can Detect
|
|
108
|
+
|
|
109
|
+
- **Interactions that produce no observable effect** (buttons that don't work, links that don't navigate)
|
|
110
|
+
- **State mutations that don't update UI** (state changes in React/Vue that don't trigger re-renders)
|
|
111
|
+
- **Form submissions without feedback** (successful API calls with no success message or redirect)
|
|
112
|
+
- **Navigation without content rendering** (URL changes but page content doesn't load)
|
|
113
|
+
- **Missing UI feedback elements** (validation messages, toast notifications, loading states that never appear)
|
|
114
|
+
|
|
115
|
+
### ❌ VERAX Cannot Detect (Unsupported)
|
|
116
|
+
|
|
117
|
+
- **Async race conditions** - Concurrent operations are too complex to reliably detect without semantic analysis
|
|
118
|
+
- **Response body validation** - VERAX does not parse API responses to validate success semantics
|
|
119
|
+
- **Semantic correctness** - Cannot verify if an action is "correct" only that observable effects occurred
|
|
120
|
+
- **Performance issues** - Slow operations that eventually complete are not silent failures
|
|
121
|
+
- **Partial/delayed rendering** - Changes that occur after the observation window closes
|
|
122
|
+
|
|
123
|
+
### ⚠️ PARTIAL SUPPORT
|
|
124
|
+
|
|
125
|
+
- **Conditional rendering bugs** - Detected via state change + no UI change heuristic (confidence 60%)
|
|
126
|
+
- **Complex state dependencies** - Simple state mutations detected, but complex derived state may be missed
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Statistics
|
|
131
|
+
|
|
132
|
+
- **Expectations extracted:** ${learnData?.stats?.totalExpectations || 0}
|
|
133
|
+
- **Interactions attempted:** ${observeData?.stats?.attempted || 0}
|
|
134
|
+
- **Interactions observed:** ${observeData?.stats?.observed || 0}
|
|
135
|
+
- **Silent failures:** ${findings?.stats?.silentFailures || 0}
|
|
136
|
+
- **Coverage gaps:** ${findings?.stats?.coverageGaps || 0}
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Next Steps
|
|
141
|
+
|
|
142
|
+
1. **Review the findings** above for details on each silent failure
|
|
143
|
+
2. **Examine the evidence** in the \`EVIDENCE/\` folder (screenshots, DOM diffs)
|
|
144
|
+
3. **Categorize by priority** - Use Impact and Confidence levels to prioritize fixes
|
|
145
|
+
4. **Fix the code** to provide proper feedback or complete the intended action
|
|
146
|
+
5. **Re-run VERAX** after fixes to verify the silent failures are resolved
|
|
147
|
+
|
|
148
|
+
For more details, see the full machine-readable report in \`REPORT.json\`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
*Generated by VERAX — Silent Failure Detection Engine*
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
writeFileSync(summaryPath, markdown + '\n');
|
|
157
|
+
|
|
158
|
+
return summaryPath;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Detect framework from learn data
|
|
163
|
+
*/
|
|
164
|
+
function detectFramework(learnData) {
|
|
165
|
+
if (!learnData) return null;
|
|
166
|
+
|
|
167
|
+
const type = learnData.projectType || learnData.detectedFramework;
|
|
168
|
+
if (!type) return null;
|
|
169
|
+
|
|
170
|
+
// Map internal type names to human-friendly names
|
|
171
|
+
const typeMap = {
|
|
172
|
+
'nextjs': 'Next.js',
|
|
173
|
+
'nextjs_app_router': 'Next.js (App Router)',
|
|
174
|
+
'nextjs_pages_router': 'Next.js (Pages Router)',
|
|
175
|
+
'react': 'React',
|
|
176
|
+
'react_spa': 'React SPA',
|
|
177
|
+
'vue': 'Vue.js',
|
|
178
|
+
'angular': 'Angular',
|
|
179
|
+
'sveltekit': 'SvelteKit',
|
|
180
|
+
'static_html': 'Static HTML',
|
|
181
|
+
'astro': 'Astro'
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return typeMap[type] || type;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Determine overall result status
|
|
189
|
+
*/
|
|
190
|
+
function determineResultStatus(findings) {
|
|
191
|
+
if (!findings) {
|
|
192
|
+
return '⚠️ **Status:** Run incomplete or no findings available';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const silentFailures = findings.stats?.silentFailures || 0;
|
|
196
|
+
|
|
197
|
+
if (silentFailures === 0) {
|
|
198
|
+
return '✅ **Status:** No silent failures detected';
|
|
199
|
+
} else if (silentFailures === 1) {
|
|
200
|
+
return '❌ **Status:** 1 silent failure detected';
|
|
201
|
+
} else {
|
|
202
|
+
return `❌ **Status:** ${silentFailures} silent failures detected`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build problems markdown section
|
|
208
|
+
*/
|
|
209
|
+
function buildProblemsMarkdown(problems, findingsList) {
|
|
210
|
+
// Show max 5 problems
|
|
211
|
+
const topProblems = problems.slice(0, 5);
|
|
212
|
+
|
|
213
|
+
let result = '*High-level problems for decision makers. Each problem groups related findings from the same page/workflow.*\n\n';
|
|
214
|
+
|
|
215
|
+
topProblems.forEach((problem, i) => {
|
|
216
|
+
result += `### ${i + 1}. ${problem.title}\n\n`;
|
|
217
|
+
result += `**Page:** ${problem.page} \n`;
|
|
218
|
+
result += `**User Intent:** ${problem.userIntent} \n`;
|
|
219
|
+
result += `**Impact:** ${problem.impact} \n`;
|
|
220
|
+
result += `**Confidence:** ${Math.round(problem.confidence * 100)}% \n`;
|
|
221
|
+
result += `**Related Findings:** ${problem.findingCount}\n\n`;
|
|
222
|
+
|
|
223
|
+
result += `#### What the user tried:\n${problem.whatUserTried}\n\n`;
|
|
224
|
+
result += `#### What was expected:\n${problem.whatWasExpected}\n\n`;
|
|
225
|
+
result += `#### What actually happened:\n${problem.whatActuallyHappened}\n\n`;
|
|
226
|
+
result += `#### Why it matters:\n${problem.whyItMatters}\n\n`;
|
|
227
|
+
|
|
228
|
+
// Likely causes section
|
|
229
|
+
const problemCauses = [];
|
|
230
|
+
problem.findings.forEach(findingId => {
|
|
231
|
+
const finding = findingsList.find(f => f.id === findingId);
|
|
232
|
+
if (finding && finding.causes && finding.causes.length > 0) {
|
|
233
|
+
finding.causes.forEach(cause => {
|
|
234
|
+
if (!problemCauses.find(c => c.id === cause.id)) {
|
|
235
|
+
problemCauses.push(cause);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (problemCauses.length > 0) {
|
|
242
|
+
result += `#### Likely Causes:\n`;
|
|
243
|
+
problemCauses.forEach(cause => {
|
|
244
|
+
result += `- ${cause.statement}\n`;
|
|
245
|
+
});
|
|
246
|
+
result += '\n';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// List underlying findings
|
|
250
|
+
result += `<details>\n<summary><strong>View ${problem.findingCount} underlying finding(s)</strong></summary>\n\n`;
|
|
251
|
+
|
|
252
|
+
problem.findings.forEach(findingId => {
|
|
253
|
+
const finding = findingsList.find(f => f.id === findingId);
|
|
254
|
+
if (finding) {
|
|
255
|
+
result += `- **${finding.promise}** (${finding.source?.file || 'unknown'}:${finding.source?.line || '?'})\n`;
|
|
256
|
+
} else {
|
|
257
|
+
result += `- Finding ${findingId}\n`;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
result += `\n</details>\n\n`;
|
|
262
|
+
|
|
263
|
+
// Evidence section
|
|
264
|
+
if (problem.evidence && problem.evidence.length > 0) {
|
|
265
|
+
result += `**Evidence:**\n`;
|
|
266
|
+
problem.evidence.forEach(ev => {
|
|
267
|
+
if (ev.type === 'screenshot' && ev.path) {
|
|
268
|
+
result += `- Screenshot: [${ev.path}](${ev.path})\n`;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
result += '\n';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
result += '---\n\n';
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (problems.length > 5) {
|
|
278
|
+
result += `*Showing top 5 of ${problems.length} problems. See Detailed Findings section below for complete list.*\n\n`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Group findings by category and format as markdown
|
|
286
|
+
*/
|
|
287
|
+
function buildFindingsByCategoryMarkdown(findingsList) {
|
|
288
|
+
// Group by category
|
|
289
|
+
const byCategory = {
|
|
290
|
+
interaction: [],
|
|
291
|
+
navigation: [],
|
|
292
|
+
form: [],
|
|
293
|
+
state: [],
|
|
294
|
+
feedback: [],
|
|
295
|
+
other: []
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
findingsList.forEach(f => {
|
|
299
|
+
const cat = f.category || 'other';
|
|
300
|
+
byCategory[cat].push(f);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Build markdown
|
|
304
|
+
let result = '';
|
|
305
|
+
|
|
306
|
+
if (byCategory.interaction.length > 0) {
|
|
307
|
+
result += '### Interaction Issues\n\n';
|
|
308
|
+
byCategory.interaction.forEach((f, i) => {
|
|
309
|
+
result += `**${i + 1}. ${f.title}**\n\n${f.content}\n\n`;
|
|
310
|
+
});
|
|
311
|
+
result += '\n';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (byCategory.navigation.length > 0) {
|
|
315
|
+
result += '### Navigation Issues\n\n';
|
|
316
|
+
byCategory.navigation.forEach((f, i) => {
|
|
317
|
+
result += `**${i + 1}. ${f.title}**\n\n${f.content}\n\n`;
|
|
318
|
+
});
|
|
319
|
+
result += '\n';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (byCategory.form.length > 0) {
|
|
323
|
+
result += '### Form Issues\n\n';
|
|
324
|
+
byCategory.form.forEach((f, i) => {
|
|
325
|
+
result += `**${i + 1}. ${f.title}**\n\n${f.content}\n\n`;
|
|
326
|
+
});
|
|
327
|
+
result += '\n';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (byCategory.state.length > 0) {
|
|
331
|
+
result += '### State Management Issues\n\n*State changes that don\'t produce visible UI updates (likely reactive rendering bugs):*\n\n';
|
|
332
|
+
byCategory.state.forEach((f, i) => {
|
|
333
|
+
result += `**${i + 1}. ${f.title}**\n\n${f.content}\n\n`;
|
|
334
|
+
});
|
|
335
|
+
result += '\n';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (byCategory.feedback.length > 0) {
|
|
339
|
+
result += '### Missing Feedback\n\n*UI feedback elements that should appear but don\'t:*\n\n';
|
|
340
|
+
byCategory.feedback.forEach((f, i) => {
|
|
341
|
+
result += `**${i + 1}. ${f.title}**\n\n${f.content}\n\n`;
|
|
342
|
+
});
|
|
343
|
+
result += '\n';
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (byCategory.other.length > 0) {
|
|
347
|
+
result += '### Other Issues\n\n';
|
|
348
|
+
byCategory.other.forEach((f, i) => {
|
|
349
|
+
result += `**${i + 1}. ${f.title}**\n\n${f.content}\n\n`;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return result.trim();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Build human-readable findings list from findings object
|
|
358
|
+
*/
|
|
359
|
+
function buildFindingsList(findings, observeData) {
|
|
360
|
+
if (!findings || !findings.findings) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const observeMap = buildObserveMap(observeData);
|
|
365
|
+
|
|
366
|
+
return (findings.findings || []).slice(0, 100).map((finding) => {
|
|
367
|
+
const observation = observeMap.get(finding.id);
|
|
368
|
+
|
|
369
|
+
// Determine promise description based on promise.kind
|
|
370
|
+
let promise = 'Unknown interaction';
|
|
371
|
+
let category = 'other';
|
|
372
|
+
|
|
373
|
+
if (finding.promise?.kind === 'click') {
|
|
374
|
+
promise = `Click "${finding.promise.value}"`;
|
|
375
|
+
category = 'interaction';
|
|
376
|
+
} else if (finding.promise?.kind === 'submit') {
|
|
377
|
+
promise = `Submit form`;
|
|
378
|
+
category = 'form';
|
|
379
|
+
} else if (finding.promise?.kind === 'navigate') {
|
|
380
|
+
promise = `Navigate to ${finding.promise.value}`;
|
|
381
|
+
category = 'navigation';
|
|
382
|
+
} else if (finding.promise?.kind === 'state_mutation') {
|
|
383
|
+
// State mutations that don't produce observable UI changes
|
|
384
|
+
promise = `State change: ${finding.promise.value}`;
|
|
385
|
+
category = 'state';
|
|
386
|
+
} else if (finding.promise?.kind === 'validation') {
|
|
387
|
+
promise = `Validation feedback: ${finding.promise.value}`;
|
|
388
|
+
category = 'feedback';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let observed = 'No observable outcome';
|
|
392
|
+
if (observation && observation.signals) {
|
|
393
|
+
if (!observation.signals.navigationChanged && !observation.signals.domChanged && !observation.signals.feedbackSeen) {
|
|
394
|
+
observed = 'Nothing visible happened';
|
|
395
|
+
} else if (observation.signals.navigationChanged) {
|
|
396
|
+
observed = 'Navigation changed (but not as expected)';
|
|
397
|
+
} else if (observation.signals.domChanged) {
|
|
398
|
+
observed = 'Page updated (but not as expected)';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const noElementScreenshot = !finding.evidence?.some(e => e.path?.includes('_element'));
|
|
403
|
+
const whiteScreenshot = finding.evidence?.some(e => e.type === 'screenshot' && e.available === false);
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
id: finding.id,
|
|
407
|
+
title: promise,
|
|
408
|
+
category,
|
|
409
|
+
interaction: finding.interaction || {},
|
|
410
|
+
promise,
|
|
411
|
+
observed,
|
|
412
|
+
confidence: finding.confidence || 0.5,
|
|
413
|
+
impact: finding.impact || 'UNKNOWN',
|
|
414
|
+
source: finding.source,
|
|
415
|
+
noElementScreenshot,
|
|
416
|
+
whiteScreenshot,
|
|
417
|
+
content: `
|
|
418
|
+
**Code location:** ${finding.source?.file || 'unknown'}:${finding.source?.line || '?'}
|
|
419
|
+
|
|
420
|
+
**Promise:** ${promise}
|
|
421
|
+
**Observed:** ${observed}
|
|
422
|
+
**Confidence:** ${Math.round((finding.confidence || 0.5) * 100)}%
|
|
423
|
+
**Impact:** ${finding.impact || 'UNKNOWN'}
|
|
424
|
+
|
|
425
|
+
Evidence:
|
|
426
|
+
${
|
|
427
|
+
(finding.evidence || [])
|
|
428
|
+
.map(e => {
|
|
429
|
+
const evidencePath = e.path ? e.path.replace(/^evidence\//i, 'EVIDENCE/') : null;
|
|
430
|
+
if (e.type === 'screenshot') {
|
|
431
|
+
return `- Screenshot: [\`${evidencePath || 'unknown'}\`](${evidencePath || '#'})`;
|
|
432
|
+
} else if (e.type === 'dom-diff') {
|
|
433
|
+
return `- DOM diff: [\`${evidencePath || 'unknown'}\`](${evidencePath || '#'})`;
|
|
434
|
+
} else if (e.type === 'network') {
|
|
435
|
+
return `- Network events captured`;
|
|
436
|
+
} else if (e.type === 'console') {
|
|
437
|
+
return `- Console errors captured`;
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
})
|
|
441
|
+
.filter(Boolean)
|
|
442
|
+
.join('\n')
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
${
|
|
446
|
+
finding.causes && finding.causes.length > 0
|
|
447
|
+
? `Likely Causes:
|
|
448
|
+
${finding.causes.map(c => `- **${c.title}** (${c.id}): ${c.statement}`).join('\n')}`
|
|
449
|
+
: ''
|
|
450
|
+
}
|
|
451
|
+
`.trim()
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Build a map of observation IDs to observation data for quick lookup
|
|
458
|
+
*/
|
|
459
|
+
function buildObserveMap(observeData) {
|
|
460
|
+
const map = new Map();
|
|
461
|
+
|
|
462
|
+
if (observeData && observeData.observations) {
|
|
463
|
+
observeData.observations.forEach(obs => {
|
|
464
|
+
map.set(obs.id, obs);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return map;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Export for integration into detect phase
|
|
473
|
+
*/
|
|
474
|
+
export function integrateHumanSummary(runDir, url, srcDir, findings, learnData, observeData) {
|
|
475
|
+
// Summary generation is MANDATORY - throw if it fails
|
|
476
|
+
return writeSummaryMarkdown(runDir, url, srcDir, findings, learnData, observeData);
|
|
477
|
+
}
|