@bugmole/cli 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 (308) hide show
  1. package/.bugmole.env.example +20 -0
  2. package/LICENSE +7 -0
  3. package/README.md +293 -0
  4. package/TESTING.md +117 -0
  5. package/bugmole.config.yaml +39 -0
  6. package/package.json +85 -0
  7. package/scripts/billing/paypal-setup.mjs +121 -0
  8. package/scripts/bugmole-continue.ts +318 -0
  9. package/scripts/bugmole-init.ts +188 -0
  10. package/scripts/bugmole.cjs +17 -0
  11. package/scripts/bugmole.test.ts +344 -0
  12. package/scripts/bugmole.ts +657 -0
  13. package/scripts/ensure-maestro.cjs +79 -0
  14. package/scripts/ios-tunnel-keeper.sh +45 -0
  15. package/scripts/ios-wda-keeper.sh +66 -0
  16. package/scripts/sync-plan-catalog.d.mts +3 -0
  17. package/scripts/sync-plan-catalog.mjs +16 -0
  18. package/scripts/ui-parity-diff.py +65 -0
  19. package/scripts/ui-parity-requirements.txt +1 -0
  20. package/scripts/verify-manage-to-plans.mts +194 -0
  21. package/spec/app-ui-audit.schema.json +176 -0
  22. package/spec/blockers.yaml +79 -0
  23. package/spec/bugs.index.json +42 -0
  24. package/spec/design-dna.schema.json +38 -0
  25. package/spec/domain_rules.yaml +24 -0
  26. package/spec/flows/flow_manage-to-plans-64c3c61b.yaml +8 -0
  27. package/spec/flows/flow_screen-to-evidence-6c3ab309.yaml +7 -0
  28. package/spec/journey_graph.yaml +126 -0
  29. package/spec/journeys.graph.json +2618 -0
  30. package/spec/plans/dashboard-smoke.flow.yaml +20 -0
  31. package/spec/plans/example.flow.yaml +99 -0
  32. package/spec/plans/flow_api-keys-to-logout-7396cd5d.flow.yaml +33 -0
  33. package/spec/plans/flow_api-keys-to-screen-157e10a6.flow.yaml +33 -0
  34. package/spec/plans/flow_manage-to-audits-176c1eb8.flow.yaml +33 -0
  35. package/spec/plans/flow_manage-to-blockers-633282c8.flow.yaml +112 -0
  36. package/spec/plans/flow_manage-to-devices-ad57e202.flow.yaml +33 -0
  37. package/spec/plans/flow_manage-to-logout-86c54656.flow.yaml +33 -0
  38. package/spec/plans/flow_manage-to-manage-resource-action-6325f99a.flow.yaml +185 -0
  39. package/spec/plans/flow_manage-to-parity-issues-01fa2579.flow.yaml +34 -0
  40. package/spec/plans/flow_manage-to-plans-64c3c61b.flow.yaml +123 -0
  41. package/spec/plans/flow_manage-to-screen-6857f915.flow.yaml +106 -0
  42. package/spec/plans/flow_manage-to-tasks-c5791c95.flow.yaml +33 -0
  43. package/spec/plans/flow_profile-to-screen-6045aa3d.flow.yaml +33 -0
  44. package/spec/plans/flow_register-to-api-auth-login-460a9cc8.flow.yaml +34 -0
  45. package/spec/plans/flow_screen-to-audits-07666356.flow.yaml +33 -0
  46. package/spec/plans/flow_screen-to-blockers-091d258b.flow.yaml +33 -0
  47. package/spec/plans/flow_screen-to-devices-cad72f75.flow.yaml +33 -0
  48. package/spec/plans/flow_screen-to-evidence-6c3ab309.flow.yaml +126 -0
  49. package/spec/plans/flow_screen-to-parity-issues-2218e3ad.flow.yaml +34 -0
  50. package/spec/plans/flow_screen-to-plans-8232a94f.flow.yaml +33 -0
  51. package/spec/plans/flow_screen-to-tasks-540671d1.flow.yaml +33 -0
  52. package/spec/plans/login.flow.yaml +22 -0
  53. package/spec/plans/owner-operations.flow.yaml +20 -0
  54. package/spec/project_config.yaml +55 -0
  55. package/spec/roles.yaml +30 -0
  56. package/spec/schema.md +394 -0
  57. package/spec/test-case-results.schema.json +62 -0
  58. package/spec/test-cases.schema.json +85 -0
  59. package/spec/ui-parity-audit.schema.json +194 -0
  60. package/spec/ui-reverse-engineering.schema.json +94 -0
  61. package/src/billing/plan-catalog.test.ts +46 -0
  62. package/src/billing/plan-catalog.ts +199 -0
  63. package/src/integrations/aws-sigv4.test.ts +42 -0
  64. package/src/integrations/aws-sigv4.ts +72 -0
  65. package/src/integrations/device-farm.ts +155 -0
  66. package/src/integrations/github-app.test.ts +57 -0
  67. package/src/integrations/github-app.ts +143 -0
  68. package/src/integrations/gitlab.ts +81 -0
  69. package/src/integrations/temp-email.test.ts +123 -0
  70. package/src/integrations/temp-email.ts +175 -0
  71. package/src/integrations/testflight-feedback.test.ts +51 -0
  72. package/src/integrations/testflight-feedback.ts +173 -0
  73. package/src/integrations/webdriver-client.ts +131 -0
  74. package/src/mcp/server.test.ts +1220 -0
  75. package/src/mcp/server.ts +3064 -0
  76. package/src/mcp/write-test-cases.test.ts +287 -0
  77. package/src/registry/api-key-client.ts +39 -0
  78. package/src/registry/control-plane-client.ts +212 -0
  79. package/src/registry/migrations/0001_registry.sql +47 -0
  80. package/src/registry/migrations/0002_device_authorizations.sql +23 -0
  81. package/src/registry/migrations/0003_project_environments.sql +25 -0
  82. package/src/registry/migrations/0004_device_authorization_email.sql +1 -0
  83. package/src/registry/migrations/0005_testing_control_plane.sql +55 -0
  84. package/src/registry/migrations/0006_workspaces.sql +36 -0
  85. package/src/registry/migrations/0007_project_apps.sql +26 -0
  86. package/src/registry/migrations/0008_agent_tasks.sql +30 -0
  87. package/src/registry/migrations/0009_journey_revisions.sql +17 -0
  88. package/src/registry/migrations/0010_agent_task_journey.sql +2 -0
  89. package/src/registry/migrations/0011_run_journey_revision.sql +1 -0
  90. package/src/registry/migrations/0012_canonical_flow_execution.sql +10 -0
  91. package/src/registry/migrations/0013_device_sessions.sql +22 -0
  92. package/src/registry/migrations/0014_agent_task_step.sql +1 -0
  93. package/src/registry/migrations/0015_agent_task_attempts.sql +6 -0
  94. package/src/registry/migrations/0016_github_issue_tracker.sql +54 -0
  95. package/src/registry/migrations/0017_run_targets.sql +7 -0
  96. package/src/registry/migrations/0018_run_fix_from.sql +4 -0
  97. package/src/registry/migrations/0019_workspace_flags.sql +9 -0
  98. package/src/registry/migrations/0020_orgs.sql +40 -0
  99. package/src/registry/migrations/0021_billing_core.sql +58 -0
  100. package/src/registry/migrations/0022_cloud_runners.sql +19 -0
  101. package/src/registry/migrations/0023_signup.sql +4 -0
  102. package/src/registry/migrations/0024_billing.sql +67 -0
  103. package/src/registry/migrations/0025_notifications.sql +47 -0
  104. package/src/registry/migrations/0026_repo_bindings.sql +28 -0
  105. package/src/registry/migrations/0027_feedback.sql +29 -0
  106. package/src/registry/migrations/0028_devices.sql +48 -0
  107. package/src/registry/migrations/0029_sso.sql +31 -0
  108. package/src/registry/migrations/0030_workspace_domains.sql +18 -0
  109. package/src/registry/migrations/0031_gitlab_and_teams.sql +45 -0
  110. package/src/registry/migrations/0032_personas.sql +15 -0
  111. package/src/registry/migrations/0033_bugmole_rename.sql +11 -0
  112. package/src/registry/task-scheduling.test.ts +100 -0
  113. package/src/registry/task-scheduling.ts +80 -0
  114. package/src/registry-worker/ai/platform-model.ts +77 -0
  115. package/src/registry-worker/ai/routes.test.ts +88 -0
  116. package/src/registry-worker/ai/routes.ts +90 -0
  117. package/src/registry-worker/artifacts.test.ts +98 -0
  118. package/src/registry-worker/artifacts.ts +85 -0
  119. package/src/registry-worker/billing/billing-core.test.ts +175 -0
  120. package/src/registry-worker/billing/checkout-routes.ts +209 -0
  121. package/src/registry-worker/billing/enforcement.ts +69 -0
  122. package/src/registry-worker/billing/entitlements.ts +108 -0
  123. package/src/registry-worker/billing/ledger.ts +186 -0
  124. package/src/registry-worker/billing/paypal/api.ts +259 -0
  125. package/src/registry-worker/billing/paypal/client.ts +91 -0
  126. package/src/registry-worker/billing/paypal/provider.ts +143 -0
  127. package/src/registry-worker/billing/paypal.test.ts +466 -0
  128. package/src/registry-worker/billing/provider.ts +114 -0
  129. package/src/registry-worker/billing/routes.ts +66 -0
  130. package/src/registry-worker/billing/subscriptions.ts +780 -0
  131. package/src/registry-worker/billing/thresholds.ts +107 -0
  132. package/src/registry-worker/core.ts +308 -0
  133. package/src/registry-worker/devices/devices.test.ts +185 -0
  134. package/src/registry-worker/devices/policy.ts +71 -0
  135. package/src/registry-worker/devices/routes.ts +453 -0
  136. package/src/registry-worker/domains/domains.test.ts +210 -0
  137. package/src/registry-worker/domains/routes.ts +139 -0
  138. package/src/registry-worker/domains.ts +88 -0
  139. package/src/registry-worker/email/sender.ts +75 -0
  140. package/src/registry-worker/env.d.ts +14716 -0
  141. package/src/registry-worker/features.ts +20 -0
  142. package/src/registry-worker/feedback/feedback.test.ts +230 -0
  143. package/src/registry-worker/feedback/format.ts +148 -0
  144. package/src/registry-worker/feedback/routes.ts +386 -0
  145. package/src/registry-worker/flags.ts +39 -0
  146. package/src/registry-worker/github/checks.test.ts +177 -0
  147. package/src/registry-worker/github/checks.ts +374 -0
  148. package/src/registry-worker/gitlab/checks.test.ts +141 -0
  149. package/src/registry-worker/gitlab/checks.ts +349 -0
  150. package/src/registry-worker/hooks.ts +54 -0
  151. package/src/registry-worker/index.ts +2077 -0
  152. package/src/registry-worker/jobs/index.ts +29 -0
  153. package/src/registry-worker/jobs/retention.ts +68 -0
  154. package/src/registry-worker/mcp/mcp.test.ts +355 -0
  155. package/src/registry-worker/mcp/routes.ts +215 -0
  156. package/src/registry-worker/mcp/token.ts +126 -0
  157. package/src/registry-worker/mcp/tools.ts +563 -0
  158. package/src/registry-worker/notifications/alerts.ts +212 -0
  159. package/src/registry-worker/notifications/notifications.test.ts +298 -0
  160. package/src/registry-worker/notifications/outbox.ts +83 -0
  161. package/src/registry-worker/notifications/routes.ts +280 -0
  162. package/src/registry-worker/notifications/secrets.ts +49 -0
  163. package/src/registry-worker/notifications/slack.ts +96 -0
  164. package/src/registry-worker/notifications/teams.ts +46 -0
  165. package/src/registry-worker/org/audit.ts +116 -0
  166. package/src/registry-worker/org/routes.test.ts +163 -0
  167. package/src/registry-worker/org/routes.ts +302 -0
  168. package/src/registry-worker/personas/personas.test.ts +78 -0
  169. package/src/registry-worker/personas/routes.ts +100 -0
  170. package/src/registry-worker/repo-triggers.ts +20 -0
  171. package/src/registry-worker/routes/index.ts +74 -0
  172. package/src/registry-worker/run-events.ts +24 -0
  173. package/src/registry-worker/runner/dispatch.ts +219 -0
  174. package/src/registry-worker/runner/jobs.ts +43 -0
  175. package/src/registry-worker/runner/metering.ts +82 -0
  176. package/src/registry-worker/runner/policy.ts +59 -0
  177. package/src/registry-worker/runner/routes.ts +171 -0
  178. package/src/registry-worker/runner/runner.test.ts +358 -0
  179. package/src/registry-worker/runner/tokens.ts +93 -0
  180. package/src/registry-worker/runs.test.ts +60 -0
  181. package/src/registry-worker/signup/policy.ts +57 -0
  182. package/src/registry-worker/signup/routes.ts +106 -0
  183. package/src/registry-worker/signup/signup.test.ts +81 -0
  184. package/src/registry-worker/sso/aegis.ts +141 -0
  185. package/src/registry-worker/sso/membership.ts +157 -0
  186. package/src/registry-worker/sso/routes.ts +458 -0
  187. package/src/registry-worker/sso/sso.test.ts +344 -0
  188. package/src/registry-worker/testing/d1-shim.ts +180 -0
  189. package/src/registry-worker/testing/harness.ts +137 -0
  190. package/src/runner-worker/index.ts +108 -0
  191. package/src/runtime/ai-analysis.ts +97 -0
  192. package/src/runtime/ai-exploration.test.ts +32 -0
  193. package/src/runtime/ai-exploration.ts +69 -0
  194. package/src/runtime/ai-repair.ts +74 -0
  195. package/src/runtime/ai-work.test.ts +99 -0
  196. package/src/runtime/android-screen-record.test.ts +75 -0
  197. package/src/runtime/android-screen-record.ts +192 -0
  198. package/src/runtime/app-understanding.test.ts +123 -0
  199. package/src/runtime/app-understanding.ts +201 -0
  200. package/src/runtime/appium-driver.test.ts +179 -0
  201. package/src/runtime/appium-driver.ts +295 -0
  202. package/src/runtime/blocker-resolution.test.ts +113 -0
  203. package/src/runtime/blocker-resolution.ts +111 -0
  204. package/src/runtime/browser-matrix.integration.test.ts +212 -0
  205. package/src/runtime/browser-matrix.test.ts +143 -0
  206. package/src/runtime/browser-matrix.ts +200 -0
  207. package/src/runtime/canonical-flow.test.ts +52 -0
  208. package/src/runtime/config-validate.ts +185 -0
  209. package/src/runtime/continuous-execution.ts +291 -0
  210. package/src/runtime/cursor-applescript.ts +573 -0
  211. package/src/runtime/cursor-cli-driver.test.ts +78 -0
  212. package/src/runtime/cursor-cli-driver.ts +156 -0
  213. package/src/runtime/cursor-driver-example.ts +117 -0
  214. package/src/runtime/cursor-driver-index.ts +65 -0
  215. package/src/runtime/cursor-driver-init.ts +277 -0
  216. package/src/runtime/cursor-driver-run.test.ts +15 -0
  217. package/src/runtime/cursor-driver-run.ts +323 -0
  218. package/src/runtime/cursor-driver.ts +332 -0
  219. package/src/runtime/cursor-llm-example.ts +90 -0
  220. package/src/runtime/cursor-llm.ts +206 -0
  221. package/src/runtime/cursor-mcp-monitor.ts +386 -0
  222. package/src/runtime/device-clouds/browserstack.ts +73 -0
  223. package/src/runtime/device-clouds/device-farm.ts +52 -0
  224. package/src/runtime/device-clouds/index.ts +92 -0
  225. package/src/runtime/device-clouds/kobiton.ts +70 -0
  226. package/src/runtime/device-clouds/targets.ts +44 -0
  227. package/src/runtime/device-clouds/types.ts +62 -0
  228. package/src/runtime/diff-proposal.ts +84 -0
  229. package/src/runtime/discovery-task.test.ts +29 -0
  230. package/src/runtime/discovery-task.ts +284 -0
  231. package/src/runtime/driver-recovery.ts +69 -0
  232. package/src/runtime/driver.ts +79 -0
  233. package/src/runtime/environment.test.ts +104 -0
  234. package/src/runtime/environment.ts +137 -0
  235. package/src/runtime/executor.test.ts +509 -0
  236. package/src/runtime/executor.ts +921 -0
  237. package/src/runtime/explorer.test.ts +101 -0
  238. package/src/runtime/explorer.ts +1013 -0
  239. package/src/runtime/failure-analysis.test.ts +111 -0
  240. package/src/runtime/failure-analysis.ts +272 -0
  241. package/src/runtime/fixtures/fake-maestro.sh +36 -0
  242. package/src/runtime/flow-language.test.ts +268 -0
  243. package/src/runtime/flow-language.ts +414 -0
  244. package/src/runtime/init-wizard.ts +354 -0
  245. package/src/runtime/ios-screen-record.test.ts +68 -0
  246. package/src/runtime/ios-screen-record.ts +155 -0
  247. package/src/runtime/journey-editor.ts +452 -0
  248. package/src/runtime/journey-evidence.test.ts +161 -0
  249. package/src/runtime/journey-evidence.ts +180 -0
  250. package/src/runtime/journey-graph.test.ts +257 -0
  251. package/src/runtime/journey-graph.ts +170 -0
  252. package/src/runtime/legacy-names.ts +32 -0
  253. package/src/runtime/llm-example.ts +105 -0
  254. package/src/runtime/llm.ts +527 -0
  255. package/src/runtime/local-browser.test.ts +45 -0
  256. package/src/runtime/local-browser.ts +48 -0
  257. package/src/runtime/local-registry-stub.test.ts +325 -0
  258. package/src/runtime/local-registry-stub.ts +803 -0
  259. package/src/runtime/maestro-driver.test.ts +84 -0
  260. package/src/runtime/maestro-driver.ts +209 -0
  261. package/src/runtime/mole-voice.ts +21 -0
  262. package/src/runtime/nav-crawl.test.ts +100 -0
  263. package/src/runtime/nav-crawl.ts +153 -0
  264. package/src/runtime/pipeline.test.ts +405 -0
  265. package/src/runtime/pipeline.ts +833 -0
  266. package/src/runtime/planner.test.ts +37 -0
  267. package/src/runtime/planner.ts +274 -0
  268. package/src/runtime/platform-ai.ts +76 -0
  269. package/src/runtime/playwright-driver.test.ts +93 -0
  270. package/src/runtime/playwright-driver.ts +620 -0
  271. package/src/runtime/project-spec.ts +140 -0
  272. package/src/runtime/record-run-verdicts.ts +68 -0
  273. package/src/runtime/reporter.test.ts +56 -0
  274. package/src/runtime/reporter.ts +158 -0
  275. package/src/runtime/reset.test.ts +44 -0
  276. package/src/runtime/reset.ts +61 -0
  277. package/src/runtime/reviewer.test.ts +73 -0
  278. package/src/runtime/reviewer.ts +158 -0
  279. package/src/runtime/run-job.ts +136 -0
  280. package/src/runtime/run-once.test.ts +207 -0
  281. package/src/runtime/run-once.ts +168 -0
  282. package/src/runtime/run.ts +132 -0
  283. package/src/runtime/screen-recording.ts +34 -0
  284. package/src/runtime/serve-gateway.test.ts +74 -0
  285. package/src/runtime/serve-gateway.ts +164 -0
  286. package/src/runtime/serve-worker.test.ts +23 -0
  287. package/src/runtime/serve-worker.ts +278 -0
  288. package/src/runtime/site-discovery.test.ts +168 -0
  289. package/src/runtime/site-discovery.ts +308 -0
  290. package/src/runtime/target-runner.ts +144 -0
  291. package/src/runtime/test-case-verdicts.test.ts +94 -0
  292. package/src/runtime/test-case-verdicts.ts +120 -0
  293. package/src/runtime/ui-reverse-engineering/coordinator.test.ts +59 -0
  294. package/src/runtime/ui-reverse-engineering/coordinator.ts +391 -0
  295. package/src/runtime/ui-reverse-engineering/types.ts +197 -0
  296. package/src/runtime/web-suite.test.ts +97 -0
  297. package/src/runtime/web-suite.ts +176 -0
  298. package/src/storage/create-object-store.ts +144 -0
  299. package/src/storage/keys.ts +34 -0
  300. package/src/storage/local-artifact-server.test.ts +314 -0
  301. package/src/storage/local-artifact-server.ts +357 -0
  302. package/src/storage/object-store.test.ts +28 -0
  303. package/src/storage/object-store.ts +101 -0
  304. package/src/storage/registry-object-store.ts +88 -0
  305. package/src/storage/remote-object-store.ts +104 -0
  306. package/src/storage/storage-directory.test.ts +43 -0
  307. package/src/storage/storage-directory.ts +24 -0
  308. package/tsconfig.json +24 -0
@@ -0,0 +1,52 @@
1
+ import { strict as assert } from "node:assert";
2
+ import test from "node:test";
3
+ import { canonicalFlows, emptyJourneyGraph, validateJourneyGraph } from "./journey-graph.js";
4
+
5
+ test("only stable canonical flows project into the application graph", () => {
6
+ const graph = emptyJourneyGraph("demo");
7
+ graph.journeys = [
8
+ {
9
+ id: "draft-flow",
10
+ name: "Draft",
11
+ actor: "user",
12
+ nodeIds: [],
13
+ edgeIds: [],
14
+ revision: 1,
15
+ status: "draft",
16
+ sourceOfTruth: "canonical_flow",
17
+ stability: "stabilizing",
18
+ linkedToGraph: false,
19
+ },
20
+ {
21
+ id: "stable-flow",
22
+ name: "Stable",
23
+ actor: "user",
24
+ nodeIds: [],
25
+ edgeIds: [],
26
+ revision: 2,
27
+ status: "published",
28
+ sourceOfTruth: "canonical_flow",
29
+ stability: "stable",
30
+ linkedToGraph: true,
31
+ },
32
+ ];
33
+ assert.deepEqual(canonicalFlows(graph).map((journey) => journey.id), ["stable-flow"]);
34
+ assert.deepEqual(validateJourneyGraph(graph), { valid: true, errors: [] });
35
+ });
36
+
37
+ test("rejects a flow linked to the graph before stabilization", () => {
38
+ const graph = emptyJourneyGraph("demo");
39
+ graph.journeys = [{
40
+ id: "unstable-flow",
41
+ name: "Unstable",
42
+ actor: "user",
43
+ nodeIds: [],
44
+ edgeIds: [],
45
+ revision: 1,
46
+ status: "published",
47
+ sourceOfTruth: "canonical_flow",
48
+ stability: "stabilizing",
49
+ linkedToGraph: true,
50
+ }];
51
+ assert.equal(validateJourneyGraph(graph).valid, false);
52
+ });
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Config validation against governor policy rules.
3
+ * Validates environment classification, watermark rules, and safety constraints.
4
+ */
5
+
6
+ import { defaultTargets } from "./browser-matrix.js";
7
+
8
+ type Environment = "SANDBOX" | "TEST" | "STAGING" | "PRODUCTION";
9
+
10
+ interface Config {
11
+ runtime: {
12
+ env: string;
13
+ tenant: string;
14
+ artifacts_dir: string;
15
+ spec_dir: string;
16
+ };
17
+ llm: {
18
+ provider: string;
19
+ api_key: string;
20
+ model: string;
21
+ base_url?: string;
22
+ };
23
+ mcp?: {
24
+ enabled: boolean;
25
+ host: string;
26
+ port: number;
27
+ };
28
+ execution: {
29
+ driver?: string;
30
+ maestro?: {
31
+ command?: string;
32
+ timeout_ms?: number;
33
+ };
34
+ playwright: {
35
+ base_url: string;
36
+ headless: boolean;
37
+ browsers?: unknown;
38
+ devices?: unknown;
39
+ parallel?: unknown;
40
+ };
41
+ retries: {
42
+ step: number;
43
+ journey: number;
44
+ };
45
+ };
46
+ storage?: {
47
+ provider?: string;
48
+ [provider: string]: unknown;
49
+ };
50
+ }
51
+
52
+ interface ValidationResult {
53
+ valid: boolean;
54
+ errors: string[];
55
+ warnings: string[];
56
+ environment: Environment;
57
+ }
58
+
59
+ /**
60
+ * Classify environment from config string.
61
+ * Defaults to STAGING if uncertain (safest default).
62
+ */
63
+ function classifyEnvironment(envStr?: string): Environment {
64
+ const upper = envStr?.toUpperCase() ?? "";
65
+ if (upper === "SANDBOX" || upper === "SANDBOX") return "SANDBOX";
66
+ if (upper === "TEST") return "TEST";
67
+ if (upper === "STAGING") return "STAGING";
68
+ if (upper === "PRODUCTION" || upper === "PROD") return "PRODUCTION";
69
+ // Default to STAGING for safety (per governor policy)
70
+ return "STAGING";
71
+ }
72
+
73
+ /**
74
+ * Validate config against governor policy rules.
75
+ * See docs/governor-policy.md for complete rules.
76
+ */
77
+ export function validateConfig(cfg: Config): ValidationResult {
78
+ const errors: string[] = [];
79
+ const warnings: string[] = [];
80
+ const env = classifyEnvironment(cfg.runtime.env);
81
+
82
+ // Required fields
83
+ if (!cfg.runtime?.env) {
84
+ errors.push("runtime.env is required");
85
+ }
86
+ if (!cfg.runtime?.artifacts_dir) {
87
+ errors.push("runtime.artifacts_dir is required");
88
+ }
89
+ if (!cfg.runtime?.spec_dir) {
90
+ errors.push("runtime.spec_dir is required");
91
+ }
92
+ if (!cfg.llm?.provider) {
93
+ errors.push("llm.provider is required");
94
+ }
95
+ if (!cfg.llm?.api_key) {
96
+ errors.push("llm.api_key is required (set LLM_API_KEY in .env)");
97
+ }
98
+ const driver = cfg.execution?.driver || "maestro";
99
+ if (!["maestro", "playwright", "simulation"].includes(driver)) {
100
+ warnings.push(`Execution driver "${driver}" is not recognized; configure a registered driver adapter`);
101
+ }
102
+ if (driver === "maestro" && cfg.execution?.maestro?.timeout_ms !== undefined &&
103
+ cfg.execution.maestro.timeout_ms <= 0) {
104
+ errors.push("execution.maestro.timeout_ms must be greater than zero");
105
+ }
106
+ // Fail at startup, not mid-run, when the browser/device matrix has a typo.
107
+ try {
108
+ defaultTargets(cfg);
109
+ } catch (error) {
110
+ errors.push(`execution.playwright: ${error instanceof Error ? error.message : String(error)}`);
111
+ }
112
+ const parallel = cfg.execution?.playwright?.parallel;
113
+ if (parallel !== undefined && !(Number.isInteger(parallel) && (parallel as number) > 0)) {
114
+ errors.push("execution.playwright.parallel must be a positive integer");
115
+ }
116
+ const storageProvider = cfg.storage?.provider;
117
+ const storageDetails =
118
+ storageProvider && typeof cfg.storage?.[storageProvider] === "object"
119
+ ? (cfg.storage[storageProvider] as { bucket?: string; account_id?: string })
120
+ : undefined;
121
+ if (storageProvider && !["local", "gcs", "s3", "r2"].includes(storageProvider)) {
122
+ errors.push(`storage.provider "${storageProvider}" is unsupported`);
123
+ }
124
+ if (storageProvider && storageProvider !== "local" && !storageDetails?.bucket) {
125
+ errors.push(`storage.${storageProvider}.bucket is required`);
126
+ }
127
+ if (storageProvider === "r2" && !storageDetails?.account_id && !process.env.R2_ACCOUNT_ID) {
128
+ warnings.push("Cloudflare R2 account_id is not configured; provide R2_ACCOUNT_ID at runtime");
129
+ }
130
+
131
+ // Environment classification warnings
132
+ const envStr = cfg.runtime.env?.toUpperCase() ?? "";
133
+ if (!["SANDBOX", "TEST", "STAGING", "PRODUCTION", "PROD"].includes(envStr)) {
134
+ warnings.push(
135
+ `Environment "${cfg.runtime.env}" not recognized, defaulting to STAGING (safest)`,
136
+ );
137
+ }
138
+
139
+ // Production environment warnings
140
+ if (env === "PRODUCTION") {
141
+ warnings.push("PRODUCTION environment detected. Destructive actions will be blocked.");
142
+ }
143
+
144
+ // Retry budget validation (per governor policy defaults)
145
+ if (cfg.execution?.retries?.step !== undefined) {
146
+ const stepRetries = cfg.execution.retries.step;
147
+ if (stepRetries < 0 || stepRetries > 5) {
148
+ warnings.push(`Step retry count (${stepRetries}) is outside recommended range (0-5)`);
149
+ }
150
+ }
151
+ if (cfg.execution?.retries?.journey !== undefined) {
152
+ const journeyRetries = cfg.execution.retries.journey;
153
+ if (journeyRetries < 0 || journeyRetries > 3) {
154
+ warnings.push(`Journey retry count (${journeyRetries}) is outside recommended range (0-3)`);
155
+ }
156
+ }
157
+
158
+ // Artifacts directory must be writable (check will happen at runtime)
159
+ // Spec directory must exist and be readable
160
+
161
+ return {
162
+ valid: errors.length === 0,
163
+ errors,
164
+ warnings,
165
+ environment: env,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Validate and throw if invalid.
171
+ * Use this before starting any runtime operations.
172
+ */
173
+ export function validateConfigOrThrow(cfg: Config): Environment {
174
+ const result = validateConfig(cfg);
175
+ if (!result.valid) {
176
+ throw new Error(
177
+ `Config validation failed:\n${result.errors.map((e) => ` - ${e}`).join("\n")}`,
178
+ );
179
+ }
180
+ if (result.warnings.length > 0) {
181
+ console.warn("[Bugmole MCP] Config warnings:");
182
+ result.warnings.forEach((w) => console.warn(` ⚠ ${w}`));
183
+ }
184
+ return result.environment;
185
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Continuous execution loop for LLM agents
3
+ *
4
+ * This module provides a continuous execution loop that:
5
+ * 1. Sends a task to the LLM
6
+ * 2. Waits for commit response
7
+ * 3. Checks for next_steps in the response
8
+ * 4. If next_steps exist, automatically continues with follow-up prompts
9
+ * 5. Repeats until task is complete (no more next_steps)
10
+ *
11
+ * Works with any LLM provider (OpenAI, Anthropic, Cursor, Ollama)
12
+ */
13
+
14
+ import { llmStructured, type LLMOptions, type LLMResponse } from "./llm.js";
15
+ import { cursorLLMStructured, type CursorLLMOptions } from "./cursor-llm.js";
16
+ import type { CursorDriverResponse } from "./cursor-driver.js";
17
+
18
+ /**
19
+ * Options for continuous execution
20
+ */
21
+ export interface ContinuousExecutionOptions extends LLMOptions {
22
+ /**
23
+ * Maximum number of iterations (prevents infinite loops)
24
+ * @default 100
25
+ */
26
+ maxIterations?: number;
27
+
28
+ /**
29
+ * Delay between iterations in ms
30
+ * @default 2000
31
+ */
32
+ iterationDelay?: number;
33
+
34
+ /**
35
+ * Whether to use Cursor LLM (true) or standard LLM (false)
36
+ * @default true
37
+ */
38
+ useCursor?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Result of continuous execution
43
+ */
44
+ export interface ContinuousExecutionResult {
45
+ /**
46
+ * All responses from the execution loop
47
+ */
48
+ responses: Array<LLMResponse | CursorDriverResponse>;
49
+
50
+ /**
51
+ * Final response
52
+ */
53
+ finalResponse: LLMResponse | CursorDriverResponse;
54
+
55
+ /**
56
+ * Total iterations
57
+ */
58
+ iterations: number;
59
+
60
+ /**
61
+ * Whether execution completed successfully
62
+ */
63
+ success: boolean;
64
+
65
+ /**
66
+ * Error if execution failed
67
+ */
68
+ error?: string;
69
+ }
70
+
71
+ /**
72
+ * Extracts next steps from a response
73
+ */
74
+ function extractNextSteps(response: LLMResponse | CursorDriverResponse): string[] | undefined {
75
+ if ("payload" in response && response.payload) {
76
+ // CursorDriverResponse
77
+ const cursorResponse = response as CursorDriverResponse;
78
+ return cursorResponse.payload?.next_steps;
79
+ } else {
80
+ // LLMResponse - check metadata
81
+ const llmResponse = response as LLMResponse;
82
+ return llmResponse.metadata?.next_steps as string[] | undefined;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Generates a follow-up prompt from next steps with enhanced state summarization
88
+ * This provides context about what was done previously so the agent can continue efficiently
89
+ */
90
+ function generateFollowUpPrompt(
91
+ initialTask: string,
92
+ previousResponses: Array<LLMResponse | CursorDriverResponse>,
93
+ nextSteps: string[],
94
+ ): string {
95
+ // Build a concise summary of all previous work
96
+ const summaries: string[] = [];
97
+ const actions: string[] = [];
98
+ const artifacts: string[] = [];
99
+ let lastStatus = "ok";
100
+
101
+ previousResponses.forEach((response, index) => {
102
+ if ("payload" in response && response.payload) {
103
+ const cursorResponse = response as CursorDriverResponse;
104
+ const payload = cursorResponse.payload;
105
+ if (payload) {
106
+ summaries.push(`Iteration ${index + 1}: ${payload.summary}`);
107
+ lastStatus = payload.status || "ok";
108
+
109
+ // Collect key actions
110
+ if (payload.actions && payload.actions.length > 0) {
111
+ payload.actions.forEach((action) => {
112
+ actions.push(`- ${action.type}: ${action.target}`);
113
+ });
114
+ }
115
+
116
+ // Collect artifacts
117
+ if (payload.artifacts && payload.artifacts.length > 0) {
118
+ payload.artifacts.forEach((artifact) => {
119
+ artifacts.push(`- ${artifact.type}: ${artifact.content.substring(0, 100)}...`);
120
+ });
121
+ }
122
+ }
123
+ } else {
124
+ const llmResponse = response as LLMResponse;
125
+ summaries.push(`Iteration ${index + 1}: ${llmResponse.text.substring(0, 200)}...`);
126
+ }
127
+ });
128
+
129
+ // Build the prompt with concise context
130
+ let prompt = `# Continuing Task (Iteration ${previousResponses.length + 1})
131
+
132
+ ## Original Task
133
+ ${initialTask}
134
+
135
+ ## Previous Work Summary
136
+ ${summaries.length > 0 ? summaries.join("\n") : "No previous iterations"}
137
+
138
+ `;
139
+
140
+ // Include actions if any
141
+ if (actions.length > 0) {
142
+ prompt += `## Actions Taken
143
+ ${actions.slice(-10).join("\n")} // Showing last 10 actions
144
+
145
+ `;
146
+ }
147
+
148
+ // Include artifacts if any
149
+ if (artifacts.length > 0) {
150
+ prompt += `## Artifacts Created
151
+ ${artifacts.slice(-5).join("\n")} // Showing last 5 artifacts
152
+
153
+ `;
154
+ }
155
+
156
+ // Include last status
157
+ if (lastStatus === "error") {
158
+ prompt += `⚠️ **Note:** Previous iteration had an error status. Please review and continue carefully.\n\n`;
159
+ }
160
+
161
+ prompt += `## Next Steps (Continue Here)
162
+ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
163
+
164
+ ## Instructions
165
+ - Continue from where you left off - you have full context of previous work
166
+ - Complete the next steps listed above
167
+ - Use MCP tools as needed (bugmole_explore, bugmole_journey_next, bugmole_record_discovery, bugmole_plan, bugmole_run, bugmole_write_spec, etc.)
168
+ - After every exploration or discovery action, call bugmole_journey_get and bugmole_journey_next for the selected flow.
169
+ - If bugmole_journey_next returns an actionable task, perform it, record the resulting evidence, and continue the loop.
170
+ - Stop the exploration loop only when bugmole_journey_next returns exhausted or an explicit blocked reason; do not replace an actionable flow task with a generic plan.
171
+ - If there are more steps after completing these, include them in your \`next_steps\` field
172
+ - If this is the final step, do NOT include \`next_steps\` in your response
173
+
174
+ Continue with the next steps now.`;
175
+
176
+ return prompt;
177
+ }
178
+
179
+ /**
180
+ * Executes a task continuously, following next_steps until completion
181
+ *
182
+ * @param initialTask - The initial task to start with
183
+ * @param options - Execution options
184
+ * @returns All responses and final result
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const result = await executeContinuously(
189
+ * 'Investigate the project and set up testing',
190
+ * { useCursor: true, maxIterations: 5 }
191
+ * );
192
+ *
193
+ * console.log(`Completed in ${result.iterations} iterations`);
194
+ * console.log('Final response:', result.finalResponse);
195
+ * ```
196
+ */
197
+ export async function executeContinuously(
198
+ initialTask: string,
199
+ options: ContinuousExecutionOptions = {},
200
+ ): Promise<ContinuousExecutionResult> {
201
+ const { maxIterations = 100, iterationDelay = 2000, useCursor = true, ...llmOptions } = options;
202
+
203
+ const responses: Array<LLMResponse | CursorDriverResponse> = [];
204
+ let currentTask = initialTask;
205
+ let iteration = 0;
206
+
207
+ console.log(`[Continuous Execution] Starting with max ${maxIterations} iterations\n`);
208
+
209
+ while (iteration < maxIterations) {
210
+ iteration++;
211
+ console.log(`[Continuous Execution] Iteration ${iteration}/${maxIterations}`);
212
+
213
+ try {
214
+ // Execute current task
215
+ let response: LLMResponse | CursorDriverResponse;
216
+
217
+ if (useCursor) {
218
+ const cursorResponse = await cursorLLMStructured(
219
+ currentTask,
220
+ llmOptions as CursorLLMOptions,
221
+ );
222
+ response = cursorResponse;
223
+ } else {
224
+ response = await llmStructured(currentTask, llmOptions);
225
+ }
226
+
227
+ responses.push(response);
228
+
229
+ // Check if successful
230
+ if ("success" in response && !response.success) {
231
+ return {
232
+ responses,
233
+ finalResponse: response,
234
+ iterations: iteration,
235
+ success: false,
236
+ error: response.error || "Execution failed",
237
+ };
238
+ }
239
+
240
+ // Check for next steps
241
+ let nextSteps = extractNextSteps(response);
242
+
243
+ // Debug: log what we extracted
244
+ if (nextSteps && nextSteps.length > 0) {
245
+ console.log(`[Continuous Execution] Found ${nextSteps.length} next step(s):`, nextSteps);
246
+ } else {
247
+ console.log(
248
+ `[Continuous Execution] No next steps found (value: ${JSON.stringify(nextSteps)})`,
249
+ );
250
+ }
251
+
252
+ if (!nextSteps || nextSteps.length === 0) {
253
+ // No more steps, we're done!
254
+ console.log(`[Continuous Execution] Task completed in ${iteration} iteration(s)\n`);
255
+ return {
256
+ responses,
257
+ finalResponse: response,
258
+ iterations: iteration,
259
+ success: true,
260
+ };
261
+ }
262
+
263
+ // Generate follow-up prompt
264
+ console.log(`[Continuous Execution] Found ${nextSteps.length} next step(s), continuing...\n`);
265
+ currentTask = generateFollowUpPrompt(initialTask, responses, nextSteps);
266
+
267
+ // Wait before next iteration
268
+ if (iteration < maxIterations) {
269
+ await new Promise((resolve) => setTimeout(resolve, iterationDelay));
270
+ }
271
+ } catch (error: any) {
272
+ return {
273
+ responses,
274
+ finalResponse: responses[responses.length - 1] || ({} as any),
275
+ iterations: iteration,
276
+ success: false,
277
+ error: error.message || "Execution error",
278
+ };
279
+ }
280
+ }
281
+
282
+ // Max iterations reached
283
+ console.log(`[Continuous Execution] Reached max iterations (${maxIterations})\n`);
284
+ return {
285
+ responses,
286
+ finalResponse: responses[responses.length - 1],
287
+ iterations: iteration,
288
+ success: false,
289
+ error: `Reached maximum iterations (${maxIterations})`,
290
+ };
291
+ }