@avi770/testteam 1.2.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 (325) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/agents/01-analyst.ts +100 -0
  5. package/agents/02-seed-architect.ts +59 -0
  6. package/agents/03-test-generator.ts +191 -0
  7. package/agents/04-unit-runner.ts +160 -0
  8. package/agents/05-browser-crawler.ts +790 -0
  9. package/agents/06-api-exerciser.ts +311 -0
  10. package/agents/07-security-scout.ts +188 -0
  11. package/agents/08-a11y-guardian.ts +212 -0
  12. package/agents/09-healer.ts +228 -0
  13. package/agents/10-reporter.ts +266 -0
  14. package/agents/11-fixer.ts +253 -0
  15. package/agents/12-ux-inspector.ts +444 -0
  16. package/agents/13-performance-profiler.ts +271 -0
  17. package/agents/14-data-integrity-auditor.ts +417 -0
  18. package/agents/15-regression-sentinel.ts +307 -0
  19. package/agents/16-chaos-agent.ts +228 -0
  20. package/agents/17-documentation-validator.ts +266 -0
  21. package/agents/18-integration-watchdog.ts +178 -0
  22. package/agents/19-tenant-isolation-auditor.ts +199 -0
  23. package/agents/20-workflow-completion-tester.ts +203 -0
  24. package/agents/21-state-session-tester.ts +262 -0
  25. package/agents/22-email-notification-verifier.ts +244 -0
  26. package/agents/23-migration-tester.ts +80 -0
  27. package/agents/__tests__/01-analyst.test.ts +188 -0
  28. package/agents/__tests__/02-seed-architect.test.ts +152 -0
  29. package/agents/__tests__/03-test-generator-full.test.ts +321 -0
  30. package/agents/__tests__/03-test-generator.test.ts +318 -0
  31. package/agents/__tests__/04-unit-runner.test.ts +320 -0
  32. package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
  33. package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
  34. package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
  35. package/agents/__tests__/05-browser-crawler.test.ts +518 -0
  36. package/agents/__tests__/06-api-exerciser.test.ts +619 -0
  37. package/agents/__tests__/07-security-scout.test.ts +382 -0
  38. package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
  39. package/agents/__tests__/09-healer.test.ts +384 -0
  40. package/agents/__tests__/10-reporter.test.ts +366 -0
  41. package/agents/__tests__/11-fixer.test.ts +406 -0
  42. package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
  43. package/agents/__tests__/12-ux-inspector.test.ts +443 -0
  44. package/agents/__tests__/13-performance-profiler.test.ts +411 -0
  45. package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
  46. package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
  47. package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
  48. package/agents/__tests__/16-chaos-agent.test.ts +427 -0
  49. package/agents/__tests__/17-documentation-validator.test.ts +402 -0
  50. package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
  51. package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
  52. package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
  53. package/agents/__tests__/21-state-session-tester.test.ts +374 -0
  54. package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
  55. package/agents/__tests__/23-migration-tester.test.ts +145 -0
  56. package/agents/__tests__/base-agent.test.ts +188 -0
  57. package/agents/__tests__/registry.test.ts +218 -0
  58. package/agents/base-agent.ts +77 -0
  59. package/agents/registry.ts +136 -0
  60. package/baselines/api-schemas/.gitkeep +0 -0
  61. package/baselines/performance/.gitkeep +0 -0
  62. package/baselines/screenshots/.gitkeep +0 -0
  63. package/bin/testteam.js +10 -0
  64. package/core/__tests__/ci-output.test.ts +430 -0
  65. package/core/__tests__/cli.test.ts +387 -0
  66. package/core/__tests__/config.test.ts +78 -0
  67. package/core/__tests__/cost-tracker.test.ts +158 -0
  68. package/core/__tests__/evidence.test.ts +265 -0
  69. package/core/__tests__/fix-loop.test.ts +210 -0
  70. package/core/__tests__/health-check.test.ts +44 -0
  71. package/core/__tests__/init.test.ts +609 -0
  72. package/core/__tests__/integration.test.ts +204 -0
  73. package/core/__tests__/license-gen.test.ts +227 -0
  74. package/core/__tests__/license.test.ts +326 -0
  75. package/core/__tests__/multi-browser.test.ts +278 -0
  76. package/core/__tests__/orchestrator.test.ts +519 -0
  77. package/core/__tests__/phase-gate.test.ts +43 -0
  78. package/core/__tests__/report-html.test.ts +398 -0
  79. package/core/__tests__/report-upload.test.ts +325 -0
  80. package/core/__tests__/run-counter.test.ts +234 -0
  81. package/core/ci-output.ts +240 -0
  82. package/core/cli.ts +232 -0
  83. package/core/config.ts +178 -0
  84. package/core/cost-tracker.ts +59 -0
  85. package/core/evidence.ts +132 -0
  86. package/core/fix-loop.ts +85 -0
  87. package/core/health-check.ts +54 -0
  88. package/core/init.ts +546 -0
  89. package/core/license-gen.ts +212 -0
  90. package/core/license.ts +211 -0
  91. package/core/messages.ts +67 -0
  92. package/core/multi-browser.ts +136 -0
  93. package/core/orchestrator.ts +354 -0
  94. package/core/phase-gate.ts +55 -0
  95. package/core/report-html.ts +657 -0
  96. package/core/report-upload.ts +188 -0
  97. package/core/run-counter.ts +175 -0
  98. package/core/types.ts +57 -0
  99. package/dist/agents/01-analyst.d.ts +11 -0
  100. package/dist/agents/01-analyst.d.ts.map +1 -0
  101. package/dist/agents/01-analyst.js +75 -0
  102. package/dist/agents/01-analyst.js.map +1 -0
  103. package/dist/agents/02-seed-architect.d.ts +11 -0
  104. package/dist/agents/02-seed-architect.d.ts.map +1 -0
  105. package/dist/agents/02-seed-architect.js +51 -0
  106. package/dist/agents/02-seed-architect.js.map +1 -0
  107. package/dist/agents/03-test-generator.d.ts +9 -0
  108. package/dist/agents/03-test-generator.d.ts.map +1 -0
  109. package/dist/agents/03-test-generator.js +167 -0
  110. package/dist/agents/03-test-generator.js.map +1 -0
  111. package/dist/agents/04-unit-runner.d.ts +9 -0
  112. package/dist/agents/04-unit-runner.d.ts.map +1 -0
  113. package/dist/agents/04-unit-runner.js +113 -0
  114. package/dist/agents/04-unit-runner.js.map +1 -0
  115. package/dist/agents/05-browser-crawler.d.ts +30 -0
  116. package/dist/agents/05-browser-crawler.d.ts.map +1 -0
  117. package/dist/agents/05-browser-crawler.js +685 -0
  118. package/dist/agents/05-browser-crawler.js.map +1 -0
  119. package/dist/agents/06-api-exerciser.d.ts +23 -0
  120. package/dist/agents/06-api-exerciser.d.ts.map +1 -0
  121. package/dist/agents/06-api-exerciser.js +253 -0
  122. package/dist/agents/06-api-exerciser.js.map +1 -0
  123. package/dist/agents/07-security-scout.d.ts +11 -0
  124. package/dist/agents/07-security-scout.d.ts.map +1 -0
  125. package/dist/agents/07-security-scout.js +142 -0
  126. package/dist/agents/07-security-scout.js.map +1 -0
  127. package/dist/agents/08-a11y-guardian.d.ts +13 -0
  128. package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
  129. package/dist/agents/08-a11y-guardian.js +176 -0
  130. package/dist/agents/08-a11y-guardian.js.map +1 -0
  131. package/dist/agents/09-healer.d.ts +33 -0
  132. package/dist/agents/09-healer.d.ts.map +1 -0
  133. package/dist/agents/09-healer.js +167 -0
  134. package/dist/agents/09-healer.js.map +1 -0
  135. package/dist/agents/10-reporter.d.ts +26 -0
  136. package/dist/agents/10-reporter.d.ts.map +1 -0
  137. package/dist/agents/10-reporter.js +215 -0
  138. package/dist/agents/10-reporter.js.map +1 -0
  139. package/dist/agents/11-fixer.d.ts +26 -0
  140. package/dist/agents/11-fixer.d.ts.map +1 -0
  141. package/dist/agents/11-fixer.js +195 -0
  142. package/dist/agents/11-fixer.js.map +1 -0
  143. package/dist/agents/12-ux-inspector.d.ts +15 -0
  144. package/dist/agents/12-ux-inspector.d.ts.map +1 -0
  145. package/dist/agents/12-ux-inspector.js +364 -0
  146. package/dist/agents/12-ux-inspector.js.map +1 -0
  147. package/dist/agents/13-performance-profiler.d.ts +13 -0
  148. package/dist/agents/13-performance-profiler.d.ts.map +1 -0
  149. package/dist/agents/13-performance-profiler.js +216 -0
  150. package/dist/agents/13-performance-profiler.js.map +1 -0
  151. package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
  152. package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
  153. package/dist/agents/14-data-integrity-auditor.js +356 -0
  154. package/dist/agents/14-data-integrity-auditor.js.map +1 -0
  155. package/dist/agents/15-regression-sentinel.d.ts +25 -0
  156. package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
  157. package/dist/agents/15-regression-sentinel.js +251 -0
  158. package/dist/agents/15-regression-sentinel.js.map +1 -0
  159. package/dist/agents/16-chaos-agent.d.ts +9 -0
  160. package/dist/agents/16-chaos-agent.d.ts.map +1 -0
  161. package/dist/agents/16-chaos-agent.js +207 -0
  162. package/dist/agents/16-chaos-agent.js.map +1 -0
  163. package/dist/agents/17-documentation-validator.d.ts +31 -0
  164. package/dist/agents/17-documentation-validator.d.ts.map +1 -0
  165. package/dist/agents/17-documentation-validator.js +246 -0
  166. package/dist/agents/17-documentation-validator.js.map +1 -0
  167. package/dist/agents/18-integration-watchdog.d.ts +10 -0
  168. package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
  169. package/dist/agents/18-integration-watchdog.js +138 -0
  170. package/dist/agents/18-integration-watchdog.js.map +1 -0
  171. package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
  172. package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
  173. package/dist/agents/19-tenant-isolation-auditor.js +166 -0
  174. package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
  175. package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
  176. package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
  177. package/dist/agents/20-workflow-completion-tester.js +159 -0
  178. package/dist/agents/20-workflow-completion-tester.js.map +1 -0
  179. package/dist/agents/21-state-session-tester.d.ts +10 -0
  180. package/dist/agents/21-state-session-tester.d.ts.map +1 -0
  181. package/dist/agents/21-state-session-tester.js +233 -0
  182. package/dist/agents/21-state-session-tester.js.map +1 -0
  183. package/dist/agents/22-email-notification-verifier.d.ts +11 -0
  184. package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
  185. package/dist/agents/22-email-notification-verifier.js +199 -0
  186. package/dist/agents/22-email-notification-verifier.js.map +1 -0
  187. package/dist/agents/23-migration-tester.d.ts +10 -0
  188. package/dist/agents/23-migration-tester.d.ts.map +1 -0
  189. package/dist/agents/23-migration-tester.js +74 -0
  190. package/dist/agents/23-migration-tester.js.map +1 -0
  191. package/dist/agents/base-agent.d.ts +19 -0
  192. package/dist/agents/base-agent.d.ts.map +1 -0
  193. package/dist/agents/base-agent.js +67 -0
  194. package/dist/agents/base-agent.js.map +1 -0
  195. package/dist/agents/registry.d.ts +29 -0
  196. package/dist/agents/registry.d.ts.map +1 -0
  197. package/dist/agents/registry.js +117 -0
  198. package/dist/agents/registry.js.map +1 -0
  199. package/dist/core/ci-output.d.ts +35 -0
  200. package/dist/core/ci-output.d.ts.map +1 -0
  201. package/dist/core/ci-output.js +193 -0
  202. package/dist/core/ci-output.js.map +1 -0
  203. package/dist/core/cli.d.ts +11 -0
  204. package/dist/core/cli.d.ts.map +1 -0
  205. package/dist/core/cli.js +197 -0
  206. package/dist/core/cli.js.map +1 -0
  207. package/dist/core/config.d.ts +111 -0
  208. package/dist/core/config.d.ts.map +1 -0
  209. package/dist/core/config.js +42 -0
  210. package/dist/core/config.js.map +1 -0
  211. package/dist/core/cost-tracker.d.ts +22 -0
  212. package/dist/core/cost-tracker.d.ts.map +1 -0
  213. package/dist/core/cost-tracker.js +41 -0
  214. package/dist/core/cost-tracker.js.map +1 -0
  215. package/dist/core/evidence.d.ts +28 -0
  216. package/dist/core/evidence.d.ts.map +1 -0
  217. package/dist/core/evidence.js +95 -0
  218. package/dist/core/evidence.js.map +1 -0
  219. package/dist/core/fix-loop.d.ts +29 -0
  220. package/dist/core/fix-loop.d.ts.map +1 -0
  221. package/dist/core/fix-loop.js +70 -0
  222. package/dist/core/fix-loop.js.map +1 -0
  223. package/dist/core/health-check.d.ts +21 -0
  224. package/dist/core/health-check.d.ts.map +1 -0
  225. package/dist/core/health-check.js +26 -0
  226. package/dist/core/health-check.js.map +1 -0
  227. package/dist/core/init.d.ts +2 -0
  228. package/dist/core/init.d.ts.map +1 -0
  229. package/dist/core/init.js +435 -0
  230. package/dist/core/init.js.map +1 -0
  231. package/dist/core/license-gen.d.ts +12 -0
  232. package/dist/core/license-gen.d.ts.map +1 -0
  233. package/dist/core/license-gen.js +169 -0
  234. package/dist/core/license-gen.js.map +1 -0
  235. package/dist/core/license.d.ts +33 -0
  236. package/dist/core/license.d.ts.map +1 -0
  237. package/dist/core/license.js +170 -0
  238. package/dist/core/license.js.map +1 -0
  239. package/dist/core/messages.d.ts +10 -0
  240. package/dist/core/messages.d.ts.map +1 -0
  241. package/dist/core/messages.js +47 -0
  242. package/dist/core/messages.js.map +1 -0
  243. package/dist/core/multi-browser.d.ts +36 -0
  244. package/dist/core/multi-browser.d.ts.map +1 -0
  245. package/dist/core/multi-browser.js +88 -0
  246. package/dist/core/multi-browser.js.map +1 -0
  247. package/dist/core/orchestrator.d.ts +48 -0
  248. package/dist/core/orchestrator.d.ts.map +1 -0
  249. package/dist/core/orchestrator.js +291 -0
  250. package/dist/core/orchestrator.js.map +1 -0
  251. package/dist/core/phase-gate.d.ts +4 -0
  252. package/dist/core/phase-gate.d.ts.map +1 -0
  253. package/dist/core/phase-gate.js +39 -0
  254. package/dist/core/phase-gate.js.map +1 -0
  255. package/dist/core/report-html.d.ts +9 -0
  256. package/dist/core/report-html.d.ts.map +1 -0
  257. package/dist/core/report-html.js +617 -0
  258. package/dist/core/report-html.js.map +1 -0
  259. package/dist/core/report-upload.d.ts +16 -0
  260. package/dist/core/report-upload.d.ts.map +1 -0
  261. package/dist/core/report-upload.js +124 -0
  262. package/dist/core/report-upload.js.map +1 -0
  263. package/dist/core/run-counter.d.ts +40 -0
  264. package/dist/core/run-counter.d.ts.map +1 -0
  265. package/dist/core/run-counter.js +120 -0
  266. package/dist/core/run-counter.js.map +1 -0
  267. package/dist/core/types.d.ts +53 -0
  268. package/dist/core/types.d.ts.map +1 -0
  269. package/dist/core/types.js +2 -0
  270. package/dist/core/types.js.map +1 -0
  271. package/dist/helpers/api-client.d.ts +30 -0
  272. package/dist/helpers/api-client.d.ts.map +1 -0
  273. package/dist/helpers/api-client.js +77 -0
  274. package/dist/helpers/api-client.js.map +1 -0
  275. package/dist/helpers/element-discovery.d.ts +18 -0
  276. package/dist/helpers/element-discovery.d.ts.map +1 -0
  277. package/dist/helpers/element-discovery.js +82 -0
  278. package/dist/helpers/element-discovery.js.map +1 -0
  279. package/dist/helpers/env-resolver.d.ts +29 -0
  280. package/dist/helpers/env-resolver.d.ts.map +1 -0
  281. package/dist/helpers/env-resolver.js +51 -0
  282. package/dist/helpers/env-resolver.js.map +1 -0
  283. package/dist/helpers/form-filler.d.ts +13 -0
  284. package/dist/helpers/form-filler.d.ts.map +1 -0
  285. package/dist/helpers/form-filler.js +98 -0
  286. package/dist/helpers/form-filler.js.map +1 -0
  287. package/dist/helpers/modal-handler.d.ts +16 -0
  288. package/dist/helpers/modal-handler.d.ts.map +1 -0
  289. package/dist/helpers/modal-handler.js +95 -0
  290. package/dist/helpers/modal-handler.js.map +1 -0
  291. package/dist/helpers/navigation.d.ts +37 -0
  292. package/dist/helpers/navigation.d.ts.map +1 -0
  293. package/dist/helpers/navigation.js +83 -0
  294. package/dist/helpers/navigation.js.map +1 -0
  295. package/dist/helpers/quality-gate.d.ts +17 -0
  296. package/dist/helpers/quality-gate.d.ts.map +1 -0
  297. package/dist/helpers/quality-gate.js +144 -0
  298. package/dist/helpers/quality-gate.js.map +1 -0
  299. package/dist/helpers/screenshot.d.ts +24 -0
  300. package/dist/helpers/screenshot.d.ts.map +1 -0
  301. package/dist/helpers/screenshot.js +76 -0
  302. package/dist/helpers/screenshot.js.map +1 -0
  303. package/dist/helpers/seed-validator.d.ts +15 -0
  304. package/dist/helpers/seed-validator.d.ts.map +1 -0
  305. package/dist/helpers/seed-validator.js +53 -0
  306. package/dist/helpers/seed-validator.js.map +1 -0
  307. package/helpers/__tests__/api-client.test.ts +199 -0
  308. package/helpers/__tests__/element-discovery.test.ts +202 -0
  309. package/helpers/__tests__/form-filler-extended.test.ts +212 -0
  310. package/helpers/__tests__/form-filler.test.ts +99 -0
  311. package/helpers/__tests__/modal-handler.test.ts +152 -0
  312. package/helpers/__tests__/navigation.test.ts +214 -0
  313. package/helpers/__tests__/quality-gate.test.ts +117 -0
  314. package/helpers/__tests__/screenshot.test.ts +139 -0
  315. package/helpers/__tests__/seed-validator.test.ts +114 -0
  316. package/helpers/api-client.ts +111 -0
  317. package/helpers/element-discovery.ts +105 -0
  318. package/helpers/env-resolver.ts +69 -0
  319. package/helpers/form-filler.ts +126 -0
  320. package/helpers/modal-handler.ts +108 -0
  321. package/helpers/navigation.ts +100 -0
  322. package/helpers/quality-gate.ts +180 -0
  323. package/helpers/screenshot.ts +111 -0
  324. package/helpers/seed-validator.ts +70 -0
  325. package/package.json +88 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Check whether a URL responds with a non-5xx status within a timeout.
3
+ * Exported so agents can use it directly without duplicating the function.
4
+ */
5
+ export async function isUrlReachable(url, timeoutMs = 5000) {
6
+ try {
7
+ const response = await fetch(url, {
8
+ method: 'HEAD',
9
+ signal: AbortSignal.timeout(timeoutMs),
10
+ });
11
+ return response.ok || response.status < 500;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /**
18
+ * Resolve the environment for a given phase with automatic fallback.
19
+ *
20
+ * 1. Try the phase's configured environment (`config.environments[phase]`).
21
+ * 2. If the URL is reachable → return `{ env, fallback: false }`.
22
+ * 3. If unreachable → try `config.environments['alpha']` (localhost).
23
+ * 4. If alpha is reachable → return `{ env: alphaEnv, fallback: true }`.
24
+ * 5. If neither is reachable → return the phase's env with `fallback: false`
25
+ * so the agent can decide how to handle the failure.
26
+ */
27
+ export async function resolveEnvironment(config, phase) {
28
+ // Phase-specific environment
29
+ const phaseEnv = config.environments[phase];
30
+ if (phaseEnv) {
31
+ const reachable = await isUrlReachable(phaseEnv.baseUrl);
32
+ if (reachable) {
33
+ return { env: phaseEnv, fallback: false };
34
+ }
35
+ }
36
+ // Fallback chain: alpha → first available
37
+ const alphaEnv = config.environments['alpha'];
38
+ if (alphaEnv && alphaEnv.baseUrl !== phaseEnv?.baseUrl) {
39
+ const reachable = await isUrlReachable(alphaEnv.baseUrl);
40
+ if (reachable) {
41
+ return { env: alphaEnv, fallback: true };
42
+ }
43
+ }
44
+ // Nothing reachable — return phase env (or first available) and let agent handle it
45
+ const fallbackEnv = phaseEnv ?? Object.values(config.environments)[0];
46
+ if (!fallbackEnv) {
47
+ throw new Error(`No environment configured for phase "${phase}"`);
48
+ }
49
+ return { env: fallbackEnv, fallback: false };
50
+ }
51
+ //# sourceMappingURL=env-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-resolver.js","sourceRoot":"","sources":["../../helpers/env-resolver.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,SAAS,GAAG,IAAI;IAChE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACvC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAuB,EACvB,KAAY;IAEZ,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,QAAQ,IAAI,QAAQ,CAAC,OAAO,KAAK,QAAQ,EAAE,OAAO,EAAE,CAAC;QACvD,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,MAAM,WAAW,GAAG,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,wCAAwC,KAAK,GAAG,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Page } from 'playwright';
2
+ /**
3
+ * Generates a valid test value based on the input's name and type attributes.
4
+ * Handles common field patterns used across the target application.
5
+ */
6
+ export declare function generateTestValue(inputType: string, inputName: string): string;
7
+ /**
8
+ * Fills all visible, enabled form inputs within the given form selector.
9
+ * If testData is provided, uses its values for fields matching the key names;
10
+ * otherwise auto-generates values using generateTestValue.
11
+ */
12
+ export declare function fillForm(page: Page, formSelector: string, testData?: Record<string, string>): Promise<void>;
13
+ //# sourceMappingURL=form-filler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-filler.d.ts","sourceRoot":"","sources":["../../helpers/form-filler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA6B9E;AAyCD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAwCf"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Generates a valid test value based on the input's name and type attributes.
3
+ * Handles common field patterns used across the target application.
4
+ */
5
+ export function generateTestValue(inputType, inputName) {
6
+ const nameLower = inputName.toLowerCase();
7
+ if (nameLower.includes('email')) {
8
+ const random = Math.random().toString(36).slice(2, 8);
9
+ return `test-${random}@example.com`;
10
+ }
11
+ if (nameLower.includes('phone')) {
12
+ const digits = Math.floor(Math.random() * 900000 + 100000).toString();
13
+ return `+61 400 ${digits}`;
14
+ }
15
+ if (nameLower.includes('abn')) {
16
+ return generateValidAbn();
17
+ }
18
+ if (nameLower.includes('date')) {
19
+ const future = new Date();
20
+ future.setDate(future.getDate() + 30);
21
+ return future.toISOString().split('T')[0];
22
+ }
23
+ if (inputType === 'number') {
24
+ return '100';
25
+ }
26
+ const random = Math.random().toString(36).slice(2, 8);
27
+ return `Test Value ${random}`;
28
+ }
29
+ /**
30
+ * Generates a valid 11-digit Australian Business Number (ABN).
31
+ * Applies the official ABN checksum algorithm.
32
+ */
33
+ function generateValidAbn() {
34
+ // ABN weights for digits 1-11
35
+ const weights = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
36
+ // Generate random 8-digit core (positions 3-10)
37
+ // We fix the last 8 digits, then solve for digits 1-2
38
+ for (let attempt = 0; attempt < 100; attempt++) {
39
+ const digits = new Array(11).fill(0);
40
+ // Fill digits 3-10 randomly
41
+ for (let i = 2; i < 11; i++) {
42
+ digits[i] = Math.floor(Math.random() * 10);
43
+ }
44
+ // Try all combinations for digits 1-2 (first digit can't be 0 in reduced form)
45
+ for (let d1 = 1; d1 <= 9; d1++) {
46
+ for (let d2 = 0; d2 <= 9; d2++) {
47
+ digits[0] = d1;
48
+ digits[1] = d2;
49
+ // ABN validation: subtract 1 from first digit, sum weighted digits, divisible by 89
50
+ const checkDigits = [...digits];
51
+ checkDigits[0] -= 1;
52
+ const sum = checkDigits.reduce((acc, d, i) => acc + d * weights[i], 0);
53
+ if (sum % 89 === 0) {
54
+ return digits.join('');
55
+ }
56
+ }
57
+ }
58
+ }
59
+ // Known valid fallback ABN
60
+ return '51824753556';
61
+ }
62
+ /**
63
+ * Fills all visible, enabled form inputs within the given form selector.
64
+ * If testData is provided, uses its values for fields matching the key names;
65
+ * otherwise auto-generates values using generateTestValue.
66
+ */
67
+ export async function fillForm(page, formSelector, testData) {
68
+ const inputs = await page.$$(`${formSelector} input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([disabled])`);
69
+ for (const input of inputs) {
70
+ const name = (await input.getAttribute('name')) ?? '';
71
+ const type = (await input.getAttribute('type')) ?? 'text';
72
+ if (testData && name && Object.prototype.hasOwnProperty.call(testData, name)) {
73
+ await input.fill(testData[name]);
74
+ continue;
75
+ }
76
+ if (type === 'checkbox' || type === 'radio') {
77
+ const checked = await input.isChecked();
78
+ if (!checked) {
79
+ await input.check();
80
+ }
81
+ continue;
82
+ }
83
+ const value = generateTestValue(type, name);
84
+ await input.fill(value);
85
+ }
86
+ const textareas = await page.$$(`${formSelector} textarea:not([disabled])`);
87
+ for (const textarea of textareas) {
88
+ const name = (await textarea.getAttribute('name')) ?? '';
89
+ if (testData && name && Object.prototype.hasOwnProperty.call(testData, name)) {
90
+ await textarea.fill(testData[name]);
91
+ }
92
+ else {
93
+ const random = Math.random().toString(36).slice(2, 8);
94
+ await textarea.fill(`Test textarea value ${random}`);
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=form-filler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-filler.js","sourceRoot":"","sources":["../../helpers/form-filler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Page } from 'playwright';
2
+ /**
3
+ * Clicks the trigger element and waits for a modal/dialog to become visible.
4
+ * Returns true if a modal is detected within 5 seconds; false otherwise.
5
+ */
6
+ export declare function openModal(page: Page, triggerSelector: string): Promise<boolean>;
7
+ /**
8
+ * Closes the currently open modal using the specified method.
9
+ * Returns true if the modal is no longer visible after the action; false otherwise.
10
+ */
11
+ export declare function closeModal(page: Page, method: 'x-button' | 'escape' | 'overlay'): Promise<boolean>;
12
+ /**
13
+ * Returns true if any recognised modal or dialog element is currently visible.
14
+ */
15
+ export declare function isModalOpen(page: Page): Promise<boolean>;
16
+ //# sourceMappingURL=modal-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal-handler.d.ts","sourceRoot":"","sources":["../../helpers/modal-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA8BvC;;;GAGG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAarF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,SAAS,GACxC,OAAO,CAAC,OAAO,CAAC,CAiClB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAa9D"}
@@ -0,0 +1,95 @@
1
+ /** CSS selectors used to detect an open modal or dialog. */
2
+ const MODAL_SELECTORS = [
3
+ '[role="dialog"]',
4
+ '[role="alertdialog"]',
5
+ '.modal',
6
+ '[data-modal]',
7
+ '[data-testid="modal"]',
8
+ ];
9
+ /** CSS selectors used to locate the close (×) button inside a modal. */
10
+ const CLOSE_BUTTON_SELECTORS = [
11
+ '[aria-label="Close"]',
12
+ '[aria-label="close"]',
13
+ '[data-testid="modal-close"]',
14
+ 'button.modal-close',
15
+ 'button[class*="close"]',
16
+ '[role="dialog"] button:first-of-type',
17
+ ];
18
+ /** CSS selectors for the overlay/backdrop behind a modal. */
19
+ const OVERLAY_SELECTORS = [
20
+ '[data-testid="modal-overlay"]',
21
+ '.modal-overlay',
22
+ '.modal-backdrop',
23
+ '[class*="overlay"]',
24
+ '[class*="backdrop"]',
25
+ ];
26
+ /**
27
+ * Clicks the trigger element and waits for a modal/dialog to become visible.
28
+ * Returns true if a modal is detected within 5 seconds; false otherwise.
29
+ */
30
+ export async function openModal(page, triggerSelector) {
31
+ await page.click(triggerSelector);
32
+ for (const selector of MODAL_SELECTORS) {
33
+ try {
34
+ await page.waitForSelector(selector, { state: 'visible', timeout: 5000 });
35
+ return true;
36
+ }
37
+ catch {
38
+ // Selector not found — try the next candidate
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+ /**
44
+ * Closes the currently open modal using the specified method.
45
+ * Returns true if the modal is no longer visible after the action; false otherwise.
46
+ */
47
+ export async function closeModal(page, method) {
48
+ switch (method) {
49
+ case 'x-button': {
50
+ for (const selector of CLOSE_BUTTON_SELECTORS) {
51
+ const button = page.locator(selector);
52
+ if (await button.isVisible().catch(() => false)) {
53
+ await button.click();
54
+ break;
55
+ }
56
+ }
57
+ break;
58
+ }
59
+ case 'escape': {
60
+ await page.keyboard.press('Escape');
61
+ break;
62
+ }
63
+ case 'overlay': {
64
+ for (const selector of OVERLAY_SELECTORS) {
65
+ const overlay = page.locator(selector);
66
+ if (await overlay.isVisible().catch(() => false)) {
67
+ await overlay.click({ position: { x: 1, y: 1 } });
68
+ break;
69
+ }
70
+ }
71
+ break;
72
+ }
73
+ }
74
+ // Give the DOM a moment to react then verify the modal is gone
75
+ await page.waitForTimeout(300);
76
+ return !(await isModalOpen(page));
77
+ }
78
+ /**
79
+ * Returns true if any recognised modal or dialog element is currently visible.
80
+ */
81
+ export async function isModalOpen(page) {
82
+ for (const selector of MODAL_SELECTORS) {
83
+ try {
84
+ const locator = page.locator(selector);
85
+ if (await locator.isVisible()) {
86
+ return true;
87
+ }
88
+ }
89
+ catch {
90
+ // Element not present — continue checking
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+ //# sourceMappingURL=modal-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal-handler.js","sourceRoot":"","sources":["../../helpers/modal-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Page } from 'playwright';
2
+ /**
3
+ * Navigate to `loginUrl`, fill the email and password inputs, submit the form,
4
+ * and wait for the browser to navigate away from the login page.
5
+ */
6
+ export declare function login(page: Page, credentials: {
7
+ email: string;
8
+ password: string;
9
+ }, loginUrl: string): Promise<void>;
10
+ /**
11
+ * Resolve `moduleId` to its route via `config.modules`, navigate to that route,
12
+ * and wait for the section's lazy-loaded content to appear.
13
+ *
14
+ * @throws {Error} when `moduleId` is not found in `config.modules`.
15
+ */
16
+ export declare function navigateToSection(page: Page, moduleId: string, config: {
17
+ modules: Array<{
18
+ id: string;
19
+ route: string;
20
+ }>;
21
+ }): Promise<void>;
22
+ /**
23
+ * Wait for all React Suspense loading fallbacks (spinners, skeletons) to
24
+ * disappear, indicating that lazy-loaded section content has fully rendered.
25
+ *
26
+ * @param timeout Maximum time in milliseconds to wait (default: 10 000 ms).
27
+ */
28
+ export declare function waitForSectionLoad(page: Page, timeout?: number): Promise<void>;
29
+ /**
30
+ * Switch between the bookkeeper portal and the client portal by manipulating
31
+ * the `portal` query-string parameter on the current page URL.
32
+ *
33
+ * @param portalQueryParam Pass `"client"` (or any portal slug) to set
34
+ * `?portal=<value>`, or `null` to remove the query parameter entirely.
35
+ */
36
+ export declare function switchPortal(page: Page, portalQueryParam: string | null): Promise<void>;
37
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../helpers/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAiBvC;;;GAGG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,IAAI,EACV,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,SAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrG;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7F"}
@@ -0,0 +1,83 @@
1
+ /** Default milliseconds to wait for all loading indicators to disappear. */
2
+ const DEFAULT_WAIT_TIMEOUT_MS = 10_000;
3
+ /**
4
+ * Selectors that represent loading / skeleton states produced by React Suspense
5
+ * fallback components. All must be absent before `waitForSectionLoad` resolves.
6
+ */
7
+ const LOADING_SELECTORS = [
8
+ '[aria-label="Loading"]',
9
+ '[data-testid="loading-spinner"]',
10
+ '[data-testid="skeleton"]',
11
+ '.loading-spinner',
12
+ '.skeleton',
13
+ ];
14
+ /**
15
+ * Navigate to `loginUrl`, fill the email and password inputs, submit the form,
16
+ * and wait for the browser to navigate away from the login page.
17
+ */
18
+ export async function login(page, credentials, loginUrl) {
19
+ await page.goto(loginUrl, { waitUntil: 'domcontentloaded', timeout: 10000 });
20
+ await page.fill('input[type="email"], input[name="email"], #login-email', credentials.email);
21
+ await page.fill('input[type="password"], input[name="password"], #login-password', credentials.password);
22
+ // Use Promise.race — SPAs may not trigger a traditional navigation after login
23
+ await Promise.all([
24
+ page.waitForURL((url) => !url.toString().includes('/login'), { timeout: 10000 }).catch(() => {
25
+ // SPA fallback: wait for the login form to disappear
26
+ return page.waitForSelector('input[type="email"], #login-email', { state: 'hidden', timeout: 5000 }).catch(() => {
27
+ // Graceful return — login may have failed (wrong credentials, no API)
28
+ // Caller should check page URL to determine if login succeeded
29
+ });
30
+ }),
31
+ page.click('button[type="submit"]'),
32
+ ]);
33
+ }
34
+ /**
35
+ * Resolve `moduleId` to its route via `config.modules`, navigate to that route,
36
+ * and wait for the section's lazy-loaded content to appear.
37
+ *
38
+ * @throws {Error} when `moduleId` is not found in `config.modules`.
39
+ */
40
+ export async function navigateToSection(page, moduleId, config) {
41
+ const module = config.modules.find((m) => m.id === moduleId);
42
+ if (module === undefined) {
43
+ throw new Error(`navigateToSection: moduleId "${moduleId}" not found in config. ` +
44
+ `Available ids: [${config.modules.map((m) => m.id).join(', ')}]`);
45
+ }
46
+ // Resolve relative routes against the current page origin
47
+ const currentUrl = page.url();
48
+ const origin = currentUrl.startsWith('http') ? new URL(currentUrl).origin : '';
49
+ const fullUrl = module.route.startsWith('http') ? module.route : `${origin}${module.route}`;
50
+ await page.goto(fullUrl);
51
+ await waitForSectionLoad(page);
52
+ }
53
+ /**
54
+ * Wait for all React Suspense loading fallbacks (spinners, skeletons) to
55
+ * disappear, indicating that lazy-loaded section content has fully rendered.
56
+ *
57
+ * @param timeout Maximum time in milliseconds to wait (default: 10 000 ms).
58
+ */
59
+ export async function waitForSectionLoad(page, timeout = DEFAULT_WAIT_TIMEOUT_MS) {
60
+ for (const selector of LOADING_SELECTORS) {
61
+ // waitForSelector with state "hidden" resolves immediately if the element
62
+ // is already absent, so this is safe even when the spinner never appears.
63
+ await page.waitForSelector(selector, { state: 'hidden', timeout });
64
+ }
65
+ }
66
+ /**
67
+ * Switch between the bookkeeper portal and the client portal by manipulating
68
+ * the `portal` query-string parameter on the current page URL.
69
+ *
70
+ * @param portalQueryParam Pass `"client"` (or any portal slug) to set
71
+ * `?portal=<value>`, or `null` to remove the query parameter entirely.
72
+ */
73
+ export async function switchPortal(page, portalQueryParam) {
74
+ const currentUrl = new URL(page.url());
75
+ if (portalQueryParam === null) {
76
+ currentUrl.searchParams.delete('portal');
77
+ }
78
+ else {
79
+ currentUrl.searchParams.set('portal', portalQueryParam);
80
+ }
81
+ await page.goto(currentUrl.toString());
82
+ }
83
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../helpers/navigation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface QualityViolation {
2
+ rule: string;
3
+ message: string;
4
+ line?: number;
5
+ severity: 'error' | 'warning';
6
+ }
7
+ /**
8
+ * Validates the given source code content against AVI-OS coding quality rules.
9
+ * Returns an array of QualityViolation objects; an empty array means the code
10
+ * passes all checks.
11
+ */
12
+ export declare function validateCodeQuality(fileContent: string, filePath: string): QualityViolation[];
13
+ /**
14
+ * Reads a file from disk and runs validateCodeQuality on its contents.
15
+ */
16
+ export declare function runQualityGate(filePath: string): QualityViolation[];
17
+ //# sourceMappingURL=quality-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-gate.d.ts","sourceRoot":"","sources":["../../helpers/quality-gate.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAiJD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,gBAAgB,EAAE,CAWpB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAGnE"}
@@ -0,0 +1,144 @@
1
+ import { readFileSync } from 'fs';
2
+ // Matches `: any` type annotations
3
+ const ANY_TYPE_PATTERN = /:\s*any\b/;
4
+ // Matches function declarations and arrow functions that open a block body on the same line
5
+ const FUNCTION_START_PATTERN = /(?:function\s+\w+\s*\([^)]*\)\s*\{|(?:\([^)]*\)|\w+)\s*=>\s*\{)/;
6
+ // Matches hardcoded secret assignments with literal string values
7
+ const HARDCODED_SECRET_PATTERN = /(?:password|secret|apiKey|api_key|token)\s*=\s*['"][^'"]+['"]/i;
8
+ // Matches lines that safely read from process.env
9
+ const PROCESS_ENV_PATTERN = /process\.env\./;
10
+ // Maximum lines allowed inside a single function body before a violation is raised
11
+ const MAX_FUNCTION_LINES = 30;
12
+ // Maximum brace-nesting depth before a violation is raised
13
+ const MAX_NESTING_DEPTH = 3;
14
+ function isCommentLine(line) {
15
+ const trimmed = line.trim();
16
+ return (trimmed.startsWith('//') ||
17
+ trimmed.startsWith('*') ||
18
+ trimmed.startsWith('/*'));
19
+ }
20
+ function checkAnyType(lines) {
21
+ const violations = [];
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i];
24
+ if (isCommentLine(line))
25
+ continue;
26
+ // Strip string literals to avoid false positives from values containing "any"
27
+ const stripped = line.replace(/(['"`])(?:(?!\1)[^\\]|\\.)*\1/g, '""');
28
+ if (ANY_TYPE_PATTERN.test(stripped)) {
29
+ violations.push({
30
+ rule: 'no-any',
31
+ message: `Avoid \`any\` type — use explicit types to preserve TypeScript safety (line ${i + 1}).`,
32
+ line: i + 1,
33
+ severity: 'error',
34
+ });
35
+ }
36
+ }
37
+ return violations;
38
+ }
39
+ function checkLongFunctions(lines) {
40
+ const violations = [];
41
+ // Stack entries: { startLine (0-based), braceDepthAtStart }
42
+ const stack = [];
43
+ let braceDepth = 0;
44
+ for (let i = 0; i < lines.length; i++) {
45
+ const line = lines[i];
46
+ const opens = (line.match(/\{/g) ?? []).length;
47
+ const closes = (line.match(/\}/g) ?? []).length;
48
+ if (FUNCTION_START_PATTERN.test(line)) {
49
+ // Record function start; its body starts at the `{` on this line, so the
50
+ // depth before processing this line's braces is `braceDepth`.
51
+ stack.push({ startLine: i, braceDepthAtStart: braceDepth });
52
+ }
53
+ braceDepth += opens - closes;
54
+ // Pop any functions whose body just closed (depth returned to start level)
55
+ while (stack.length > 0) {
56
+ const top = stack[stack.length - 1];
57
+ if (braceDepth <= top.braceDepthAtStart) {
58
+ const functionLineCount = i - top.startLine + 1;
59
+ if (functionLineCount > MAX_FUNCTION_LINES) {
60
+ violations.push({
61
+ rule: 'max-function-lines',
62
+ message: `Function starting at line ${top.startLine + 1} is ${functionLineCount} lines long ` +
63
+ `— keep functions under ${MAX_FUNCTION_LINES + 1} lines.`,
64
+ line: top.startLine + 1,
65
+ severity: 'warning',
66
+ });
67
+ }
68
+ stack.pop();
69
+ }
70
+ else {
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ return violations;
76
+ }
77
+ function checkHardcodedSecrets(lines) {
78
+ const violations = [];
79
+ for (let i = 0; i < lines.length; i++) {
80
+ const line = lines[i];
81
+ if (isCommentLine(line))
82
+ continue;
83
+ if (PROCESS_ENV_PATTERN.test(line))
84
+ continue;
85
+ if (HARDCODED_SECRET_PATTERN.test(line)) {
86
+ violations.push({
87
+ rule: 'no-hardcoded-secrets',
88
+ message: `Hardcoded secret detected at line ${i + 1} — use environment variables instead.`,
89
+ line: i + 1,
90
+ severity: 'error',
91
+ });
92
+ }
93
+ }
94
+ return violations;
95
+ }
96
+ function checkDeepNesting(lines) {
97
+ const violations = [];
98
+ let depth = 0;
99
+ let violationActive = false;
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = lines[i];
102
+ const opens = (line.match(/\{/g) ?? []).length;
103
+ const closes = (line.match(/\}/g) ?? []).length;
104
+ depth += opens - closes;
105
+ if (depth > MAX_NESTING_DEPTH && !violationActive) {
106
+ violations.push({
107
+ rule: 'max-nesting-depth',
108
+ message: `Nesting depth exceeds ${MAX_NESTING_DEPTH} levels at line ${i + 1} ` +
109
+ `— refactor to reduce complexity.`,
110
+ line: i + 1,
111
+ severity: 'warning',
112
+ });
113
+ violationActive = true;
114
+ }
115
+ if (depth <= MAX_NESTING_DEPTH) {
116
+ violationActive = false;
117
+ }
118
+ }
119
+ return violations;
120
+ }
121
+ /**
122
+ * Validates the given source code content against AVI-OS coding quality rules.
123
+ * Returns an array of QualityViolation objects; an empty array means the code
124
+ * passes all checks.
125
+ */
126
+ export function validateCodeQuality(fileContent, filePath) {
127
+ // filePath is available for future rule extensions (e.g. path-based exclusions)
128
+ void filePath;
129
+ const lines = fileContent.split('\n');
130
+ return [
131
+ ...checkAnyType(lines),
132
+ ...checkHardcodedSecrets(lines),
133
+ ...checkLongFunctions(lines),
134
+ ...checkDeepNesting(lines),
135
+ ];
136
+ }
137
+ /**
138
+ * Reads a file from disk and runs validateCodeQuality on its contents.
139
+ */
140
+ export function runQualityGate(filePath) {
141
+ const content = readFileSync(filePath, 'utf-8');
142
+ return validateCodeQuality(content, filePath);
143
+ }
144
+ //# sourceMappingURL=quality-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-gate.js","sourceRoot":"","sources":["../../helpers/quality-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Page } from 'playwright';
2
+ export interface ScreenshotCompareResult {
3
+ match: boolean;
4
+ diffPercentage: number;
5
+ diffPath?: string;
6
+ }
7
+ /**
8
+ * Captures a screenshot of the current page state and saves it to outputDir.
9
+ * Returns the absolute file path of the saved screenshot.
10
+ */
11
+ export declare function captureScreenshot(page: Page, name: string, outputDir: string): Promise<string>;
12
+ /**
13
+ * Compares two PNG screenshots using pixelmatch.
14
+ * Returns match=true when the diff percentage is at or below the threshold.
15
+ * Default threshold: 0.1 (0.1% pixel difference).
16
+ * Writes a diff PNG alongside the current screenshot when images differ.
17
+ */
18
+ export declare function compareScreenshots(currentPath: string, baselinePath: string, threshold?: number): Promise<ScreenshotCompareResult>;
19
+ /**
20
+ * Copies the current screenshot to the baseline directory, establishing it
21
+ * as the new reference image. Returns the path of the new baseline file.
22
+ */
23
+ export declare function updateBaseline(currentPath: string, baselineDir: string): Promise<string>;
24
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../helpers/screenshot.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,SAAS,SAAM,GACd,OAAO,CAAC,uBAAuB,CAAC,CAyClC;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAYjB"}