@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,113 @@
1
+ /**
2
+ * PHASE 21.11 — Truth Command
3
+ *
4
+ * `verax truth` - Shows truth certificate summary
5
+ */
6
+
7
+ import { findLatestRunId } from '../util/run-resolver.js';
8
+ import { generateTruthCertificate, loadTruthCertificate } from '../../verax/core/truth/truth.certificate.js';
9
+
10
+ /**
11
+ * Truth command
12
+ *
13
+ * @param {string} projectDir - Project directory
14
+ * @param {Object} options - Command options
15
+ */
16
+ export async function truthCommand(projectDir, options = {}) {
17
+ const { json = false, runId: runIdOpt = null } = options;
18
+
19
+ const runId = runIdOpt || findLatestRunId(projectDir);
20
+
21
+ if (!runId) {
22
+ if (json) {
23
+ console.log(JSON.stringify({
24
+ error: 'NO_RUNS_FOUND',
25
+ message: 'No runs found. Run a scan first.'
26
+ }, null, 2));
27
+ } else {
28
+ console.log('\n=== Truth Certificate ===\n');
29
+ console.log('Error: No runs found. Run a scan first.\n');
30
+ }
31
+ return;
32
+ }
33
+
34
+ // Try to load existing certificate, otherwise generate
35
+ let certificate = loadTruthCertificate(projectDir, runId);
36
+
37
+ if (!certificate) {
38
+ certificate = await generateTruthCertificate(projectDir, runId);
39
+ if (certificate) {
40
+ const { writeTruthCertificate } = await import('../../verax/core/truth/truth.certificate.js');
41
+ writeTruthCertificate(projectDir, runId, certificate);
42
+ }
43
+ }
44
+
45
+ if (!certificate) {
46
+ if (json) {
47
+ console.log(JSON.stringify({
48
+ error: 'CERTIFICATE_GENERATION_FAILED',
49
+ message: 'Failed to generate truth certificate'
50
+ }, null, 2));
51
+ } else {
52
+ console.log('\n=== Truth Certificate ===\n');
53
+ console.log('Error: Failed to generate truth certificate\n');
54
+ }
55
+ return;
56
+ }
57
+
58
+ if (json) {
59
+ console.log(JSON.stringify(certificate, null, 2));
60
+ } else {
61
+ console.log('\n=== Truth Certificate ===\n');
62
+ console.log(`Run ID: ${certificate.runId}`);
63
+ console.log(`URL: ${certificate.url || 'N/A'}`);
64
+ console.log(`Generated: ${certificate.generatedAt}`);
65
+
66
+ console.log('\nEvidence Law:');
67
+ console.log(` Status: ${certificate.evidenceLaw.status}`);
68
+ console.log(` Violated: ${certificate.evidenceLaw.violated ? 'YES' : 'NO'}`);
69
+
70
+ console.log('\nDeterminism:');
71
+ console.log(` Verdict: ${certificate.determinism.verdict}`);
72
+ console.log(` Message: ${certificate.determinism.message}`);
73
+
74
+ console.log('\nFailures:');
75
+ console.log(` Total: ${certificate.failures.total}`);
76
+ console.log(` Blocking: ${certificate.failures.blocking ? 'YES' : 'NO'}`);
77
+ console.log(` Degraded: ${certificate.failures.degraded ? 'YES' : 'NO'}`);
78
+ console.log(` By Severity: ${JSON.stringify(certificate.failures.bySeverity)}`);
79
+
80
+ console.log('\nGA:');
81
+ console.log(` Verdict: ${certificate.ga.verdict}`);
82
+ console.log(` Ready: ${certificate.ga.ready ? 'YES' : 'NO'}`);
83
+ console.log(` Blockers: ${certificate.ga.blockers}`);
84
+ console.log(` Warnings: ${certificate.ga.warnings}`);
85
+
86
+ console.log('\nSecurity:');
87
+ console.log(` Overall: ${certificate.security.overall}`);
88
+ console.log(` Secrets: ${certificate.security.secrets}`);
89
+ console.log(` Vulnerabilities: ${certificate.security.vulnerabilities}`);
90
+
91
+ console.log('\nPerformance:');
92
+ console.log(` Verdict: ${certificate.performance.verdict}`);
93
+ console.log(` OK: ${certificate.performance.ok ? 'YES' : 'NO'}`);
94
+ console.log(` Violations: ${certificate.performance.violations}`);
95
+
96
+ console.log('\nBaseline:');
97
+ console.log(` Hash: ${certificate.baseline.hash || 'N/A'}`);
98
+ console.log(` Frozen: ${certificate.baseline.frozen ? 'YES' : 'NO'}`);
99
+ console.log(` Version: ${certificate.baseline.version || 'N/A'}`);
100
+
101
+ console.log('\nProvenance:');
102
+ console.log(` Hash: ${certificate.provenance.hash || 'N/A'}`);
103
+ console.log(` Version: ${certificate.provenance.version || 'N/A'}`);
104
+
105
+ console.log('\nOverall Verdict:');
106
+ console.log(` Status: ${certificate.overallVerdict.status}`);
107
+ if (certificate.overallVerdict.reasons.length > 0) {
108
+ console.log(` Reasons: ${certificate.overallVerdict.reasons.join('; ')}`);
109
+ }
110
+ console.log('');
111
+ }
112
+ }
113
+
package/src/cli/entry.js CHANGED
@@ -4,7 +4,6 @@
4
4
  * VERAX CLI Entry Point
5
5
  *
6
6
  * Commands:
7
- * - verax (smart default with URL detection/prompting)
8
7
  * - verax run --url <url> (strict, non-interactive)
9
8
  * - verax inspect <runPath> (read and display run summary)
10
9
  *
@@ -13,25 +12,42 @@
13
12
  * - 2: internal crash
14
13
  * - 64: invalid CLI usage
15
14
  * - 65: invalid input data
15
+ *
16
+ * DESIGN: Lazy imports for heavy modules
17
+ * --version and --help are fast and don't load Playwright or observation-engine
18
+ * Heavy modules are only loaded when running actual commands (run, inspect, doctor)
16
19
  */
17
20
 
18
21
  import { fileURLToPath } from 'url';
19
22
  import { dirname, resolve } from 'path';
20
23
  import { readFileSync } from 'fs';
21
- import { defaultCommand } from './commands/default.js';
22
- import { runCommand } from './commands/run.js';
23
- import { inspectCommand } from './commands/inspect.js';
24
- import { doctorCommand } from './commands/doctor.js';
25
24
  import { getExitCode, UsageError } from './util/errors.js';
26
25
 
27
26
  const __filename = fileURLToPath(import.meta.url);
28
27
  const __dirname = dirname(__filename);
29
28
 
29
+ // Lazy loaders for heavy modules (loaded only when needed)
30
+ async function loadRunCommand() {
31
+ const mod = await import('./commands/run.js');
32
+ return mod.runCommand;
33
+ }
34
+
35
+ async function loadInspectCommand() {
36
+ const mod = await import('./commands/inspect.js');
37
+ return mod.inspectCommand;
38
+ }
39
+
40
+ async function loadDoctorCommand() {
41
+ const mod = await import('./commands/doctor.js');
42
+ return mod.doctorCommand;
43
+ }
44
+
30
45
  // Read package.json for version
31
46
  function getVersion() {
32
47
  try {
33
48
  const pkgPath = resolve(__dirname, '../../package.json');
34
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
49
+ // @ts-expect-error - readFileSync with encoding returns string
50
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
35
51
  return pkg.version;
36
52
  } catch {
37
53
  return 'unknown';
@@ -54,10 +70,10 @@ async function main() {
54
70
  process.exit(0);
55
71
  }
56
72
 
57
- // If no args, run default command
73
+ // If no args, show help (no interactive mode)
58
74
  if (args.length === 0) {
59
- await defaultCommand({});
60
- process.exit(0);
75
+ showHelp();
76
+ process.exit(64);
61
77
  }
62
78
 
63
79
  const command = args[0];
@@ -74,6 +90,7 @@ async function main() {
74
90
  throw new UsageError('run command requires --url <url> argument');
75
91
  }
76
92
 
93
+ const runCommand = await loadRunCommand();
77
94
  await runCommand({ url, src, out, json, verbose });
78
95
  process.exit(0);
79
96
  }
@@ -87,6 +104,7 @@ async function main() {
87
104
  const runPath = args[1];
88
105
  const json = args.includes('--json');
89
106
 
107
+ const inspectCommand = await loadInspectCommand();
90
108
  await inspectCommand(runPath, { json });
91
109
  process.exit(0);
92
110
  }
@@ -96,6 +114,7 @@ async function main() {
96
114
  const allowedFlags = new Set(['--json']);
97
115
  const extraFlags = args.slice(1).filter((a) => a.startsWith('-') && !allowedFlags.has(a));
98
116
  const json = args.includes('--json');
117
+ const doctorCommand = await loadDoctorCommand();
99
118
  await doctorCommand({ json, extraFlags });
100
119
  process.exit(0);
101
120
  }
@@ -106,16 +125,8 @@ async function main() {
106
125
  process.exit(0);
107
126
  }
108
127
 
109
- // Default command: smart scanning mode
110
- // Options can be passed as flags before/after the default command position
111
- const url = parseArg(args, '--url');
112
- const src = parseArg(args, '--src') || '.';
113
- const out = parseArg(args, '--out') || '.verax';
114
- const json = args.includes('--json');
115
- const verbose = args.includes('--verbose');
116
-
117
- await defaultCommand({ url, src, out, json, verbose });
118
- process.exit(0);
128
+ // Interactive mode removed
129
+ throw new UsageError('Interactive mode is disabled. Use: verax run --url <url>');
119
130
  } catch (error) {
120
131
  // Print error message
121
132
  if (error.message) {
@@ -135,7 +146,6 @@ verax ${version}
135
146
  VERAX — Silent failure detection for websites
136
147
 
137
148
  USAGE:
138
- verax [options] Smart mode (detects/prompts for URL)
139
149
  verax run --url <url> [options] Strict mode (non-interactive, CI-friendly)
140
150
  verax inspect <runPath> [--json] Inspect an existing run
141
151
  verax doctor [--json] Diagnose local environment
@@ -0,0 +1,179 @@
1
+ /**
2
+ * PHASE 20 — Angular Component Extractor
3
+ *
4
+ * Extracts component metadata, template, and class content from Angular TypeScript files.
5
+ * Handles @Component decorators, template files, and component class methods.
6
+ * Deterministic and robust (no external runtime execution).
7
+ */
8
+
9
+ /**
10
+ * PHASE 20: Extract Angular component metadata
11
+ *
12
+ * @param {string} content - Full .ts file content
13
+ * @param {string} filePath - Path to the .ts file (for context)
14
+ * @param {string} projectRoot - Project root directory
15
+ * @returns {Object} { componentClass: {content, startLine}, template: {content, path, isInline}, decorator: {content, startLine} }
16
+ */
17
+ export function extractAngularComponent(content, filePath, projectRoot) {
18
+ const result = {
19
+ componentClass: null,
20
+ template: null,
21
+ decorator: null,
22
+ };
23
+
24
+ try {
25
+ // Extract @Component decorator
26
+ const decoratorRegex = /@Component\s*\(\s*\{([\s\S]*?)\}\s*\)/;
27
+ const decoratorMatch = content.match(decoratorRegex);
28
+
29
+ if (decoratorMatch) {
30
+ const _decoratorContent = decoratorMatch[0];
31
+ const decoratorConfig = decoratorMatch[1];
32
+ const beforeDecorator = content.substring(0, decoratorMatch.index);
33
+ const decoratorStartLine = (beforeDecorator.match(/\n/g) || []).length + 1;
34
+
35
+ result.decorator = {
36
+ content: decoratorConfig,
37
+ startLine: decoratorStartLine,
38
+ };
39
+
40
+ // Extract template (inline or external file)
41
+ const templateMatch = decoratorConfig.match(/template\s*:\s*['"`]([\s\S]*?)['"`]/);
42
+ const templateUrlMatch = decoratorConfig.match(/templateUrl\s*:\s*['"`]([^'"`]+)['"`]/);
43
+
44
+ if (templateMatch) {
45
+ // Inline template
46
+ result.template = {
47
+ content: templateMatch[1],
48
+ path: filePath,
49
+ isInline: true,
50
+ };
51
+ } else if (templateUrlMatch) {
52
+ // External template file
53
+ const templateUrl = templateUrlMatch[1];
54
+ const templatePath = resolveTemplatePath(templateUrl, filePath, projectRoot);
55
+ result.template = {
56
+ path: templatePath,
57
+ isInline: false,
58
+ };
59
+ }
60
+ }
61
+
62
+ // Extract component class
63
+ const classRegex = /export\s+class\s+(\w+)\s*(?:extends\s+\w+)?\s*\{([\s\S]*?)\n\}/;
64
+ const classMatch = content.match(classRegex);
65
+
66
+ if (classMatch) {
67
+ const beforeClass = content.substring(0, classMatch.index);
68
+ const classStartLine = (beforeClass.match(/\n/g) || []).length + 1;
69
+
70
+ result.componentClass = {
71
+ content: classMatch[2],
72
+ className: classMatch[1],
73
+ startLine: classStartLine,
74
+ };
75
+ }
76
+ } catch (error) {
77
+ // Skip if extraction fails
78
+ }
79
+
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Resolve template path from templateUrl
85
+ *
86
+ * @param {string} templateUrl - Template URL from decorator
87
+ * @param {string} componentPath - Path to component file
88
+ * @param {string} projectRoot - Project root directory
89
+ * @returns {string} Resolved template path
90
+ */
91
+ function resolveTemplatePath(templateUrl, componentPath, projectRoot) {
92
+ const { join: _join, dirname, resolve } = require('path');
93
+
94
+ if (templateUrl.startsWith('./') || templateUrl.startsWith('../')) {
95
+ // Relative path
96
+ return resolve(dirname(componentPath), templateUrl);
97
+ } else {
98
+ // Absolute path from project root
99
+ return resolve(projectRoot, templateUrl);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Extract template bindings from Angular template
105
+ * Detects event handlers, property bindings, and structural directives
106
+ *
107
+ * @param {string} templateContent - Template content
108
+ * @returns {Object} { eventHandlers: [], propertyBindings: [], structuralDirectives: [] }
109
+ */
110
+ export function extractTemplateBindings(templateContent) {
111
+ const eventHandlers = [];
112
+ const propertyBindings = [];
113
+ const structuralDirectives = [];
114
+
115
+ // Extract event handlers: (click)="handler()", (submit)="onSubmit()"
116
+ const eventHandlerRegex = /\((\w+)\)\s*=\s*["']([^"']+)["']/g;
117
+ let handlerMatch;
118
+ while ((handlerMatch = eventHandlerRegex.exec(templateContent)) !== null) {
119
+ eventHandlers.push({
120
+ event: handlerMatch[1],
121
+ handler: handlerMatch[2],
122
+ line: (templateContent.substring(0, handlerMatch.index).match(/\n/g) || []).length + 1,
123
+ });
124
+ }
125
+
126
+ // Extract property bindings: [property]="value", [disabled]="isDisabled"
127
+ const propertyBindingRegex = /\[(\w+)\]\s*=\s*["']([^"']+)["']/g;
128
+ let bindingMatch;
129
+ while ((bindingMatch = propertyBindingRegex.exec(templateContent)) !== null) {
130
+ propertyBindings.push({
131
+ property: bindingMatch[1],
132
+ value: bindingMatch[2],
133
+ line: (templateContent.substring(0, bindingMatch.index).match(/\n/g) || []).length + 1,
134
+ });
135
+ }
136
+
137
+ // Extract structural directives: *ngIf="condition", *ngFor="item of items"
138
+ const structuralDirectiveRegex = /\*ng(If|For|Switch)\s*=\s*["']([^"']+)["']/g;
139
+ let directiveMatch;
140
+ while ((directiveMatch = structuralDirectiveRegex.exec(templateContent)) !== null) {
141
+ structuralDirectives.push({
142
+ directive: directiveMatch[1],
143
+ expression: directiveMatch[2],
144
+ line: (templateContent.substring(0, directiveMatch.index).match(/\n/g) || []).length + 1,
145
+ });
146
+ }
147
+
148
+ return {
149
+ eventHandlers,
150
+ propertyBindings,
151
+ structuralDirectives,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Map template handlers to component class methods
157
+ *
158
+ * @param {Array} eventHandlers - Event handlers from template
159
+ * @param {string} classContent - Component class content
160
+ * @returns {Array} Mapped handlers with method references
161
+ */
162
+ export function mapTemplateHandlersToClass(eventHandlers, classContent) {
163
+ return eventHandlers.map(handler => {
164
+ // Extract method name from handler expression
165
+ const methodName = handler.handler.split('(')[0].trim();
166
+
167
+ // Try to find method definition in class
168
+ const methodRegex = new RegExp(`(?:public|private|protected)?\\s*(?:async\\s+)?${methodName}\\s*\\(`, 'g');
169
+ const methodMatch = methodRegex.exec(classContent);
170
+
171
+ return {
172
+ ...handler,
173
+ methodName,
174
+ methodFound: !!methodMatch,
175
+ methodLine: methodMatch ? (classContent.substring(0, methodMatch.index).match(/\n/g) || []).length + 1 : null,
176
+ };
177
+ });
178
+ }
179
+
@@ -0,0 +1,141 @@
1
+ /**
2
+ * PHASE 20 — Angular Navigation Detector
3
+ *
4
+ * Detects navigation promises in Angular applications:
5
+ * - routerLink directive in templates
6
+ * - Router.navigate() calls in component methods
7
+ * - ActivatedRoute navigation
8
+ */
9
+
10
+ import { extractAngularComponent, extractTemplateBindings, mapTemplateHandlersToClass } from './angular-component-extractor.js'; // eslint-disable-line no-unused-vars
11
+ import { parse } from '@babel/parser';
12
+ import traverse from '@babel/traverse';
13
+ import { readFileSync, existsSync } from 'fs';
14
+
15
+ /**
16
+ * Detect navigation promises in Angular component
17
+ *
18
+ * @param {string} filePath - Path to .ts file
19
+ * @param {string} content - Full file content
20
+ * @param {string} projectRoot - Project root directory
21
+ * @returns {Array} Array of navigation expectations
22
+ */
23
+ export function detectAngularNavigation(filePath, content, projectRoot) {
24
+ const expectations = [];
25
+
26
+ try {
27
+ const component = extractAngularComponent(content, filePath, projectRoot);
28
+
29
+ // Extract navigation from template (routerLink)
30
+ if (component.template) {
31
+ let templateContent = null;
32
+
33
+ if (component.template.isInline) {
34
+ templateContent = component.template.content;
35
+ } else if (existsSync(component.template.path)) {
36
+ templateContent = readFileSync(component.template.path, 'utf8');
37
+ }
38
+
39
+ if (templateContent) {
40
+ const _templateBindings = extractTemplateBindings(templateContent);
41
+
42
+ // Extract routerLink directives
43
+ const routerLinkRegex = /routerLink\s*=\s*["']([^"']+)["']/g;
44
+ let linkMatch;
45
+ while ((linkMatch = routerLinkRegex.exec(templateContent)) !== null) {
46
+ const href = linkMatch[1];
47
+ // Skip external links and hash-only links
48
+ if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#')) {
49
+ continue;
50
+ }
51
+
52
+ const beforeMatch = templateContent.substring(0, linkMatch.index);
53
+ const line = (beforeMatch.match(/\n/g) || []).length + 1;
54
+
55
+ expectations.push({
56
+ type: 'navigation',
57
+ target: href,
58
+ context: 'template',
59
+ sourceRef: {
60
+ file: component.template.isInline ? filePath : component.template.path,
61
+ line,
62
+ snippet: linkMatch[0],
63
+ },
64
+ proof: 'PROVEN_EXPECTATION',
65
+ metadata: {
66
+ navigationType: 'routerLink',
67
+ },
68
+ });
69
+ }
70
+ }
71
+ }
72
+
73
+ // Extract navigation from component class (Router.navigate())
74
+ if (component.componentClass && component.componentClass.content) {
75
+ try {
76
+ const ast = parse(component.componentClass.content, {
77
+ sourceType: 'module',
78
+ plugins: ['typescript', 'decorators-legacy', 'classProperties'],
79
+ });
80
+
81
+ traverse.default(ast, {
82
+ CallExpression(path) {
83
+ const { node } = path;
84
+
85
+ // Detect router.navigate() calls
86
+ if (
87
+ node.callee.type === 'MemberExpression' &&
88
+ node.callee.property.name === 'navigate' &&
89
+ node.arguments.length > 0
90
+ ) {
91
+ const arg = node.arguments[0];
92
+ let target = null;
93
+
94
+ if (arg.type === 'StringLiteral') {
95
+ target = arg.value;
96
+ } else if (arg.type === 'ArrayExpression' && arg.elements.length > 0) {
97
+ // Router.navigate(['/path']) or Router.navigate(['/path', param])
98
+ const firstElement = arg.elements[0];
99
+ if (firstElement.type === 'StringLiteral') {
100
+ target = firstElement.value;
101
+ }
102
+ } else if (arg.type === 'TemplateLiteral' && arg.quasis.length === 1) {
103
+ target = arg.quasis[0].value.raw;
104
+ }
105
+
106
+ if (target && !target.startsWith('http://') && !target.startsWith('https://') && !target.startsWith('#')) {
107
+ const location = node.loc;
108
+ const line = component.componentClass.startLine + (location ? location.start.line - 1 : 0);
109
+
110
+ expectations.push({
111
+ type: 'navigation',
112
+ target,
113
+ context: 'router-navigate',
114
+ sourceRef: {
115
+ file: filePath,
116
+ line,
117
+ snippet: component.componentClass.content.substring(
118
+ node.start - (ast.program.body[0]?.start || 0),
119
+ node.end - (ast.program.body[0]?.start || 0)
120
+ ),
121
+ },
122
+ proof: arg.type === 'StringLiteral' ? 'PROVEN_EXPECTATION' : 'LIKELY_EXPECTATION',
123
+ metadata: {
124
+ navigationType: 'router-navigate',
125
+ },
126
+ });
127
+ }
128
+ }
129
+ },
130
+ });
131
+ } catch (parseError) {
132
+ // Skip if parsing fails
133
+ }
134
+ }
135
+ } catch (error) {
136
+ // Skip if extraction fails
137
+ }
138
+
139
+ return expectations;
140
+ }
141
+