@bugmole/cli 0.5.0 → 0.6.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 (153) hide show
  1. package/package.json +5 -2
  2. package/scripts/bugmole-admin.mjs +194 -0
  3. package/scripts/bugmole-admin.test.mjs +62 -0
  4. package/scripts/bugmole.test.ts +94 -2
  5. package/scripts/bugmole.ts +233 -46
  6. package/scripts/sync-byok.d.mts +2 -0
  7. package/scripts/sync-byok.mjs +18 -0
  8. package/scripts/sync-plan-catalog.d.mts +3 -0
  9. package/scripts/sync-plan-catalog.mjs +16 -6
  10. package/spec/domain_rules.yaml +139 -0
  11. package/spec/roles.yaml +20 -0
  12. package/spec/test-case-results.schema.json +20 -4
  13. package/spec/test-cases.schema.json +131 -16
  14. package/src/billing/plan-catalog.test.ts +82 -2
  15. package/src/billing/plan-catalog.ts +212 -9
  16. package/src/integrations/jira.ts +264 -0
  17. package/src/mcp/roles-and-review.test.ts +111 -0
  18. package/src/mcp/server.ts +358 -22
  19. package/src/mcp/write-test-cases.test.ts +68 -0
  20. package/src/registry/control-plane-client.ts +121 -6
  21. package/src/registry/migrations/0036_task_approval.sql +11 -0
  22. package/src/registry/migrations/0037_spec_proposals.sql +27 -0
  23. package/src/registry/migrations/0038_local_worker_seen.sql +5 -0
  24. package/src/registry/migrations/0039_project_secrets.sql +37 -0
  25. package/src/registry/migrations/0040_roles_and_task_review.sql +31 -0
  26. package/src/registry/migrations/0041_jira_integration.sql +75 -0
  27. package/src/registry/migrations/0042_subscription_gaps.sql +10 -0
  28. package/src/registry/migrations/0043_workspace_feature_overrides.sql +17 -0
  29. package/src/registry/migrations/0044_project_identity.sql +31 -0
  30. package/src/registry/project-identity.test.ts +99 -0
  31. package/src/registry/project-identity.ts +206 -0
  32. package/src/registry/roles.test.ts +66 -0
  33. package/src/registry/roles.ts +201 -0
  34. package/src/registry/task-scheduling.test.ts +94 -0
  35. package/src/registry/task-scheduling.ts +199 -2
  36. package/src/registry/test-case-revisions.test.ts +57 -0
  37. package/src/registry/test-case-revisions.ts +132 -0
  38. package/src/registry-worker/ai/routes.ts +3 -3
  39. package/src/registry-worker/artifacts.ts +23 -4
  40. package/src/registry-worker/billing/billing-core.test.ts +1 -1
  41. package/src/registry-worker/billing/checkout-routes.ts +53 -4
  42. package/src/registry-worker/billing/enforcement.ts +67 -12
  43. package/src/registry-worker/billing/paypal/api.ts +14 -0
  44. package/src/registry-worker/billing/paypal/client.ts +9 -0
  45. package/src/registry-worker/billing/paypal/provider.ts +10 -1
  46. package/src/registry-worker/billing/paypal.test.ts +108 -1
  47. package/src/registry-worker/billing/plan-gaps.test.ts +216 -0
  48. package/src/registry-worker/billing/provider.ts +8 -0
  49. package/src/registry-worker/billing/routes.ts +2 -1
  50. package/src/registry-worker/billing/subscriptions.ts +114 -4
  51. package/src/registry-worker/core.ts +22 -0
  52. package/src/registry-worker/devices/policy.ts +4 -5
  53. package/src/registry-worker/devices/routes.ts +8 -8
  54. package/src/registry-worker/feature-access.ts +165 -0
  55. package/src/registry-worker/feature-flags/admin.ts +300 -0
  56. package/src/registry-worker/feature-flags/feature-flags.test.ts +270 -0
  57. package/src/registry-worker/feature-flags/routes.ts +102 -0
  58. package/src/registry-worker/features.ts +9 -0
  59. package/src/registry-worker/feedback/routes.ts +2 -2
  60. package/src/registry-worker/flags.ts +80 -18
  61. package/src/registry-worker/github/checks.ts +4 -4
  62. package/src/registry-worker/hooks.ts +9 -0
  63. package/src/registry-worker/identity/oidc.test.ts +103 -0
  64. package/src/registry-worker/identity/oidc.ts +176 -0
  65. package/src/registry-worker/index.ts +477 -174
  66. package/src/registry-worker/jira/connection.ts +111 -0
  67. package/src/registry-worker/jira/jira.test.ts +404 -0
  68. package/src/registry-worker/jira/routes.ts +432 -0
  69. package/src/registry-worker/jira/workflow.ts +577 -0
  70. package/src/registry-worker/jobs/retention.ts +21 -5
  71. package/src/registry-worker/mcp/tools.ts +2 -1
  72. package/src/registry-worker/notifications/alerts.ts +4 -4
  73. package/src/registry-worker/notifications/notifications.test.ts +11 -0
  74. package/src/registry-worker/notifications/routes.ts +9 -10
  75. package/src/registry-worker/notifications/teams.ts +6 -2
  76. package/src/registry-worker/org/routes.ts +4 -4
  77. package/src/registry-worker/projects/identity.ts +194 -0
  78. package/src/registry-worker/projects/inactivity.ts +162 -0
  79. package/src/registry-worker/projects/projects.test.ts +283 -0
  80. package/src/registry-worker/proposals/proposals.test.ts +80 -0
  81. package/src/registry-worker/proposals/routes.ts +183 -0
  82. package/src/registry-worker/roles/roles.test.ts +84 -0
  83. package/src/registry-worker/roles/routes.ts +141 -0
  84. package/src/registry-worker/runner/dispatch.ts +1 -0
  85. package/src/registry-worker/runner/routes.ts +22 -5
  86. package/src/registry-worker/runner/runner.test.ts +21 -0
  87. package/src/registry-worker/runner/tokens.ts +8 -0
  88. package/src/registry-worker/secrets/crypto.ts +135 -0
  89. package/src/registry-worker/secrets/routes.ts +296 -0
  90. package/src/registry-worker/secrets/secrets.test.ts +237 -0
  91. package/src/registry-worker/signup/policy.ts +2 -2
  92. package/src/registry-worker/signup/routes.ts +12 -3
  93. package/src/registry-worker/sso/membership.ts +4 -2
  94. package/src/registry-worker/sso/routes.ts +24 -8
  95. package/src/registry-worker/sso/sso.test.ts +3 -2
  96. package/src/registry-worker/task-approval.test.ts +67 -0
  97. package/src/registry-worker/task-resume.test.ts +196 -0
  98. package/src/registry-worker/task-review.test.ts +121 -0
  99. package/src/runtime/appium-driver.ts +1 -1
  100. package/src/runtime/apply-proposals.test.ts +74 -0
  101. package/src/runtime/apply-proposals.ts +41 -0
  102. package/src/runtime/cloud-secrets.test.ts +201 -0
  103. package/src/runtime/cloud-secrets.ts +210 -0
  104. package/src/runtime/config-validate.ts +12 -9
  105. package/src/runtime/cursor-driver-run.ts +14 -3
  106. package/src/runtime/discovery-task.test.ts +26 -1
  107. package/src/runtime/discovery-task.ts +67 -6
  108. package/src/runtime/executor.ts +18 -10
  109. package/src/runtime/explorer.test.ts +32 -0
  110. package/src/runtime/explorer.ts +48 -0
  111. package/src/runtime/flow-language.ts +22 -8
  112. package/src/runtime/init-wizard.ts +112 -6
  113. package/src/runtime/journey-editor.ts +39 -2
  114. package/src/runtime/journey-graph.test.ts +18 -0
  115. package/src/runtime/local-registry-stub.test.ts +64 -0
  116. package/src/runtime/local-registry-stub.ts +255 -7
  117. package/src/runtime/local-vault.ts +65 -0
  118. package/src/runtime/pipeline.test.ts +23 -0
  119. package/src/runtime/pipeline.ts +61 -20
  120. package/src/runtime/planner.test.ts +24 -1
  121. package/src/runtime/planner.ts +88 -23
  122. package/src/runtime/playwright-driver.test.ts +21 -2
  123. package/src/runtime/playwright-driver.ts +49 -7
  124. package/src/runtime/project-identity.test.ts +161 -0
  125. package/src/runtime/project-identity.ts +232 -0
  126. package/src/runtime/project-roles.test.ts +107 -0
  127. package/src/runtime/project-roles.ts +158 -0
  128. package/src/runtime/propose-cli.ts +66 -0
  129. package/src/runtime/record-run-verdicts.test.ts +58 -0
  130. package/src/runtime/record-run-verdicts.ts +38 -4
  131. package/src/runtime/reporter.ts +1 -1
  132. package/src/runtime/reset.ts +2 -0
  133. package/src/runtime/roles-cli.test.ts +47 -0
  134. package/src/runtime/roles-cli.ts +60 -0
  135. package/src/runtime/run-once.ts +68 -5
  136. package/src/runtime/scenario-matrix.test.ts +41 -0
  137. package/src/runtime/scenario-matrix.ts +98 -0
  138. package/src/runtime/secret-driver.ts +77 -0
  139. package/src/runtime/secret-redaction.ts +69 -0
  140. package/src/runtime/secret-sources.test.ts +442 -0
  141. package/src/runtime/secret-sources.ts +176 -0
  142. package/src/runtime/secrets-cli.ts +146 -0
  143. package/src/runtime/serve-worker.ts +64 -6
  144. package/src/runtime/site-discovery.test.ts +47 -4
  145. package/src/runtime/site-discovery.ts +79 -9
  146. package/src/runtime/vault-federation.test.ts +37 -0
  147. package/src/runtime/vault-federation.ts +128 -0
  148. package/src/runtime/web-suite.ts +5 -2
  149. package/src/storage/create-object-store.ts +5 -1
  150. package/src/storage/object-store.ts +12 -1
  151. package/src/storage/test-case-results.test.ts +64 -0
  152. package/src/storage/test-case-results.ts +49 -0
  153. package/src/vendor/byok.ts +371 -0
@@ -0,0 +1,111 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import YAML from "yaml";
7
+ import { callToolInternal, writeSpecFile } from "./server.js";
8
+ import { startLocalRegistryStub } from "../runtime/local-registry-stub.js";
9
+
10
+ async function project(files: Record<string, string> = {}) {
11
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "bugmole-roles-mcp-"));
12
+ const specDir = path.join(directory, "spec");
13
+ await fs.mkdir(specDir, { recursive: true });
14
+ for (const [name, content] of Object.entries(files)) await fs.writeFile(path.join(specDir, name), content);
15
+ const cfg = {
16
+ project: { id: "demo" },
17
+ runtime: { spec_dir: specDir, artifacts_dir: directory },
18
+ storage: { provider: "local", localDirectory: directory },
19
+ };
20
+ return { directory, specDir, cfg };
21
+ }
22
+
23
+ /** Points the MCP's registry calls at a local stub for the duration of `run`. */
24
+ async function withRegistry(reviewMode: "manual" | "auto" | "extreme", run: (origin: string) => Promise<void>) {
25
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "bugmole-roles-registry-"));
26
+ const stub = await startLocalRegistryStub({ directory, reviewMode });
27
+ const saved = { url: process.env.BUGMOLE_REGISTRY_URL, key: process.env.BUGMOLE_API_KEY };
28
+ process.env.BUGMOLE_REGISTRY_URL = stub.origin;
29
+ process.env.BUGMOLE_API_KEY = "bm_test";
30
+ try {
31
+ await run(stub.origin);
32
+ } finally {
33
+ if (saved.url === undefined) delete process.env.BUGMOLE_REGISTRY_URL; else process.env.BUGMOLE_REGISTRY_URL = saved.url;
34
+ if (saved.key === undefined) delete process.env.BUGMOLE_API_KEY; else process.env.BUGMOLE_API_KEY = saved.key;
35
+ await stub.close();
36
+ await fs.rm(directory, { recursive: true, force: true });
37
+ }
38
+ }
39
+
40
+ test("bugmole_write_spec lets an agent suggest roles in roles.yaml, never confirm them", async () => {
41
+ const { specDir, cfg } = await project({ "roles.yaml": "roles:\n - name: user\n description: Signed in.\n" });
42
+ const result = writeSpecFile("roles.yaml", "roles:\n - name: user\n - name: admin\n description: Admin.\n", cfg);
43
+ assert.deepEqual(result.suggestedRoles, ["admin"]);
44
+ assert.match(result.message, /person confirms/);
45
+ const written = YAML.parse(await fs.readFile(path.join(specDir, "roles.yaml"), "utf8"));
46
+ assert.equal(written.roles[0].status, undefined);
47
+ assert.equal(written.roles[1].status, "suggested");
48
+ assert.equal(writeSpecFile("blockers.yaml", "blockers: []\n", cfg).suggestedRoles, undefined, "other spec files are written as given");
49
+ });
50
+
51
+ test("bugmole_write_test_cases refuses a case set for a role nobody confirmed", async () => {
52
+ const graph = {
53
+ version: 2,
54
+ nodes: [{ id: "screen:/", title: "Home", kind: "screen", route: "/" }],
55
+ edges: [],
56
+ journeys: [{ id: "flow:home", name: "Home", actor: "user", nodeIds: ["screen:/"], edgeIds: [], revision: 1, status: "draft" }],
57
+ };
58
+ const { cfg } = await project({
59
+ "journeys.graph.json": JSON.stringify(graph),
60
+ "roles.yaml": "roles:\n - name: user\n - name: admin\n status: suggested\n",
61
+ });
62
+ const caseSet = (actor: string) => ({
63
+ schemaVersion: 1, caseSetId: "home", journeyId: "flow:home", journeyRevision: 1, actor,
64
+ cases: [{ caseId: "TC-1", title: "Home loads", nodeId: "screen:/", intent: "Prove home renders", steps: ["Open /"], expected: ["Home renders"], confidence: "low" }],
65
+ });
66
+ await assert.rejects(callToolInternal("bugmole_write_test_cases", { caseSet: caseSet("admin") }, cfg), /only suggested/);
67
+ await assert.rejects(callToolInternal("bugmole_write_test_cases", { caseSet: caseSet("ghost") }, cfg), /not a confirmed role/);
68
+ const ok = await callToolInternal("bugmole_write_test_cases", { caseSet: caseSet("user") }, cfg);
69
+ assert.equal(ok.status, "success");
70
+ });
71
+
72
+ test("bugmole_role_propose sends a suggestion; bugmole_roles_list shows it waiting", async () => {
73
+ const { cfg } = await project({ "roles.yaml": "roles:\n - name: user\n" });
74
+ await withRegistry("extreme", async () => {
75
+ await assert.rejects(callToolInternal("bugmole_role_propose", { name: "admin" }, cfg), /evidence/);
76
+ const proposed = await callToolInternal("bugmole_role_propose", {
77
+ name: "Admin", description: "Admin area.", source: "role_gated_route", evidence: "route /admin",
78
+ }, cfg);
79
+ assert.equal(proposed.role.name, "admin");
80
+ assert.equal(proposed.role.status, "suggested");
81
+ assert.match(proposed.message, /only after a person confirms/);
82
+
83
+ const listed = await callToolInternal("bugmole_roles_list", {}, cfg);
84
+ assert.deepEqual(listed.confirmed, ["user"]);
85
+ assert.deepEqual(listed.suggested, ["admin"]);
86
+ assert.match(listed.nextSteps[0], /Roles page/);
87
+ });
88
+ });
89
+
90
+ test("bugmole_task_update says when a completed task went to review, and the note comes back with it", async () => {
91
+ const { cfg } = await project();
92
+ await withRegistry("manual", async (origin) => {
93
+ const created = await callToolInternal("bugmole_task_create", { title: "Fix it", description: "Fix the header", taskType: "code", idempotencyKey: "fix-1" }, cfg);
94
+ const taskId = created.task.id;
95
+ const claimed = await callToolInternal("bugmole_task_next", { workerId: "agent-1" }, cfg);
96
+ assert.equal(claimed.task.id, taskId);
97
+
98
+ const done = await callToolInternal("bugmole_task_update", { taskId, workerId: "agent-1", status: "completed", completionSummary: "Fixed" }, cfg);
99
+ assert.equal(done.inReview, true);
100
+ assert.equal(done.task.status, "in_review");
101
+ assert.match(done.nextSteps[0], /not treat this work as accepted/);
102
+
103
+ const sent = await fetch(`${origin}/api/tasks/${taskId}/send-back`, {
104
+ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ note: "Also fix the footer" }),
105
+ });
106
+ assert.equal(sent.status, 200);
107
+ const again = await callToolInternal("bugmole_task_next", { workerId: "agent-1" }, cfg);
108
+ assert.equal(again.task.id, taskId);
109
+ assert.match(again.message, /Also fix the footer/);
110
+ });
111
+ });