@veraxhq/verax 0.2.1 → 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.
Files changed (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /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
+ }