@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,82 @@
1
+ /**
2
+ * PHASE 21.3 — Safety Observer
3
+ *
4
+ * Responsibilities:
5
+ * - Network interception (cross-origin blocking, write method blocking)
6
+ * - NO file I/O
7
+ * - NO side effects outside its scope
8
+ */
9
+
10
+ /**
11
+ * Setup network interception firewall
12
+ *
13
+ * @param {Object} context - Observe context
14
+ * @returns {Promise<void>}
15
+ */
16
+ export async function setupNetworkInterception(context) {
17
+ const { page, baseOrigin, safetyFlags, silenceTracker, blockedNetworkWrites, blockedCrossOrigin } = context;
18
+ const { allowWrites = false, allowCrossOrigin = false } = safetyFlags;
19
+
20
+ await page.route('**/*', (route) => {
21
+ const request = route.request();
22
+ const method = request.method();
23
+ const requestUrl = request.url();
24
+ const resourceType = request.resourceType();
25
+
26
+ // Check cross-origin blocking (skip for file:// URLs)
27
+ if (!allowCrossOrigin && !requestUrl.startsWith('file://')) {
28
+ try {
29
+ const reqOrigin = new URL(requestUrl).origin;
30
+ if (reqOrigin !== baseOrigin) {
31
+ blockedCrossOrigin.push({
32
+ url: requestUrl,
33
+ origin: reqOrigin,
34
+ method,
35
+ resourceType,
36
+ timestamp: Date.now()
37
+ });
38
+
39
+ silenceTracker.record({
40
+ scope: 'safety',
41
+ reason: 'cross_origin_blocked',
42
+ description: `Cross-origin request blocked: ${method} ${requestUrl}`,
43
+ context: { url: requestUrl, origin: reqOrigin, method, baseOrigin },
44
+ impact: 'request_blocked'
45
+ });
46
+
47
+ return route.abort('blockedbyclient');
48
+ }
49
+ } catch (e) {
50
+ // Invalid URL, allow and let browser handle
51
+ }
52
+ }
53
+
54
+ // Check write method blocking
55
+ if (!allowWrites && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
56
+ // Check if it's a GraphQL mutation (best-effort)
57
+ const isGraphQLMutation = requestUrl.includes('/graphql') && method === 'POST';
58
+
59
+ blockedNetworkWrites.push({
60
+ url: requestUrl,
61
+ method,
62
+ resourceType,
63
+ isGraphQLMutation,
64
+ timestamp: Date.now()
65
+ });
66
+
67
+ silenceTracker.record({
68
+ scope: 'safety',
69
+ reason: 'blocked_network_write',
70
+ description: `Network write blocked: ${method} ${requestUrl}${isGraphQLMutation ? ' (GraphQL mutation)' : ''}`,
71
+ context: { url: requestUrl, method, resourceType, isGraphQLMutation },
72
+ impact: 'write_blocked'
73
+ });
74
+
75
+ return route.abort('blockedbyclient');
76
+ }
77
+
78
+ // Allow request
79
+ route.continue();
80
+ });
81
+ }
82
+
@@ -0,0 +1,99 @@
1
+ /**
2
+ * PHASE 21.3 — UI Feedback Observer
3
+ *
4
+ * Responsibilities:
5
+ * - DOM mutation observation
6
+ * - Loading / disabled / feedback signals
7
+ * - UI settle signals (NO adaptive waiting - that's in settle.js)
8
+ *
9
+ * NO file I/O
10
+ * NO side effects outside its scope
11
+ */
12
+
13
+ import { UISignalSensor } from '../ui-signal-sensor.js';
14
+ import { captureDomSignature } from '../dom-signature.js';
15
+
16
+ /**
17
+ * Observe UI feedback and DOM state on current page
18
+ *
19
+ * @param {Object} context - Observe context
20
+ * @param {Object} _runState - Current run state
21
+ * @returns {Promise<Array<Object>>} Array of UI feedback observations
22
+ */
23
+ export async function observe(context, _runState) {
24
+ const { page, currentUrl, timestamp } = context;
25
+ const observations = [];
26
+
27
+ try {
28
+ // Capture current UI signals
29
+ const uiSignalSensor = new UISignalSensor();
30
+ const uiSignals = await uiSignalSensor.snapshot(page);
31
+
32
+ // Capture DOM signature for mutation tracking
33
+ const domSignature = await captureDomSignature(page);
34
+
35
+ // Create observation for UI signals
36
+ observations.push({
37
+ type: 'ui_feedback',
38
+ scope: 'page',
39
+ data: {
40
+ hasLoadingIndicator: uiSignals.hasLoadingIndicator,
41
+ hasDialog: uiSignals.hasDialog,
42
+ hasErrorSignal: uiSignals.hasErrorSignal,
43
+ hasStatusSignal: uiSignals.hasStatusSignal,
44
+ hasLiveRegion: uiSignals.hasLiveRegion,
45
+ validationFeedbackDetected: uiSignals.validationFeedbackDetected,
46
+ disabledElementsCount: uiSignals.disabledElements?.length || 0,
47
+ explanation: uiSignals.explanation || []
48
+ },
49
+ timestamp,
50
+ url: currentUrl
51
+ });
52
+
53
+ // Create observation for DOM state
54
+ observations.push({
55
+ type: 'dom_state',
56
+ scope: 'page',
57
+ data: {
58
+ domHash: domSignature,
59
+ hasDom: !!domSignature
60
+ },
61
+ timestamp,
62
+ url: currentUrl
63
+ });
64
+
65
+ // If there are loading indicators, create specific observation
66
+ if (uiSignals.hasLoadingIndicator) {
67
+ observations.push({
68
+ type: 'ui_loading',
69
+ scope: 'page',
70
+ data: {
71
+ loading: true,
72
+ explanation: uiSignals.explanation?.filter(e => e.includes('loading') || e.includes('busy')) || []
73
+ },
74
+ timestamp,
75
+ url: currentUrl
76
+ });
77
+ }
78
+
79
+ // If there are disabled elements, create observation
80
+ if (uiSignals.disabledElements && uiSignals.disabledElements.length > 0) {
81
+ observations.push({
82
+ type: 'ui_disabled',
83
+ scope: 'page',
84
+ data: {
85
+ disabledCount: uiSignals.disabledElements.length,
86
+ disabledElements: uiSignals.disabledElements.slice(0, 10) // Limit to 10
87
+ },
88
+ timestamp,
89
+ url: currentUrl
90
+ });
91
+ }
92
+ } catch (error) {
93
+ // Propagate error - no silent catch
94
+ throw new Error(`UI feedback observer failed: ${error.message}`);
95
+ }
96
+
97
+ return observations;
98
+ }
99
+
@@ -0,0 +1,138 @@
1
+ /**
2
+ * PAGE TRAVERSAL ENGINE
3
+ *
4
+ * Manages page frontier, link discovery, and page-to-page navigation.
5
+ */
6
+
7
+ import { isExternalUrl } from './domain-boundary.js';
8
+
9
+ /**
10
+ * Discover links on current page
11
+ *
12
+ * @param {Object} page - Playwright page
13
+ * @param {string} baseOrigin - Base origin URL
14
+ * @param {Object} silenceTracker - Silence tracker
15
+ * @returns {Promise<Array>}
16
+ */
17
+ export async function discoverPageLinks(page, baseOrigin, silenceTracker) {
18
+ try {
19
+ // Discover all links on the page
20
+ const links = await page.locator('a[href]').all();
21
+ const linkData = [];
22
+
23
+ for (const link of links) {
24
+ const href = await link.getAttribute('href');
25
+ if (href) {
26
+ linkData.push({ href });
27
+ }
28
+ }
29
+
30
+ // Filter to same-origin links
31
+ const sameOriginLinks = linkData.filter(link => {
32
+ if (!link.href) return false;
33
+ return !isExternalUrl(link.href, baseOrigin);
34
+ });
35
+
36
+ return sameOriginLinks;
37
+ } catch (error) {
38
+ silenceTracker.record('link_discovery_error');
39
+ return [];
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get next page URL from frontier
45
+ *
46
+ * @param {Object} frontier - Page frontier object
47
+ * @returns {string|null}
48
+ */
49
+ export function getNextPageUrl(frontier) {
50
+ if (!frontier || !frontier.queue || frontier.queue.length === 0) {
51
+ return null;
52
+ }
53
+
54
+ const nextUrl = frontier.queue[0];
55
+ return nextUrl;
56
+ }
57
+
58
+ /**
59
+ * Mark page as visited in frontier
60
+ *
61
+ * @param {Object} frontier - Page frontier object
62
+ * @param {string} url - URL that was visited
63
+ */
64
+ export function markPageVisited(frontier, url) {
65
+ if (!frontier) return;
66
+
67
+ // Remove from queue
68
+ if (frontier.queue && frontier.queue.length > 0) {
69
+ frontier.queue = frontier.queue.filter(u => u !== url);
70
+ }
71
+
72
+ // Add to visited
73
+ if (!frontier.visited) {
74
+ frontier.visited = [];
75
+ }
76
+ if (!frontier.visited.includes(url)) {
77
+ frontier.visited.push(url);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Add discovered links to frontier queue
83
+ *
84
+ * @param {Object} frontier - Page frontier object
85
+ * @param {Array} links - Links to add
86
+ */
87
+ export function addLinksToFrontier(frontier, links) {
88
+ if (!frontier || !links || links.length === 0) return;
89
+
90
+ if (!frontier.queue) {
91
+ frontier.queue = [];
92
+ }
93
+
94
+ for (const link of links) {
95
+ if (link.href && !frontier.queue.includes(link.href) &&
96
+ (!frontier.visited || !frontier.visited.includes(link.href))) {
97
+ frontier.queue.push(link.href);
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Check if page limit has been reached
104
+ *
105
+ * @param {number} pagesVisited - Number of pages visited
106
+ * @param {number} pageLimit - Maximum pages to visit
107
+ * @returns {boolean}
108
+ */
109
+ export function isPageLimitReached(pagesVisited, pageLimit) {
110
+ return pagesVisited >= pageLimit;
111
+ }
112
+
113
+ /**
114
+ * Cap frontier to maximum size
115
+ *
116
+ * @param {Object} frontier - Page frontier object
117
+ * @param {number} maxFrontierSize - Maximum frontier queue size
118
+ */
119
+ export function capFrontier(frontier, maxFrontierSize) {
120
+ if (!frontier || !frontier.queue) return;
121
+
122
+ if (frontier.queue.length > maxFrontierSize) {
123
+ frontier.queue = frontier.queue.slice(0, maxFrontierSize);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Initialize frontier for traversal
129
+ *
130
+ * @param {string} baseUrl - Starting URL
131
+ * @returns {Object}
132
+ */
133
+ export function initializeFrontier(baseUrl) {
134
+ return {
135
+ queue: [baseUrl],
136
+ visited: []
137
+ };
138
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * SNAPSHOT OPERATIONS MODULE
3
+ *
4
+ * Encapsulates snapshot loading, comparison, building, and saving logic.
5
+ * Extracted from observe/index.js (STAGE D2.1).
6
+ *
7
+ * Preserves 100% of original behavior:
8
+ * - Same incremental mode conditions
9
+ * - Same snapshot JSON shape
10
+ * - Same load/save timing
11
+ * - Same trace filtering
12
+ */
13
+
14
+ import {
15
+ loadPreviousSnapshot,
16
+ buildSnapshot,
17
+ compareSnapshots,
18
+ saveSnapshot
19
+ } from '../core/incremental-store.js';
20
+
21
+ /**
22
+ * Initialize snapshot operations at the beginning of observation
23
+ *
24
+ * Loads previous snapshot, compares with baseline, determines incremental mode.
25
+ * Extracted from lines 108-111 of observe/index.js
26
+ *
27
+ * @param {string} projectDir - Project directory
28
+ * @param {Object} manifest - Loaded manifest object
29
+ * @returns {Promise<{oldSnapshot: Object|null, snapshotDiff: Object|null, incrementalMode: boolean}>}
30
+ */
31
+ export async function initializeSnapshot(projectDir, manifest) {
32
+ let oldSnapshot = null;
33
+ let snapshotDiff = null;
34
+ let incrementalMode = false;
35
+
36
+ // SCALE INTELLIGENCE: Load previous snapshot for incremental mode
37
+ oldSnapshot = loadPreviousSnapshot(projectDir);
38
+ if (oldSnapshot) {
39
+ const currentSnapshot = buildSnapshot(manifest, []);
40
+ snapshotDiff = compareSnapshots(oldSnapshot, currentSnapshot);
41
+ incrementalMode = !snapshotDiff.hasChanges; // Use incremental if nothing changed
42
+ }
43
+
44
+ return {
45
+ oldSnapshot,
46
+ snapshotDiff,
47
+ incrementalMode
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Finalize snapshot operations at the end of observation
53
+ *
54
+ * Builds current snapshot from observed interactions, saves for next run,
55
+ * and creates incremental metadata for observation object.
56
+ * Extracted from lines 789-811 of observe/index.js
57
+ *
58
+ * @param {Object} manifest - Loaded manifest object (or null if not provided)
59
+ * @param {Array} traces - All collected traces
60
+ * @param {Array} skippedInteractions - Array of skipped interactions
61
+ * @param {boolean} incrementalMode - Whether incremental mode was enabled
62
+ * @param {Object} snapshotDiff - Snapshot diff from initialization (or null)
63
+ * @param {string} projectDir - Project directory
64
+ * @param {string} runId - Run identifier
65
+ * @param {string} url - Initial URL (fallback for trace.before.url)
66
+ * @returns {Promise<{enabled: boolean, snapshotDiff: Object, skippedInteractionsCount: number}|null>}
67
+ */
68
+ export async function finalizeSnapshot(manifest, traces, skippedInteractions, incrementalMode, snapshotDiff, projectDir, runId, url) {
69
+ let incrementalMetadata = null;
70
+
71
+ // SCALE INTELLIGENCE: Save snapshot for next incremental run
72
+ if (manifest) {
73
+ // Build snapshot from current run (extract interactions from traces)
74
+ const observedInteractions = traces
75
+ .filter(t => t.interaction && !t.incremental)
76
+ .map(t => ({
77
+ type: t.interaction?.type,
78
+ selector: t.interaction?.selector,
79
+ url: t.before?.url || url
80
+ }));
81
+
82
+ const currentSnapshot = buildSnapshot(manifest, observedInteractions);
83
+ saveSnapshot(projectDir, currentSnapshot, runId);
84
+
85
+ // Add incremental mode metadata to observation
86
+ incrementalMetadata = {
87
+ enabled: incrementalMode,
88
+ snapshotDiff: snapshotDiff,
89
+ skippedInteractionsCount: skippedInteractions.filter(s => s.reason === 'incremental_unchanged').length
90
+ };
91
+ }
92
+
93
+ return incrementalMetadata;
94
+ }