@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.
- package/.bugmole.env.example +20 -0
- package/LICENSE +7 -0
- package/README.md +293 -0
- package/TESTING.md +117 -0
- package/bugmole.config.yaml +39 -0
- package/package.json +85 -0
- package/scripts/billing/paypal-setup.mjs +121 -0
- package/scripts/bugmole-continue.ts +318 -0
- package/scripts/bugmole-init.ts +188 -0
- package/scripts/bugmole.cjs +17 -0
- package/scripts/bugmole.test.ts +344 -0
- package/scripts/bugmole.ts +657 -0
- package/scripts/ensure-maestro.cjs +79 -0
- package/scripts/ios-tunnel-keeper.sh +45 -0
- package/scripts/ios-wda-keeper.sh +66 -0
- package/scripts/sync-plan-catalog.d.mts +3 -0
- package/scripts/sync-plan-catalog.mjs +16 -0
- package/scripts/ui-parity-diff.py +65 -0
- package/scripts/ui-parity-requirements.txt +1 -0
- package/scripts/verify-manage-to-plans.mts +194 -0
- package/spec/app-ui-audit.schema.json +176 -0
- package/spec/blockers.yaml +79 -0
- package/spec/bugs.index.json +42 -0
- package/spec/design-dna.schema.json +38 -0
- package/spec/domain_rules.yaml +24 -0
- package/spec/flows/flow_manage-to-plans-64c3c61b.yaml +8 -0
- package/spec/flows/flow_screen-to-evidence-6c3ab309.yaml +7 -0
- package/spec/journey_graph.yaml +126 -0
- package/spec/journeys.graph.json +2618 -0
- package/spec/plans/dashboard-smoke.flow.yaml +20 -0
- package/spec/plans/example.flow.yaml +99 -0
- package/spec/plans/flow_api-keys-to-logout-7396cd5d.flow.yaml +33 -0
- package/spec/plans/flow_api-keys-to-screen-157e10a6.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-audits-176c1eb8.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-blockers-633282c8.flow.yaml +112 -0
- package/spec/plans/flow_manage-to-devices-ad57e202.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-logout-86c54656.flow.yaml +33 -0
- package/spec/plans/flow_manage-to-manage-resource-action-6325f99a.flow.yaml +185 -0
- package/spec/plans/flow_manage-to-parity-issues-01fa2579.flow.yaml +34 -0
- package/spec/plans/flow_manage-to-plans-64c3c61b.flow.yaml +123 -0
- package/spec/plans/flow_manage-to-screen-6857f915.flow.yaml +106 -0
- package/spec/plans/flow_manage-to-tasks-c5791c95.flow.yaml +33 -0
- package/spec/plans/flow_profile-to-screen-6045aa3d.flow.yaml +33 -0
- package/spec/plans/flow_register-to-api-auth-login-460a9cc8.flow.yaml +34 -0
- package/spec/plans/flow_screen-to-audits-07666356.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-blockers-091d258b.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-devices-cad72f75.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-evidence-6c3ab309.flow.yaml +126 -0
- package/spec/plans/flow_screen-to-parity-issues-2218e3ad.flow.yaml +34 -0
- package/spec/plans/flow_screen-to-plans-8232a94f.flow.yaml +33 -0
- package/spec/plans/flow_screen-to-tasks-540671d1.flow.yaml +33 -0
- package/spec/plans/login.flow.yaml +22 -0
- package/spec/plans/owner-operations.flow.yaml +20 -0
- package/spec/project_config.yaml +55 -0
- package/spec/roles.yaml +30 -0
- package/spec/schema.md +394 -0
- package/spec/test-case-results.schema.json +62 -0
- package/spec/test-cases.schema.json +85 -0
- package/spec/ui-parity-audit.schema.json +194 -0
- package/spec/ui-reverse-engineering.schema.json +94 -0
- package/src/billing/plan-catalog.test.ts +46 -0
- package/src/billing/plan-catalog.ts +199 -0
- package/src/integrations/aws-sigv4.test.ts +42 -0
- package/src/integrations/aws-sigv4.ts +72 -0
- package/src/integrations/device-farm.ts +155 -0
- package/src/integrations/github-app.test.ts +57 -0
- package/src/integrations/github-app.ts +143 -0
- package/src/integrations/gitlab.ts +81 -0
- package/src/integrations/temp-email.test.ts +123 -0
- package/src/integrations/temp-email.ts +175 -0
- package/src/integrations/testflight-feedback.test.ts +51 -0
- package/src/integrations/testflight-feedback.ts +173 -0
- package/src/integrations/webdriver-client.ts +131 -0
- package/src/mcp/server.test.ts +1220 -0
- package/src/mcp/server.ts +3064 -0
- package/src/mcp/write-test-cases.test.ts +287 -0
- package/src/registry/api-key-client.ts +39 -0
- package/src/registry/control-plane-client.ts +212 -0
- package/src/registry/migrations/0001_registry.sql +47 -0
- package/src/registry/migrations/0002_device_authorizations.sql +23 -0
- package/src/registry/migrations/0003_project_environments.sql +25 -0
- package/src/registry/migrations/0004_device_authorization_email.sql +1 -0
- package/src/registry/migrations/0005_testing_control_plane.sql +55 -0
- package/src/registry/migrations/0006_workspaces.sql +36 -0
- package/src/registry/migrations/0007_project_apps.sql +26 -0
- package/src/registry/migrations/0008_agent_tasks.sql +30 -0
- package/src/registry/migrations/0009_journey_revisions.sql +17 -0
- package/src/registry/migrations/0010_agent_task_journey.sql +2 -0
- package/src/registry/migrations/0011_run_journey_revision.sql +1 -0
- package/src/registry/migrations/0012_canonical_flow_execution.sql +10 -0
- package/src/registry/migrations/0013_device_sessions.sql +22 -0
- package/src/registry/migrations/0014_agent_task_step.sql +1 -0
- package/src/registry/migrations/0015_agent_task_attempts.sql +6 -0
- package/src/registry/migrations/0016_github_issue_tracker.sql +54 -0
- package/src/registry/migrations/0017_run_targets.sql +7 -0
- package/src/registry/migrations/0018_run_fix_from.sql +4 -0
- package/src/registry/migrations/0019_workspace_flags.sql +9 -0
- package/src/registry/migrations/0020_orgs.sql +40 -0
- package/src/registry/migrations/0021_billing_core.sql +58 -0
- package/src/registry/migrations/0022_cloud_runners.sql +19 -0
- package/src/registry/migrations/0023_signup.sql +4 -0
- package/src/registry/migrations/0024_billing.sql +67 -0
- package/src/registry/migrations/0025_notifications.sql +47 -0
- package/src/registry/migrations/0026_repo_bindings.sql +28 -0
- package/src/registry/migrations/0027_feedback.sql +29 -0
- package/src/registry/migrations/0028_devices.sql +48 -0
- package/src/registry/migrations/0029_sso.sql +31 -0
- package/src/registry/migrations/0030_workspace_domains.sql +18 -0
- package/src/registry/migrations/0031_gitlab_and_teams.sql +45 -0
- package/src/registry/migrations/0032_personas.sql +15 -0
- package/src/registry/migrations/0033_bugmole_rename.sql +11 -0
- package/src/registry/task-scheduling.test.ts +100 -0
- package/src/registry/task-scheduling.ts +80 -0
- package/src/registry-worker/ai/platform-model.ts +77 -0
- package/src/registry-worker/ai/routes.test.ts +88 -0
- package/src/registry-worker/ai/routes.ts +90 -0
- package/src/registry-worker/artifacts.test.ts +98 -0
- package/src/registry-worker/artifacts.ts +85 -0
- package/src/registry-worker/billing/billing-core.test.ts +175 -0
- package/src/registry-worker/billing/checkout-routes.ts +209 -0
- package/src/registry-worker/billing/enforcement.ts +69 -0
- package/src/registry-worker/billing/entitlements.ts +108 -0
- package/src/registry-worker/billing/ledger.ts +186 -0
- package/src/registry-worker/billing/paypal/api.ts +259 -0
- package/src/registry-worker/billing/paypal/client.ts +91 -0
- package/src/registry-worker/billing/paypal/provider.ts +143 -0
- package/src/registry-worker/billing/paypal.test.ts +466 -0
- package/src/registry-worker/billing/provider.ts +114 -0
- package/src/registry-worker/billing/routes.ts +66 -0
- package/src/registry-worker/billing/subscriptions.ts +780 -0
- package/src/registry-worker/billing/thresholds.ts +107 -0
- package/src/registry-worker/core.ts +308 -0
- package/src/registry-worker/devices/devices.test.ts +185 -0
- package/src/registry-worker/devices/policy.ts +71 -0
- package/src/registry-worker/devices/routes.ts +453 -0
- package/src/registry-worker/domains/domains.test.ts +210 -0
- package/src/registry-worker/domains/routes.ts +139 -0
- package/src/registry-worker/domains.ts +88 -0
- package/src/registry-worker/email/sender.ts +75 -0
- package/src/registry-worker/env.d.ts +14716 -0
- package/src/registry-worker/features.ts +20 -0
- package/src/registry-worker/feedback/feedback.test.ts +230 -0
- package/src/registry-worker/feedback/format.ts +148 -0
- package/src/registry-worker/feedback/routes.ts +386 -0
- package/src/registry-worker/flags.ts +39 -0
- package/src/registry-worker/github/checks.test.ts +177 -0
- package/src/registry-worker/github/checks.ts +374 -0
- package/src/registry-worker/gitlab/checks.test.ts +141 -0
- package/src/registry-worker/gitlab/checks.ts +349 -0
- package/src/registry-worker/hooks.ts +54 -0
- package/src/registry-worker/index.ts +2077 -0
- package/src/registry-worker/jobs/index.ts +29 -0
- package/src/registry-worker/jobs/retention.ts +68 -0
- package/src/registry-worker/mcp/mcp.test.ts +355 -0
- package/src/registry-worker/mcp/routes.ts +215 -0
- package/src/registry-worker/mcp/token.ts +126 -0
- package/src/registry-worker/mcp/tools.ts +563 -0
- package/src/registry-worker/notifications/alerts.ts +212 -0
- package/src/registry-worker/notifications/notifications.test.ts +298 -0
- package/src/registry-worker/notifications/outbox.ts +83 -0
- package/src/registry-worker/notifications/routes.ts +280 -0
- package/src/registry-worker/notifications/secrets.ts +49 -0
- package/src/registry-worker/notifications/slack.ts +96 -0
- package/src/registry-worker/notifications/teams.ts +46 -0
- package/src/registry-worker/org/audit.ts +116 -0
- package/src/registry-worker/org/routes.test.ts +163 -0
- package/src/registry-worker/org/routes.ts +302 -0
- package/src/registry-worker/personas/personas.test.ts +78 -0
- package/src/registry-worker/personas/routes.ts +100 -0
- package/src/registry-worker/repo-triggers.ts +20 -0
- package/src/registry-worker/routes/index.ts +74 -0
- package/src/registry-worker/run-events.ts +24 -0
- package/src/registry-worker/runner/dispatch.ts +219 -0
- package/src/registry-worker/runner/jobs.ts +43 -0
- package/src/registry-worker/runner/metering.ts +82 -0
- package/src/registry-worker/runner/policy.ts +59 -0
- package/src/registry-worker/runner/routes.ts +171 -0
- package/src/registry-worker/runner/runner.test.ts +358 -0
- package/src/registry-worker/runner/tokens.ts +93 -0
- package/src/registry-worker/runs.test.ts +60 -0
- package/src/registry-worker/signup/policy.ts +57 -0
- package/src/registry-worker/signup/routes.ts +106 -0
- package/src/registry-worker/signup/signup.test.ts +81 -0
- package/src/registry-worker/sso/aegis.ts +141 -0
- package/src/registry-worker/sso/membership.ts +157 -0
- package/src/registry-worker/sso/routes.ts +458 -0
- package/src/registry-worker/sso/sso.test.ts +344 -0
- package/src/registry-worker/testing/d1-shim.ts +180 -0
- package/src/registry-worker/testing/harness.ts +137 -0
- package/src/runner-worker/index.ts +108 -0
- package/src/runtime/ai-analysis.ts +97 -0
- package/src/runtime/ai-exploration.test.ts +32 -0
- package/src/runtime/ai-exploration.ts +69 -0
- package/src/runtime/ai-repair.ts +74 -0
- package/src/runtime/ai-work.test.ts +99 -0
- package/src/runtime/android-screen-record.test.ts +75 -0
- package/src/runtime/android-screen-record.ts +192 -0
- package/src/runtime/app-understanding.test.ts +123 -0
- package/src/runtime/app-understanding.ts +201 -0
- package/src/runtime/appium-driver.test.ts +179 -0
- package/src/runtime/appium-driver.ts +295 -0
- package/src/runtime/blocker-resolution.test.ts +113 -0
- package/src/runtime/blocker-resolution.ts +111 -0
- package/src/runtime/browser-matrix.integration.test.ts +212 -0
- package/src/runtime/browser-matrix.test.ts +143 -0
- package/src/runtime/browser-matrix.ts +200 -0
- package/src/runtime/canonical-flow.test.ts +52 -0
- package/src/runtime/config-validate.ts +185 -0
- package/src/runtime/continuous-execution.ts +291 -0
- package/src/runtime/cursor-applescript.ts +573 -0
- package/src/runtime/cursor-cli-driver.test.ts +78 -0
- package/src/runtime/cursor-cli-driver.ts +156 -0
- package/src/runtime/cursor-driver-example.ts +117 -0
- package/src/runtime/cursor-driver-index.ts +65 -0
- package/src/runtime/cursor-driver-init.ts +277 -0
- package/src/runtime/cursor-driver-run.test.ts +15 -0
- package/src/runtime/cursor-driver-run.ts +323 -0
- package/src/runtime/cursor-driver.ts +332 -0
- package/src/runtime/cursor-llm-example.ts +90 -0
- package/src/runtime/cursor-llm.ts +206 -0
- package/src/runtime/cursor-mcp-monitor.ts +386 -0
- package/src/runtime/device-clouds/browserstack.ts +73 -0
- package/src/runtime/device-clouds/device-farm.ts +52 -0
- package/src/runtime/device-clouds/index.ts +92 -0
- package/src/runtime/device-clouds/kobiton.ts +70 -0
- package/src/runtime/device-clouds/targets.ts +44 -0
- package/src/runtime/device-clouds/types.ts +62 -0
- package/src/runtime/diff-proposal.ts +84 -0
- package/src/runtime/discovery-task.test.ts +29 -0
- package/src/runtime/discovery-task.ts +284 -0
- package/src/runtime/driver-recovery.ts +69 -0
- package/src/runtime/driver.ts +79 -0
- package/src/runtime/environment.test.ts +104 -0
- package/src/runtime/environment.ts +137 -0
- package/src/runtime/executor.test.ts +509 -0
- package/src/runtime/executor.ts +921 -0
- package/src/runtime/explorer.test.ts +101 -0
- package/src/runtime/explorer.ts +1013 -0
- package/src/runtime/failure-analysis.test.ts +111 -0
- package/src/runtime/failure-analysis.ts +272 -0
- package/src/runtime/fixtures/fake-maestro.sh +36 -0
- package/src/runtime/flow-language.test.ts +268 -0
- package/src/runtime/flow-language.ts +414 -0
- package/src/runtime/init-wizard.ts +354 -0
- package/src/runtime/ios-screen-record.test.ts +68 -0
- package/src/runtime/ios-screen-record.ts +155 -0
- package/src/runtime/journey-editor.ts +452 -0
- package/src/runtime/journey-evidence.test.ts +161 -0
- package/src/runtime/journey-evidence.ts +180 -0
- package/src/runtime/journey-graph.test.ts +257 -0
- package/src/runtime/journey-graph.ts +170 -0
- package/src/runtime/legacy-names.ts +32 -0
- package/src/runtime/llm-example.ts +105 -0
- package/src/runtime/llm.ts +527 -0
- package/src/runtime/local-browser.test.ts +45 -0
- package/src/runtime/local-browser.ts +48 -0
- package/src/runtime/local-registry-stub.test.ts +325 -0
- package/src/runtime/local-registry-stub.ts +803 -0
- package/src/runtime/maestro-driver.test.ts +84 -0
- package/src/runtime/maestro-driver.ts +209 -0
- package/src/runtime/mole-voice.ts +21 -0
- package/src/runtime/nav-crawl.test.ts +100 -0
- package/src/runtime/nav-crawl.ts +153 -0
- package/src/runtime/pipeline.test.ts +405 -0
- package/src/runtime/pipeline.ts +833 -0
- package/src/runtime/planner.test.ts +37 -0
- package/src/runtime/planner.ts +274 -0
- package/src/runtime/platform-ai.ts +76 -0
- package/src/runtime/playwright-driver.test.ts +93 -0
- package/src/runtime/playwright-driver.ts +620 -0
- package/src/runtime/project-spec.ts +140 -0
- package/src/runtime/record-run-verdicts.ts +68 -0
- package/src/runtime/reporter.test.ts +56 -0
- package/src/runtime/reporter.ts +158 -0
- package/src/runtime/reset.test.ts +44 -0
- package/src/runtime/reset.ts +61 -0
- package/src/runtime/reviewer.test.ts +73 -0
- package/src/runtime/reviewer.ts +158 -0
- package/src/runtime/run-job.ts +136 -0
- package/src/runtime/run-once.test.ts +207 -0
- package/src/runtime/run-once.ts +168 -0
- package/src/runtime/run.ts +132 -0
- package/src/runtime/screen-recording.ts +34 -0
- package/src/runtime/serve-gateway.test.ts +74 -0
- package/src/runtime/serve-gateway.ts +164 -0
- package/src/runtime/serve-worker.test.ts +23 -0
- package/src/runtime/serve-worker.ts +278 -0
- package/src/runtime/site-discovery.test.ts +168 -0
- package/src/runtime/site-discovery.ts +308 -0
- package/src/runtime/target-runner.ts +144 -0
- package/src/runtime/test-case-verdicts.test.ts +94 -0
- package/src/runtime/test-case-verdicts.ts +120 -0
- package/src/runtime/ui-reverse-engineering/coordinator.test.ts +59 -0
- package/src/runtime/ui-reverse-engineering/coordinator.ts +391 -0
- package/src/runtime/ui-reverse-engineering/types.ts +197 -0
- package/src/runtime/web-suite.test.ts +97 -0
- package/src/runtime/web-suite.ts +176 -0
- package/src/storage/create-object-store.ts +144 -0
- package/src/storage/keys.ts +34 -0
- package/src/storage/local-artifact-server.test.ts +314 -0
- package/src/storage/local-artifact-server.ts +357 -0
- package/src/storage/object-store.test.ts +28 -0
- package/src/storage/object-store.ts +101 -0
- package/src/storage/registry-object-store.ts +88 -0
- package/src/storage/remote-object-store.ts +104 -0
- package/src/storage/storage-directory.test.ts +43 -0
- package/src/storage/storage-directory.ts +24 -0
- 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
|
+
}
|