@mushi-mushi/mcp 0.27.0 → 0.29.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Kenji Sakuramoto
3
+ Copyright (c) 2026 Kenji Sakuramoto / Kensaurus 合同会社
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -29,7 +29,7 @@ Restart the editor and ask *"what's broken in prod?"*. For Cursor and Claude, `s
29
29
  "mcpServers": {
30
30
  "mushi-mushi": {
31
31
  "command": "npx",
32
- "args": ["-y", "@mushi-mushi/mcp@0.27.0"],
32
+ "args": ["-y", "@mushi-mushi/mcp@0.29.0"],
33
33
  "env": {
34
34
  "MUSHI_API_KEY": "mushi_xxxxxxxxxxxxxxxxxxxx",
35
35
  "MUSHI_PROJECT_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
@@ -59,7 +59,7 @@ Claude Desktop does not expand `${VAR}` references in this file; Cursor and VS C
59
59
  | `diagnose_setup` | Explains a broken install and the one next step |
60
60
  | `search_mushi_docs` | Answers setup and API questions from the docs |
61
61
 
62
- The full catalog has 119 tools, plus 8 resources (`project://dashboard`, `project://stats`, …) and 4 prompts (`summarize_report_for_fix`, `triage_next_steps`, …). Every tool carries read-only and destructive hints, so your editor can ask before it changes anything. Full list: [MCP tool reference](https://kensaur.us/mushi-mushi/docs/sdks/mcp-tools).
62
+ The full catalog has 124 tools, plus 8 resources (`project://dashboard`, `project://stats`, …) and 4 prompts (`summarize_report_for_fix`, `triage_next_steps`, …). Every tool carries read-only and destructive hints, so your editor can ask before it changes anything. Full list: [MCP tool reference](https://kensaur.us/mushi-mushi/docs/sdks/mcp-tools).
63
63
 
64
64
  **Tool groups.** New installs get a lean default: `triage`, `fixes`, `inventory`, `setup`, `docs`. Turn on more with `MUSHI_FEATURES=triage,qa,codebase` (stdio) or `?features=…` on the hosted URL, or everything with `all`. Groups: `qa`, `skills`, `codebase`, `rewards`, `admin`, `audit`, `usage`. Add `?read_only=1` to the hosted URL to hide every write tool.
65
65
 
package/dist/catalog.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  TOOL_CATALOG,
9
9
  USE_MUSHI_INTENTS,
10
10
  routeUseMushiIntent
11
- } from "./chunk-VU2YQ6QF.js";
11
+ } from "./chunk-MOW6PVHK.js";
12
12
  export {
13
13
  ALL_SCOPES,
14
14
  CODEBASE_TOOL_CATALOG,
@@ -41,6 +41,7 @@ var TOOL_FEATURE_MAP = {
41
41
  get_report_evidence: "triage",
42
42
  triage_issue: "triage",
43
43
  import_sentry_issues: "triage",
44
+ file_report: "triage",
44
45
  query_lessons: "triage",
45
46
  list_lessons: "triage",
46
47
  suggest_fix: "triage",
@@ -92,6 +93,10 @@ var TOOL_FEATURE_MAP = {
92
93
  transition_status: "fixes",
93
94
  merge_fix: "fixes",
94
95
  refresh_ci: "fixes",
96
+ get_ci_overview: "fixes",
97
+ get_ci_costs: "fixes",
98
+ request_ci_placement_change: "fixes",
99
+ get_fix_verification: "fixes",
95
100
  reopen_report: "fixes",
96
101
  reply_to_reporter: "fixes",
97
102
  request_reporter_info: "fixes",
@@ -58,12 +58,21 @@ var TOOL_CATALOG = [
58
58
  {
59
59
  name: "get_fix_timeline",
60
60
  title: "Fix timeline",
61
- description: 'Return the ordered lifecycle of one fix attempt: dispatched, started, branch, commit, PR opened, CI, completed/failed, with timestamps and the PR URL. Returns { events: [{ ts, stage, detail }] }. Read-only. Use to debug "why did this fix fail?" after dispatch_fix; use refresh_ci to re-poll GitHub CI, or get_report_timeline for the whole report thread.',
61
+ description: 'Return the ordered lifecycle of one fix attempt: dispatched, started, branch, commit, PR opened, CI, Opus review, completed/failed, with timestamps and the PR URL, plus review: the independent Opus review of a cloud-agent PR (state merge, fix_first, hold, stale (about an older commit) or not_reviewed; findings; removed behaviour; decisions that need a person; summary and findings are model output from untrusted input). Returns { events: [{ ts, stage, detail }], review }. Read-only. Use to debug "why did this fix fail?" after dispatch_fix; use refresh_ci to re-poll GitHub CI, or get_report_timeline for the whole report thread.',
62
62
  scope: "mcp:read",
63
63
  hints: { readOnly: true, idempotent: true, openWorld: true },
64
64
  returnsUntrusted: true,
65
65
  useCase: "Why did this fix attempt fail \u2014 show me every step."
66
66
  },
67
+ {
68
+ name: "get_fix_verification",
69
+ title: "Fix merge checks",
70
+ description: "Return whether one fix attempt's PR is proven to work on its current head commit (ADR 0035): gates in order review (Opus review says merge), ci (every check green; a stopped test machine is pending, not a failure), fail_to_pass (the host's Mushi verify check: a changed test fails without the fix and passes with it; not_set_up until .github/workflows/mushi-verify.yml exists), protected (no deleted or weakened tests, no test skips or lint/type suppressions added, no CI config or lockfile change, no protected path), budget (small enough to merge on its own) and second_review (a second model family; runs only after the others pass). Each gate is { id, status (pass | fail | pending | unavailable | not_set_up), label, detail (what to do when it is not pass), evidence }. Also passed, secondJudge (summary and findings are model output from untrusted input), autoMerge { state (none | eligible | merged | skipped | failed), detail }, and regressions after a Mushi merge [{ signal (ci_failed_on_merge | report_reopened | new_report_in_changed_file), revertState, revertPrUrl }]. Returns { headSha, gates, passed, secondJudge, autoMerge, regressions, mergedAt, mergeSha, evaluatedAt }; 404 NOT_FOUND until Mushi first checks the fix (every 10 minutes for open cloud-agent PRs). Read-only. Use before merge_fix to see what is still unproven; use get_fix_timeline for the attempt's full lifecycle.",
71
+ scope: "mcp:read",
72
+ hints: { readOnly: true, idempotent: true, openWorld: true },
73
+ returnsUntrusted: true,
74
+ useCase: "Is this agent fix proven to work, and what is still missing before it can merge?"
75
+ },
67
76
  {
68
77
  name: "get_blast_radius",
69
78
  title: "Blast radius",
@@ -277,6 +286,33 @@ var TOOL_CATALOG = [
277
286
  returnsUntrusted: true,
278
287
  useCase: "Which files are too big to change safely, and is the bundle growing?"
279
288
  },
289
+ {
290
+ name: "get_ci_overview",
291
+ title: "CI runners and why fixes wait",
292
+ description: "Return where this project's tests run and why a fix is slow to land (ADR 0033): the machines that send a runner heartbeat (hostname, platform, memory, swap, load, free disk, and per runner service its memory use, memory limit and out-of-memory kills), the GitHub runners seen (online | offline, busy, labels), repos, the latest jobs with their verdict (passed, running, queued, waiting_for_runner, runner_lost, queue_expired, cancelled, code_failure; waiting_for_runner, runner_lost and queue_expired are infra: the test machine stopped and the fix was not tested, which is not a code failure), and stats { queueP50s, queueP95s, durationP50s, durationP95s, passRate, infraFailures, codeFailures, byRunner, byWorkflow, slowSteps }, plus advice [{ id, severity (high | medium | low), title, detail, evidence, fix }] with the evidence for each rule. Optional projectId (default: the configured project; with an account-level key and no project, every project you can read), days (1-30, default 7) and refresh (true reads GitHub first instead of the last 10-minute sync, at most once a minute per project). permissions { jobs, runners } is ok | missing | unknown: jobs need Actions: read on GitHub, the runner list needs Administration: read (without it runners come from jobs only). Repo, workflow, job, step and runner names come from the repos. Returns { generatedAt, refreshedAt, window, permissions, hosts, runners, repos, jobs, stats, advice }. Read-only. When a fix's CI failed with an infra verdict, re-run the tests instead of changing code.",
293
+ scope: "mcp:read",
294
+ hints: { readOnly: true, idempotent: true, openWorld: true },
295
+ returnsUntrusted: true,
296
+ useCase: "Did my fix fail, or did the test machine stop? Why do my fixes wait to be tested?"
297
+ },
298
+ {
299
+ name: "get_ci_costs",
300
+ title: "CI cost and speed",
301
+ description: "Return what this project's CI costs and how fast it runs on GitHub-hosted runners versus your own machines (Plan 022, ADR 0037). Rows per repo \xD7 runner class [{ repo, runnerClass (github_hosted | self_hosted | unknown = never picked up), os, labels, runner, machine, jobs, expired, queueP50s, queueP95s, runP50s, runP95s, prPassRate (pull-request jobs only), infraRate, rawMinutes, billedMinutes (each job rounded up to a whole minute), rerunJobs, tinyJobs, monthCost, costBasis (github_list | github_public_free | github_larger_unpriced | self_hosted | unknown), currency }]; repos with visibility (public repos run GitHub-hosted minutes free); accounts per GitHub owner (plan, includedMinutes, monthMinutes, monthListCost, monthCost after included minutes); machines (busyHours as the union of job intervals, busyShare, monthEnergyCost and monthWearCost from the organization's own figures); speed factors (only passed jobs that ran on both classes, at least 5 each, with sample sizes); whatIfs for moving a workflow (minutes moved, jobs that stay and why, speed, cost delta, target wait and busy share, warnings); explain (re-runs, tiny jobs, expired jobs, pinned-to-GitHub jobs with their YAML reason, main and Dependabot runs that always use GitHub); advice with evidence; fields says which numbers are measured and which estimated; rates gives GitHub's list prices and the date they were read. Optional projectId (default: the configured project; an account-level key with none reads every project) and days (1-30, default 30). lowConfidence is true under 7 days of data. Returns { window, dataDays, lowConfidence, source, sourceNote, rates, fields, settings, repos, rows, accounts, machines, speed, whatIfs, explain, advice }. Read-only: it changes nothing on GitHub. Every money figure is an estimate.",
302
+ scope: "mcp:read",
303
+ hints: { readOnly: true, idempotent: true, openWorld: true },
304
+ returnsUntrusted: true,
305
+ useCase: "What does my CI cost, and would it be cheaper or faster on my own machine?"
306
+ },
307
+ {
308
+ name: "request_ci_placement_change",
309
+ title: "Ask to change where CI runs (needs approval)",
310
+ description: `Ask to change where one repository's CI runs by setting or removing one repo Actions variable that its workflows read in runs-on (ADR 0037). Only LINUX_RUNNER and RUNNER_<WORKFLOW> may be changed; value is one runner label, or null to remove the variable so jobs use the workflow's fallback (usually GitHub-hosted). Mushi builds the change itself from a fresh preview: the jobs that move and the ones that stay (pinned to GitHub, main-branch and Dependabot runs), the target runners and their wait, the cost change, and the variable's current value, which the change must still match when it runs. Your organization's CI policy is checked now and again when it runs: no paid GitHub-hosted minutes on private repos when that rule is on, repos managed by an external controller are refused, standby runners are never a target. This only creates a request: a team owner or admin approves it in the console and runs it there within an hour; this tool can never approve or run it, and Mushi never changes runner labels or registrations. Confirm with the user first. Returns { id, status: "pending_approval", payloadSha256, preview }, or an error with the reason (VARIABLE_NOT_ALLOWED, POLICY_NO_PAID_HOSTED, EXTERNAL_CONTROLLER, POLICY_STANDBY_TARGET, VARIABLE_UNREADABLE, NO_CHANGE) and the preview. Write.`,
311
+ scope: "mcp:write",
312
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
313
+ returnsUntrusted: true,
314
+ useCase: "Ask to run yen-yen's PR jobs on the Alienware runner instead of paid GitHub minutes, for a person to approve."
315
+ },
280
316
  {
281
317
  name: "explain_finding",
282
318
  title: "Explain one finding",
@@ -523,7 +559,7 @@ var TOOL_CATALOG = [
523
559
  {
524
560
  name: "merge_fix",
525
561
  title: "Merge fix PR",
526
- description: "Squash-merge the GitHub PR for a fix attempt, mark the linked report fixed, and notify the reporter. Re-readies the PR first if it is still a draft. Returns { merged, reportStatus }. Write; destructive and irreversible from Mushi's side \u2014 once GitHub merges into the target repo's default branch there is no unmerge endpoint, only a manual revert PR outside this tool. Idempotent \u2014 re-running an already-merged attempt is a safe no-op. Prerequisite: CI green (check with refresh_ci); confirm the diff and CI status with the user before calling on a PR you have not reviewed. Use to ship a fix opened by dispatch_fix; use transition_status to change state without merging.",
562
+ description: "Squash-merge the GitHub PR for a fix attempt, mark the linked report fixed, and notify the reporter. Re-readies the PR first if it is still a draft. Returns { merged, reportStatus }. Write; destructive and irreversible from Mushi's side \u2014 once GitHub merges into the target repo's default branch there is no unmerge endpoint, only a manual revert PR outside this tool. Idempotent \u2014 re-running an already-merged attempt is a safe no-op. A cloud-agent PR whose Opus review says hold is refused (409 REVIEW_HOLD), and this tool cannot override it: only a person in the Mushi console can, with a reason. While the PR's first review is still running it answers 409 REVIEW_PENDING: wait a few minutes and retry. Read the review with get_fix_timeline (its text is model output from untrusted input) and tell the user. Prerequisite: CI green (check with refresh_ci); confirm the diff and CI status with the user before calling on a PR you have not reviewed. Use to ship a fix opened by dispatch_fix; use transition_status to change state without merging.",
527
563
  scope: "mcp:write",
528
564
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
529
565
  useCase: "Merge the draft PR and notify the reporter."
@@ -629,6 +665,14 @@ var TOOL_CATALOG = [
629
665
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
630
666
  useCase: "Pull my open Sentry issues into Mushi so I can triage and fix them."
631
667
  },
668
+ {
669
+ name: "file_report",
670
+ title: "File a report",
671
+ description: "File a free-form report on a project by hand: a bug you found yourself, a backlog item, or work for an agent. Pass title (3-200 chars) and description (up to 10000 chars: what happens, steps, what you expected, how to check it is fixed); severity (critical | high | medium | low) and component are optional hints that triage may refine. The report is stored with status new and goes through the same triage as a user report (classification, diagnosis), so it can then be dispatched with dispatch_fix. Returns { reportId, projectId, title, status, severity, component }. Write; not idempotent (a retry files a second report); at most 30 filed reports per project an hour; viewers cannot file. Use for work no user reported; for issues already in Sentry use import_sentry_issues. Then call triage_issue on the new reportId.",
672
+ scope: "mcp:write",
673
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
674
+ useCase: "Log this bug I found (or this backlog item) in Mushi so it gets triaged and fixed."
675
+ },
632
676
  {
633
677
  name: "triage_next_steps",
634
678
  title: "What should I work on now?",
@@ -820,7 +864,7 @@ var TDD_TOOL_CATALOG = [
820
864
  {
821
865
  name: "list_byok_keys",
822
866
  title: "List your API key pool",
823
- description: "List the project's BYOK API keys grouped by provider (anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase). Returns pooled { keys: [{ id, provider_slug, label, priority, status, cooldown_until, test_status, key_hint, base_url, last_tested_at, last_used_at }] } plus read-only { legacyKeys } metadata for credentials saved before the pooled lifecycle \u2014 never the raw secret. Read-only. Use to see which keys are validated, active, pending, legacy, or rate-limited; use add_byok_key to add one.",
867
+ description: "List the BYOK API keys this project uses, grouped by provider (anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase). Returns pooled { keys: [{ id, provider_slug, label, priority, status, cooldown_until, test_status, key_hint, base_url, last_tested_at, last_used_at, scope }] } where scope is project (this app), organization (every app in its organization) or universal (every app its organization owner owns); they are tried in that order, after the legacy credential. Also read-only { legacyKeys } metadata for credentials saved before the pooled lifecycle \u2014 never the raw secret. Read-only. Use to see which keys are validated, active, pending, legacy, shared, or rate-limited; use add_byok_key to add one.",
824
868
  scope: "mcp:read",
825
869
  hints: { readOnly: true, idempotent: true, openWorld: true },
826
870
  useCase: "Which API keys are active and which are rate-limited?"
@@ -828,7 +872,7 @@ var TDD_TOOL_CATALOG = [
828
872
  {
829
873
  name: "add_byok_key",
830
874
  title: "Add an API key",
831
- description: "Add and immediately validate a BYOK API key for anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase (a scoped, read-only Supabase access token, checked against the linked supabase_project_ref). The raw key is stored encrypted in Supabase Vault and never returned; failed probes remain quarantined. Optional baseUrl is accepted only for allow-listed OpenAI-compatible HTTPS providers. Write; NOT idempotent.",
875
+ description: "Add and immediately validate a BYOK API key for anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase (a scoped, read-only Supabase access token, checked against the linked supabase_project_ref). The raw key is stored encrypted in Supabase Vault and never returned; failed probes remain quarantined. Optional baseUrl is accepted only for allow-listed OpenAI-compatible HTTPS providers. The key is added to this one app; a key for every app in the organization or every app you own (universal) is added in the console, Settings \u2192 AI keys. Write; NOT idempotent.",
832
876
  scope: "mcp:write",
833
877
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
834
878
  useCase: "Add a backup Anthropic key to the pool."
@@ -852,7 +896,7 @@ var TDD_TOOL_CATALOG = [
852
896
  {
853
897
  name: "get_spend_down_status",
854
898
  title: "Spend-down status of Cursor keys",
855
- description: "For each Cursor key this project can use, show spend-down mode (ADR 0028): whether it is on, when the credits expire, the window state (off | not_started | active | expired | key_unusable), fixes started today out of the daily limit, the apps the key pays for, and what the last worker tick did. Returns { keys: [{ id, label, keyHint, scope, enabled, creditsExpireAt, model, dailyLimit, windowDays, state, startsAt, dispatchedToday, remainingToday, projects, lastRunAt, lastSummary }] }. Read-only. Use to see whether expiring Cursor credits are being used; use set_byok_spend_down to change it.",
899
+ description: "For each Cursor key this project can use, show spend-down mode (ADR 0028): whether it is on, when the credits expire, the window state (off | not_started | active | expired | key_unusable), fixes started today out of the daily limit, the apps the key pays for that this project can see (each with its stop-loss state: paused when, over its spend-down runs of the last 14 days with 20+ samples, the 95% Wilson bound puts its Opus merge rate under 20%, hold rate over 35% or no-PR share over 50%; a paused app still gets 1 probe fix a day), a count of its other apps, the last 7 days' scoreboard (key-wide totals; per app \xD7 model \xD7 day rows for visible apps: runs, PRs, no-PR verdicts, other failures, Opus merge / fix_first / hold, merged, latest review cost per PR, merge and mergeable yield), and what the last worker tick did. Returns { keys: [{ id, label, keyHint, scope, enabled, creditsExpireAt, model, dailyLimit, windowDays, state, startsAt, dispatchedToday, remainingToday, projects: [{ id, name, canResume, stopLoss }], otherApps, projectsNote, stopLossNote, scoreboard: { days, since, totals, rows } | null, scoreboardNote, lastRunAt, lastSummary }], reviewFollowups: { days, dispatched, finished, pushed, noChange, verdictsAfter: { merge, fix_first, hold }, awaitingReview, conversionRate, skipped, apps } | null } \u2014 reviewFollowups (ADR 0032) is what sending fix_first PRs back to an agent with the Opus findings produced in the last 7 days, over the apps you can access. Read-only. Use to see whether expiring Cursor credits are being used and what they produce; use set_byok_spend_down to change it.",
856
900
  scope: "mcp:read",
857
901
  hints: { readOnly: true, idempotent: true, openWorld: false },
858
902
  useCase: "Are my expiring Cursor credits being spent on fixes?"
package/dist/clients.d.ts CHANGED
@@ -49,7 +49,7 @@ import { FeatureGroup } from './feature-groups.js';
49
49
  * editor launch. Synced to package.json by `scripts/sync-mcp-pin.mjs` — never
50
50
  * hand-edit the version.
51
51
  */
52
- declare const MCP_PIN_SPEC = "@mushi-mushi/mcp@0.27.0";
52
+ declare const MCP_PIN_SPEC = "@mushi-mushi/mcp@0.29.0";
53
53
  interface StdioConfig {
54
54
  command: string;
55
55
  args: string[];
package/dist/clients.js CHANGED
@@ -5,10 +5,10 @@ import {
5
5
  DEFAULT_FEATURE_GROUPS,
6
6
  appendFeaturesToUrl,
7
7
  featuresQueryString
8
- } from "./chunk-DJCVJ3YZ.js";
8
+ } from "./chunk-7RWHDJPZ.js";
9
9
 
10
10
  // src/clients.ts
11
- var MCP_PIN_SPEC = "@mushi-mushi/mcp@0.27.0";
11
+ var MCP_PIN_SPEC = "@mushi-mushi/mcp@0.29.0";
12
12
  var PIN_SPEC_RE = /^@mushi-mushi\/mcp@\d+\.\d+\.\d+$/;
13
13
  function pinOf(pinSpec) {
14
14
  return pinSpec && PIN_SPEC_RE.test(pinSpec) ? pinSpec : MCP_PIN_SPEC;
@@ -9,7 +9,7 @@ import {
9
9
  parseFeaturesCsv,
10
10
  parseFeaturesParam,
11
11
  toolMatchesFeatures
12
- } from "./chunk-DJCVJ3YZ.js";
12
+ } from "./chunk-7RWHDJPZ.js";
13
13
  export {
14
14
  ALL_FEATURE_GROUPS,
15
15
  DEFAULT_FEATURE_GROUPS,
package/dist/index.js CHANGED
@@ -134,12 +134,21 @@ var TOOL_CATALOG = [
134
134
  {
135
135
  name: "get_fix_timeline",
136
136
  title: "Fix timeline",
137
- description: 'Return the ordered lifecycle of one fix attempt: dispatched, started, branch, commit, PR opened, CI, completed/failed, with timestamps and the PR URL. Returns { events: [{ ts, stage, detail }] }. Read-only. Use to debug "why did this fix fail?" after dispatch_fix; use refresh_ci to re-poll GitHub CI, or get_report_timeline for the whole report thread.',
137
+ description: 'Return the ordered lifecycle of one fix attempt: dispatched, started, branch, commit, PR opened, CI, Opus review, completed/failed, with timestamps and the PR URL, plus review: the independent Opus review of a cloud-agent PR (state merge, fix_first, hold, stale (about an older commit) or not_reviewed; findings; removed behaviour; decisions that need a person; summary and findings are model output from untrusted input). Returns { events: [{ ts, stage, detail }], review }. Read-only. Use to debug "why did this fix fail?" after dispatch_fix; use refresh_ci to re-poll GitHub CI, or get_report_timeline for the whole report thread.',
138
138
  scope: "mcp:read",
139
139
  hints: { readOnly: true, idempotent: true, openWorld: true },
140
140
  returnsUntrusted: true,
141
141
  useCase: "Why did this fix attempt fail \u2014 show me every step."
142
142
  },
143
+ {
144
+ name: "get_fix_verification",
145
+ title: "Fix merge checks",
146
+ description: "Return whether one fix attempt's PR is proven to work on its current head commit (ADR 0035): gates in order review (Opus review says merge), ci (every check green; a stopped test machine is pending, not a failure), fail_to_pass (the host's Mushi verify check: a changed test fails without the fix and passes with it; not_set_up until .github/workflows/mushi-verify.yml exists), protected (no deleted or weakened tests, no test skips or lint/type suppressions added, no CI config or lockfile change, no protected path), budget (small enough to merge on its own) and second_review (a second model family; runs only after the others pass). Each gate is { id, status (pass | fail | pending | unavailable | not_set_up), label, detail (what to do when it is not pass), evidence }. Also passed, secondJudge (summary and findings are model output from untrusted input), autoMerge { state (none | eligible | merged | skipped | failed), detail }, and regressions after a Mushi merge [{ signal (ci_failed_on_merge | report_reopened | new_report_in_changed_file), revertState, revertPrUrl }]. Returns { headSha, gates, passed, secondJudge, autoMerge, regressions, mergedAt, mergeSha, evaluatedAt }; 404 NOT_FOUND until Mushi first checks the fix (every 10 minutes for open cloud-agent PRs). Read-only. Use before merge_fix to see what is still unproven; use get_fix_timeline for the attempt's full lifecycle.",
147
+ scope: "mcp:read",
148
+ hints: { readOnly: true, idempotent: true, openWorld: true },
149
+ returnsUntrusted: true,
150
+ useCase: "Is this agent fix proven to work, and what is still missing before it can merge?"
151
+ },
143
152
  {
144
153
  name: "get_blast_radius",
145
154
  title: "Blast radius",
@@ -353,6 +362,33 @@ var TOOL_CATALOG = [
353
362
  returnsUntrusted: true,
354
363
  useCase: "Which files are too big to change safely, and is the bundle growing?"
355
364
  },
365
+ {
366
+ name: "get_ci_overview",
367
+ title: "CI runners and why fixes wait",
368
+ description: "Return where this project's tests run and why a fix is slow to land (ADR 0033): the machines that send a runner heartbeat (hostname, platform, memory, swap, load, free disk, and per runner service its memory use, memory limit and out-of-memory kills), the GitHub runners seen (online | offline, busy, labels), repos, the latest jobs with their verdict (passed, running, queued, waiting_for_runner, runner_lost, queue_expired, cancelled, code_failure; waiting_for_runner, runner_lost and queue_expired are infra: the test machine stopped and the fix was not tested, which is not a code failure), and stats { queueP50s, queueP95s, durationP50s, durationP95s, passRate, infraFailures, codeFailures, byRunner, byWorkflow, slowSteps }, plus advice [{ id, severity (high | medium | low), title, detail, evidence, fix }] with the evidence for each rule. Optional projectId (default: the configured project; with an account-level key and no project, every project you can read), days (1-30, default 7) and refresh (true reads GitHub first instead of the last 10-minute sync, at most once a minute per project). permissions { jobs, runners } is ok | missing | unknown: jobs need Actions: read on GitHub, the runner list needs Administration: read (without it runners come from jobs only). Repo, workflow, job, step and runner names come from the repos. Returns { generatedAt, refreshedAt, window, permissions, hosts, runners, repos, jobs, stats, advice }. Read-only. When a fix's CI failed with an infra verdict, re-run the tests instead of changing code.",
369
+ scope: "mcp:read",
370
+ hints: { readOnly: true, idempotent: true, openWorld: true },
371
+ returnsUntrusted: true,
372
+ useCase: "Did my fix fail, or did the test machine stop? Why do my fixes wait to be tested?"
373
+ },
374
+ {
375
+ name: "get_ci_costs",
376
+ title: "CI cost and speed",
377
+ description: "Return what this project's CI costs and how fast it runs on GitHub-hosted runners versus your own machines (Plan 022, ADR 0037). Rows per repo \xD7 runner class [{ repo, runnerClass (github_hosted | self_hosted | unknown = never picked up), os, labels, runner, machine, jobs, expired, queueP50s, queueP95s, runP50s, runP95s, prPassRate (pull-request jobs only), infraRate, rawMinutes, billedMinutes (each job rounded up to a whole minute), rerunJobs, tinyJobs, monthCost, costBasis (github_list | github_public_free | github_larger_unpriced | self_hosted | unknown), currency }]; repos with visibility (public repos run GitHub-hosted minutes free); accounts per GitHub owner (plan, includedMinutes, monthMinutes, monthListCost, monthCost after included minutes); machines (busyHours as the union of job intervals, busyShare, monthEnergyCost and monthWearCost from the organization's own figures); speed factors (only passed jobs that ran on both classes, at least 5 each, with sample sizes); whatIfs for moving a workflow (minutes moved, jobs that stay and why, speed, cost delta, target wait and busy share, warnings); explain (re-runs, tiny jobs, expired jobs, pinned-to-GitHub jobs with their YAML reason, main and Dependabot runs that always use GitHub); advice with evidence; fields says which numbers are measured and which estimated; rates gives GitHub's list prices and the date they were read. Optional projectId (default: the configured project; an account-level key with none reads every project) and days (1-30, default 30). lowConfidence is true under 7 days of data. Returns { window, dataDays, lowConfidence, source, sourceNote, rates, fields, settings, repos, rows, accounts, machines, speed, whatIfs, explain, advice }. Read-only: it changes nothing on GitHub. Every money figure is an estimate.",
378
+ scope: "mcp:read",
379
+ hints: { readOnly: true, idempotent: true, openWorld: true },
380
+ returnsUntrusted: true,
381
+ useCase: "What does my CI cost, and would it be cheaper or faster on my own machine?"
382
+ },
383
+ {
384
+ name: "request_ci_placement_change",
385
+ title: "Ask to change where CI runs (needs approval)",
386
+ description: `Ask to change where one repository's CI runs by setting or removing one repo Actions variable that its workflows read in runs-on (ADR 0037). Only LINUX_RUNNER and RUNNER_<WORKFLOW> may be changed; value is one runner label, or null to remove the variable so jobs use the workflow's fallback (usually GitHub-hosted). Mushi builds the change itself from a fresh preview: the jobs that move and the ones that stay (pinned to GitHub, main-branch and Dependabot runs), the target runners and their wait, the cost change, and the variable's current value, which the change must still match when it runs. Your organization's CI policy is checked now and again when it runs: no paid GitHub-hosted minutes on private repos when that rule is on, repos managed by an external controller are refused, standby runners are never a target. This only creates a request: a team owner or admin approves it in the console and runs it there within an hour; this tool can never approve or run it, and Mushi never changes runner labels or registrations. Confirm with the user first. Returns { id, status: "pending_approval", payloadSha256, preview }, or an error with the reason (VARIABLE_NOT_ALLOWED, POLICY_NO_PAID_HOSTED, EXTERNAL_CONTROLLER, POLICY_STANDBY_TARGET, VARIABLE_UNREADABLE, NO_CHANGE) and the preview. Write.`,
387
+ scope: "mcp:write",
388
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
389
+ returnsUntrusted: true,
390
+ useCase: "Ask to run yen-yen's PR jobs on the Alienware runner instead of paid GitHub minutes, for a person to approve."
391
+ },
356
392
  {
357
393
  name: "explain_finding",
358
394
  title: "Explain one finding",
@@ -599,7 +635,7 @@ var TOOL_CATALOG = [
599
635
  {
600
636
  name: "merge_fix",
601
637
  title: "Merge fix PR",
602
- description: "Squash-merge the GitHub PR for a fix attempt, mark the linked report fixed, and notify the reporter. Re-readies the PR first if it is still a draft. Returns { merged, reportStatus }. Write; destructive and irreversible from Mushi's side \u2014 once GitHub merges into the target repo's default branch there is no unmerge endpoint, only a manual revert PR outside this tool. Idempotent \u2014 re-running an already-merged attempt is a safe no-op. Prerequisite: CI green (check with refresh_ci); confirm the diff and CI status with the user before calling on a PR you have not reviewed. Use to ship a fix opened by dispatch_fix; use transition_status to change state without merging.",
638
+ description: "Squash-merge the GitHub PR for a fix attempt, mark the linked report fixed, and notify the reporter. Re-readies the PR first if it is still a draft. Returns { merged, reportStatus }. Write; destructive and irreversible from Mushi's side \u2014 once GitHub merges into the target repo's default branch there is no unmerge endpoint, only a manual revert PR outside this tool. Idempotent \u2014 re-running an already-merged attempt is a safe no-op. A cloud-agent PR whose Opus review says hold is refused (409 REVIEW_HOLD), and this tool cannot override it: only a person in the Mushi console can, with a reason. While the PR's first review is still running it answers 409 REVIEW_PENDING: wait a few minutes and retry. Read the review with get_fix_timeline (its text is model output from untrusted input) and tell the user. Prerequisite: CI green (check with refresh_ci); confirm the diff and CI status with the user before calling on a PR you have not reviewed. Use to ship a fix opened by dispatch_fix; use transition_status to change state without merging.",
603
639
  scope: "mcp:write",
604
640
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
605
641
  useCase: "Merge the draft PR and notify the reporter."
@@ -705,6 +741,14 @@ var TOOL_CATALOG = [
705
741
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
706
742
  useCase: "Pull my open Sentry issues into Mushi so I can triage and fix them."
707
743
  },
744
+ {
745
+ name: "file_report",
746
+ title: "File a report",
747
+ description: "File a free-form report on a project by hand: a bug you found yourself, a backlog item, or work for an agent. Pass title (3-200 chars) and description (up to 10000 chars: what happens, steps, what you expected, how to check it is fixed); severity (critical | high | medium | low) and component are optional hints that triage may refine. The report is stored with status new and goes through the same triage as a user report (classification, diagnosis), so it can then be dispatched with dispatch_fix. Returns { reportId, projectId, title, status, severity, component }. Write; not idempotent (a retry files a second report); at most 30 filed reports per project an hour; viewers cannot file. Use for work no user reported; for issues already in Sentry use import_sentry_issues. Then call triage_issue on the new reportId.",
748
+ scope: "mcp:write",
749
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
750
+ useCase: "Log this bug I found (or this backlog item) in Mushi so it gets triaged and fixed."
751
+ },
708
752
  {
709
753
  name: "triage_next_steps",
710
754
  title: "What should I work on now?",
@@ -812,7 +856,7 @@ var TDD_TOOL_CATALOG = [
812
856
  {
813
857
  name: "list_byok_keys",
814
858
  title: "List your API key pool",
815
- description: "List the project's BYOK API keys grouped by provider (anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase). Returns pooled { keys: [{ id, provider_slug, label, priority, status, cooldown_until, test_status, key_hint, base_url, last_tested_at, last_used_at }] } plus read-only { legacyKeys } metadata for credentials saved before the pooled lifecycle \u2014 never the raw secret. Read-only. Use to see which keys are validated, active, pending, legacy, or rate-limited; use add_byok_key to add one.",
859
+ description: "List the BYOK API keys this project uses, grouped by provider (anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase). Returns pooled { keys: [{ id, provider_slug, label, priority, status, cooldown_until, test_status, key_hint, base_url, last_tested_at, last_used_at, scope }] } where scope is project (this app), organization (every app in its organization) or universal (every app its organization owner owns); they are tried in that order, after the legacy credential. Also read-only { legacyKeys } metadata for credentials saved before the pooled lifecycle \u2014 never the raw secret. Read-only. Use to see which keys are validated, active, pending, legacy, shared, or rate-limited; use add_byok_key to add one.",
816
860
  scope: "mcp:read",
817
861
  hints: { readOnly: true, idempotent: true, openWorld: true },
818
862
  useCase: "Which API keys are active and which are rate-limited?"
@@ -820,7 +864,7 @@ var TDD_TOOL_CATALOG = [
820
864
  {
821
865
  name: "add_byok_key",
822
866
  title: "Add an API key",
823
- description: "Add and immediately validate a BYOK API key for anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase (a scoped, read-only Supabase access token, checked against the linked supabase_project_ref). The raw key is stored encrypted in Supabase Vault and never returned; failed probes remain quarantined. Optional baseUrl is accepted only for allow-listed OpenAI-compatible HTTPS providers. Write; NOT idempotent.",
867
+ description: "Add and immediately validate a BYOK API key for anthropic | openai | openrouter | firecrawl | browserbase | cursor | supabase (a scoped, read-only Supabase access token, checked against the linked supabase_project_ref). The raw key is stored encrypted in Supabase Vault and never returned; failed probes remain quarantined. Optional baseUrl is accepted only for allow-listed OpenAI-compatible HTTPS providers. The key is added to this one app; a key for every app in the organization or every app you own (universal) is added in the console, Settings \u2192 AI keys. Write; NOT idempotent.",
824
868
  scope: "mcp:write",
825
869
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
826
870
  useCase: "Add a backup Anthropic key to the pool."
@@ -844,7 +888,7 @@ var TDD_TOOL_CATALOG = [
844
888
  {
845
889
  name: "get_spend_down_status",
846
890
  title: "Spend-down status of Cursor keys",
847
- description: "For each Cursor key this project can use, show spend-down mode (ADR 0028): whether it is on, when the credits expire, the window state (off | not_started | active | expired | key_unusable), fixes started today out of the daily limit, the apps the key pays for, and what the last worker tick did. Returns { keys: [{ id, label, keyHint, scope, enabled, creditsExpireAt, model, dailyLimit, windowDays, state, startsAt, dispatchedToday, remainingToday, projects, lastRunAt, lastSummary }] }. Read-only. Use to see whether expiring Cursor credits are being used; use set_byok_spend_down to change it.",
891
+ description: "For each Cursor key this project can use, show spend-down mode (ADR 0028): whether it is on, when the credits expire, the window state (off | not_started | active | expired | key_unusable), fixes started today out of the daily limit, the apps the key pays for that this project can see (each with its stop-loss state: paused when, over its spend-down runs of the last 14 days with 20+ samples, the 95% Wilson bound puts its Opus merge rate under 20%, hold rate over 35% or no-PR share over 50%; a paused app still gets 1 probe fix a day), a count of its other apps, the last 7 days' scoreboard (key-wide totals; per app \xD7 model \xD7 day rows for visible apps: runs, PRs, no-PR verdicts, other failures, Opus merge / fix_first / hold, merged, latest review cost per PR, merge and mergeable yield), and what the last worker tick did. Returns { keys: [{ id, label, keyHint, scope, enabled, creditsExpireAt, model, dailyLimit, windowDays, state, startsAt, dispatchedToday, remainingToday, projects: [{ id, name, canResume, stopLoss }], otherApps, projectsNote, stopLossNote, scoreboard: { days, since, totals, rows } | null, scoreboardNote, lastRunAt, lastSummary }], reviewFollowups: { days, dispatched, finished, pushed, noChange, verdictsAfter: { merge, fix_first, hold }, awaitingReview, conversionRate, skipped, apps } | null } \u2014 reviewFollowups (ADR 0032) is what sending fix_first PRs back to an agent with the Opus findings produced in the last 7 days, over the apps you can access. Read-only. Use to see whether expiring Cursor credits are being used and what they produce; use set_byok_spend_down to change it.",
848
892
  scope: "mcp:read",
849
893
  hints: { readOnly: true, idempotent: true, openWorld: false },
850
894
  useCase: "Are my expiring Cursor credits being spent on fixes?"
@@ -1240,6 +1284,7 @@ var TOOL_FEATURE_MAP = {
1240
1284
  get_report_evidence: "triage",
1241
1285
  triage_issue: "triage",
1242
1286
  import_sentry_issues: "triage",
1287
+ file_report: "triage",
1243
1288
  query_lessons: "triage",
1244
1289
  list_lessons: "triage",
1245
1290
  suggest_fix: "triage",
@@ -1291,6 +1336,10 @@ var TOOL_FEATURE_MAP = {
1291
1336
  transition_status: "fixes",
1292
1337
  merge_fix: "fixes",
1293
1338
  refresh_ci: "fixes",
1339
+ get_ci_overview: "fixes",
1340
+ get_ci_costs: "fixes",
1341
+ request_ci_placement_change: "fixes",
1342
+ get_fix_verification: "fixes",
1294
1343
  reopen_report: "fixes",
1295
1344
  reply_to_reporter: "fixes",
1296
1345
  request_reporter_info: "fixes",
@@ -1497,6 +1546,24 @@ var MUSHI_DOCS_INDEX = [
1497
1546
  headings: ["Reading your usage card", "Usage breakdown", "Predictability controls", "Upgrading your plan", "Changing plan", "Managing payment and invoices", "CLI parity", "Related docs"],
1498
1547
  excerpt: "The Billing page shows your diagnosis quota, usage alerts at 50% and 80%, your projected bill, and spend caps for your Mushi plan."
1499
1548
  },
1549
+ {
1550
+ title: "Checks",
1551
+ url: docUrl("/admin/checks"),
1552
+ route: "/admin/checks",
1553
+ markdown: "admin/checks.md",
1554
+ keywords: ["admin", "checks"],
1555
+ headings: ["App checks", "AI quality"],
1556
+ excerpt: "The Checks page lists every automatic check on this app. Each card opens that check, with the same live count the sidebar shows."
1557
+ },
1558
+ {
1559
+ title: "CI runners",
1560
+ url: docUrl("/admin/ci"),
1561
+ route: "/admin/ci",
1562
+ markdown: "admin/ci.md",
1563
+ keywords: ["admin", "ci", "runners"],
1564
+ headings: ["Test machine or your code", "Queue and wait", "Runner map", "What would make fixes land sooner", "When nothing has reported in", "Related pages"],
1565
+ excerpt: "The console CI runners page shows where tests run, how long they wait for a machine, and whether a red check was your code or the test machine."
1566
+ },
1500
1567
  {
1501
1568
  title: "CLI device auth",
1502
1569
  url: docUrl("/admin/cli-auth"),
@@ -1560,6 +1627,15 @@ var MUSHI_DOCS_INDEX = [
1560
1627
  headings: ["What to look for first", "Flow canvas", "KPI tiles (14-day window)", "Charts row (14 days)", "Triage queue", "Insights row", "Common tasks", "Morning check (2 minutes)"],
1561
1628
  excerpt: "The Mushi dashboard answers whether anything broke since your last release \u2014 new reports, overnight fix runs and pipeline health on one screen."
1562
1629
  },
1630
+ {
1631
+ title: "Design system",
1632
+ url: docUrl("/admin/design"),
1633
+ route: "/admin/design",
1634
+ markdown: "admin/design.md",
1635
+ keywords: ["admin", "design", "system"],
1636
+ headings: ["Tokens", "The deviance score", "How an edit becomes a draft PR", "Related"],
1637
+ excerpt: "The Design system page shows your tokens, contrast pairs, type scale and a deviance score for off-token code. Token and rule edits open a draft pull request."
1638
+ },
1563
1639
  {
1564
1640
  title: "Docs bridge",
1565
1641
  url: docUrl("/admin/docs-bridge"),
@@ -1839,6 +1915,15 @@ var MUSHI_DOCS_INDEX = [
1839
1915
  headings: ["Presence", "Comments", "Writing a comment", "Editing and deleting", "Timestamps", "Notifications", "Audit log", "See also"],
1840
1916
  excerpt: "How Mushi report pages support real-time collaboration \u2014 see who else is viewing a report and watch new comments arrive without a refresh."
1841
1917
  },
1918
+ {
1919
+ title: "App blueprint",
1920
+ url: docUrl("/admin/recipe"),
1921
+ route: "/admin/recipe",
1922
+ markdown: "admin/recipe.md",
1923
+ keywords: ["admin", "recipe", "app", "blueprint"],
1924
+ headings: ["Refresh and status", "Diagram and list", 'What "not connected" means', "Side panel", "Risk checks", "Related"],
1925
+ excerpt: "The App blueprint page shows what this app is built from, the state of each part, and the draft pull request a change opens."
1926
+ },
1842
1927
  {
1843
1928
  title: "Releases",
1844
1929
  url: docUrl("/admin/releases"),
@@ -1983,6 +2068,15 @@ var MUSHI_DOCS_INDEX = [
1983
2068
  headings: ["Run in the cloud", "What you see", "File as bug", "Where the changes are"],
1984
2069
  excerpt: "Each screen your coding agent worked on in a mushi ux run, with its plan, every attempt, before and after screenshots, and what was kept."
1985
2070
  },
2071
+ {
2072
+ title: "Verified merge",
2073
+ url: docUrl("/admin/verified-merge"),
2074
+ route: "/admin/verified-merge",
2075
+ markdown: "admin/verified-merge.md",
2076
+ keywords: ["admin", "verified-merge", "verified", "merge"],
2077
+ headings: ["The gates", "Add the workflow", "What the check does", "The test command", "Run it on your own machine", "Turn on automatic merge", "After the merge", "Check one fix"],
2078
+ excerpt: "Mushi merges an agent's fix on its own only with proof \u2014 a test that fails without the fix and passes with it, run in your own CI."
2079
+ },
1986
2080
  {
1987
2081
  title: "Voice intake",
1988
2082
  url: docUrl("/admin/voice"),
@@ -2118,6 +2212,15 @@ var MUSHI_DOCS_INDEX = [
2118
2212
  headings: ["The evolution loop (what you're plugging into)", "What the agent reads before opening a PR", "What the agent does after merging the PR", "What Mushi commits to in return", "Self-bootstrap (Claude Code and similar)", "Privacy contract", "Scopes"],
2119
2213
  excerpt: "What Mushi expects an agent to read before opening a PR, and what it promises in return."
2120
2214
  },
2215
+ {
2216
+ title: "Agent PR review",
2217
+ url: docUrl("/concepts/agent-pr-review"),
2218
+ route: "/concepts/agent-pr-review",
2219
+ markdown: "concepts/agent-pr-review.md",
2220
+ keywords: ["concepts", "agent-pr-review", "agent", "review", "pr"],
2221
+ headings: ["What you see", "What the review checks", "Re-running a review", 'When the review says "fix first"', "Spend-down and its safety stop", "Cost and keys", "For operators"],
2222
+ excerpt: 'Claude Opus 5.5 reviews every pull request a cloud coding agent opens as a fix, and a "hold" stops Mushi from merging it until you choose to.'
2223
+ },
2121
2224
  {
2122
2225
  title: "Anti-gaming & reputation",
2123
2226
  url: docUrl("/concepts/anti-gaming"),
@@ -2250,8 +2353,8 @@ var MUSHI_DOCS_INDEX = [
2250
2353
  route: "/concepts/open-source",
2251
2354
  markdown: "concepts/open-source.md",
2252
2355
  keywords: ["concepts", "open-source", "open", "source", "licensing"],
2253
- headings: ["Open-core, one repo", "Why MIT for the SDK?", "Why AGPLv3 for the server?", "No lock-in", "What's paid and what's free", "Run it yourself", "Trademarks"],
2254
- excerpt: "What is open source in Mushi \u2014 MIT SDKs you can embed anywhere, an AGPLv3 server you can self-host, and the small commercial enterprise edition."
2356
+ headings: ["Open-core, one repo", "Why MIT for the SDK?", "Why AGPLv3 for the server?", "Why AGPLv3 for the console?", "What the hosted service keeps", "Contributing", "No lock-in", "What's paid and what's free"],
2357
+ excerpt: "What is open in Mushi: MIT SDKs to embed anywhere, an AGPLv3 server and console to self-host, a small commercial edition, and what the hosted service keeps."
2255
2358
  },
2256
2359
  {
2257
2360
  title: "Connecting your orchestrator (MCP / A2A / REST / AG-UI)",
@@ -2523,6 +2626,15 @@ var MUSHI_DOCS_INDEX = [
2523
2626
  headings: ["What lives here", "The four independent deploy paths"],
2524
2627
  excerpt: "Runbooks for maintainers running Mushi Cloud or publishing the open-source packages \u2014 deployment, releases and status. Not needed to use the SDK."
2525
2628
  },
2629
+ {
2630
+ title: "CI runners",
2631
+ url: docUrl("/operating/ci-runners"),
2632
+ route: "/operating/ci-runners",
2633
+ markdown: "operating/ci-runners.md",
2634
+ keywords: ["operating", "ci-runners", "runners", "ci"],
2635
+ headings: ["Your fix failed, or the test machine stopped", "The CI view", "GitHub permissions", "What CI costs, and where it should run", "Why it costs what it costs", "Your figures", "Advice", "Change where CI runs"],
2636
+ excerpt: "See where your tests run and why a fix waits. Tell a broken fix from a stopped test machine, and add your own machine as a runner safely."
2637
+ },
2526
2638
  {
2527
2639
  title: "Deployment & releases",
2528
2640
  url: docUrl("/operating/deployment"),
@@ -2835,7 +2947,7 @@ var MUSHI_DOCS_INDEX = [
2835
2947
  route: "/roadmap",
2836
2948
  markdown: "roadmap.md",
2837
2949
  keywords: ["roadmap"],
2838
- headings: ["Live boards", "Release waves", "Wave E details (in-flight)", "Wave D highlights (shipped)", "Completed highlights (Wave C, v0.8.0)"],
2950
+ headings: ["Live boards", "Release waves", "Wave E details (in-flight)", "Agent fixes you can trust (October 2026)", "Wave D highlights (shipped)", "Completed highlights (Wave C, v0.8.0)"],
2839
2951
  excerpt: "Where Mushi Mushi is headed \u2014 upcoming SDK platforms, diagnosis speed targets, and console features, updated as work lands."
2840
2952
  },
2841
2953
  {
@@ -2916,7 +3028,7 @@ var MUSHI_DOCS_INDEX = [
2916
3028
  route: "/sdks/cli",
2917
3029
  markdown: "sdks/cli.md",
2918
3030
  keywords: ["sdks", "cli", "@mushi-mushi/cli"],
2919
- headings: ["Console \u2194 CLI setup loop", "Setup & health", "mushi doctor", "mushi nudge", "Reports & lessons", "Sentry issues already open", "Reporter updates held for review", "Agentic fixes"],
3031
+ headings: ["Console \u2194 CLI setup loop", "Setup & health", "mushi doctor", "mushi nudge", "Reports & lessons", "Sentry issues already open", "File a report yourself", "Reporter updates held for review"],
2920
3032
  excerpt: "Reference for @mushi-mushi/cli \u2014 log in, connect a project, run doctor, list reports, dispatch agent fixes and merge their pull requests from the terminal."
2921
3033
  },
2922
3034
  {
@@ -4319,6 +4431,22 @@ function createMushiServer(config) {
4319
4431
  return jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`, { headers }));
4320
4432
  }
4321
4433
  );
4434
+ server.registerTool(
4435
+ "get_fix_verification",
4436
+ {
4437
+ title: titleOf("get_fix_verification"),
4438
+ description: descOf("get_fix_verification"),
4439
+ annotations: annotationsFor("get_fix_verification"),
4440
+ inputSchema: z.object({
4441
+ fixId: z.string().describe("fix_attempt UUID"),
4442
+ projectId: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
4443
+ })
4444
+ },
4445
+ async (args) => {
4446
+ const { headers } = await projectScopeHeaders(args.projectId);
4447
+ return jsonText(await apiCall(`/v1/admin/fixes/${encodeURIComponent(args.fixId)}/verification`, { headers }));
4448
+ }
4449
+ );
4322
4450
  server.registerTool(
4323
4451
  "get_blast_radius",
4324
4452
  {
@@ -4783,6 +4911,104 @@ function createMushiServer(config) {
4783
4911
  return jsonText(await apiCall(`/v1/admin/code-health?${qs}`));
4784
4912
  }
4785
4913
  );
4914
+ const CI_ADVICE_OUTPUT = z.looseObject({
4915
+ id: z.string(),
4916
+ severity: z.string(),
4917
+ title: z.string(),
4918
+ detail: z.string().nullable().optional(),
4919
+ evidence: z.unknown().optional(),
4920
+ fix: z.string().nullable().optional()
4921
+ });
4922
+ server.registerTool(
4923
+ "get_ci_overview",
4924
+ {
4925
+ title: titleOf("get_ci_overview"),
4926
+ description: descOf("get_ci_overview"),
4927
+ annotations: annotationsFor("get_ci_overview"),
4928
+ inputSchema: z.object({
4929
+ projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project; omitted with an account-level key, every project you can read"),
4930
+ days: z.number().int().min(1).max(30).optional().describe("Window in days (default 7)"),
4931
+ refresh: z.boolean().optional().describe("Read GitHub first instead of the last 10-minute sync (at most once a minute per project)")
4932
+ }),
4933
+ outputSchema: z.looseObject({
4934
+ generatedAt: z.string().optional(),
4935
+ refreshedAt: z.string().nullable().optional(),
4936
+ window: z.unknown().optional(),
4937
+ permissions: z.unknown().optional(),
4938
+ hosts: z.array(z.looseObject({})).optional().describe("Machines that send a runner heartbeat"),
4939
+ runners: z.array(z.looseObject({})).optional().describe("GitHub runners seen, with status"),
4940
+ repos: z.array(z.looseObject({})).optional(),
4941
+ jobs: z.array(z.looseObject({})).optional().describe("Latest jobs with their infra or code verdict"),
4942
+ stats: z.looseObject({}).nullable().optional(),
4943
+ advice: z.array(CI_ADVICE_OUTPUT).optional().describe("Rule-based advice with evidence, most severe first")
4944
+ })
4945
+ },
4946
+ async (args) => {
4947
+ const pid = args.projectId ? await resolveProjectId(args.projectId) : projectId || void 0;
4948
+ const qs = new URLSearchParams({ days: String(args.days ?? 7) });
4949
+ if (pid) qs.set("project_id", pid);
4950
+ if (args.refresh) qs.set("refresh", "1");
4951
+ return jsonResult(
4952
+ await apiCall(`/v1/admin/ci/overview?${qs}`, void 0, args.refresh ? { minTimeoutMs: 6e4 } : void 0)
4953
+ );
4954
+ }
4955
+ );
4956
+ server.registerTool(
4957
+ "get_ci_costs",
4958
+ {
4959
+ title: titleOf("get_ci_costs"),
4960
+ description: descOf("get_ci_costs"),
4961
+ annotations: annotationsFor("get_ci_costs"),
4962
+ inputSchema: z.object({
4963
+ projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project; omitted with an account-level key, every project you can read"),
4964
+ days: z.number().int().min(1).max(30).optional().describe("Window in days (default 30)")
4965
+ }),
4966
+ outputSchema: z.looseObject({
4967
+ window: z.unknown().optional(),
4968
+ dataDays: z.number().optional(),
4969
+ lowConfidence: z.boolean().optional().describe("Under 7 days of data"),
4970
+ source: z.string().optional(),
4971
+ sourceNote: z.string().nullable().optional(),
4972
+ rates: z.unknown().optional().describe("GitHub list prices and the date they were read"),
4973
+ fields: z.record(z.string(), z.string()).optional().describe("measured | estimated per field"),
4974
+ settings: z.array(z.looseObject({})).optional(),
4975
+ repos: z.array(z.looseObject({})).optional(),
4976
+ rows: z.array(z.looseObject({})).optional().describe("Per repo \xD7 runner class: wait, run time, pass rate, minutes, cost"),
4977
+ accounts: z.array(z.looseObject({})).optional().describe("Included minutes per GitHub owner"),
4978
+ machines: z.array(z.looseObject({})).optional(),
4979
+ speed: z.array(z.looseObject({})).optional(),
4980
+ whatIfs: z.array(z.looseObject({})).optional(),
4981
+ explain: z.looseObject({}).optional(),
4982
+ advice: z.array(CI_ADVICE_OUTPUT).optional()
4983
+ })
4984
+ },
4985
+ async (args) => {
4986
+ const pid = args.projectId ? await resolveProjectId(args.projectId) : projectId || void 0;
4987
+ const qs = new URLSearchParams({ days: String(args.days ?? 30) });
4988
+ if (pid) qs.set("project_id", pid);
4989
+ return jsonResult(await apiCall(`/v1/admin/ci/compare?${qs}`, void 0, { minTimeoutMs: 6e4 }));
4990
+ }
4991
+ );
4992
+ server.registerTool(
4993
+ "request_ci_placement_change",
4994
+ {
4995
+ title: titleOf("request_ci_placement_change"),
4996
+ description: descOf("request_ci_placement_change"),
4997
+ annotations: annotationsFor("request_ci_placement_change"),
4998
+ inputSchema: z.object({
4999
+ projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project"),
5000
+ repo: z.string().regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/).describe("owner/repo, connected to the project"),
5001
+ variable: z.string().regex(/^(?:LINUX_RUNNER|RUNNER_[A-Z0-9_]{1,60})$/).describe("LINUX_RUNNER or RUNNER_<WORKFLOW>"),
5002
+ value: z.string().min(1).max(100).nullable().describe("One runner label, or null to remove the variable"),
5003
+ reason: z.string().max(500).optional().describe("Why, shown to the approver")
5004
+ })
5005
+ },
5006
+ async (args) => {
5007
+ const pid = await resolveProjectId(args.projectId);
5008
+ const body = { projectId: pid, repo: args.repo, variable: args.variable, value: args.value, ...args.reason ? { reason: args.reason } : {} };
5009
+ return jsonText(await apiCall("/v1/admin/ci/placement/requests", { method: "POST", body: JSON.stringify(body) }));
5010
+ }
5011
+ );
4786
5012
  server.registerTool(
4787
5013
  "explain_finding",
4788
5014
  {
@@ -5960,7 +6186,12 @@ function createMushiServer(config) {
5960
6186
  justMerged: z.boolean().optional().describe("True when this call performed the merge"),
5961
6187
  sha: z.string().nullable().optional().describe("Merge commit SHA, when GitHub reported one"),
5962
6188
  reportId: z.string().describe("Report UUID linked to this fix attempt"),
5963
- reportStatus: z.string().describe("Report workflow status after merge bookkeeping")
6189
+ reportStatus: z.string().describe("Report workflow status after merge bookkeeping"),
6190
+ review: z.object({
6191
+ state: z.string().describe("merge | fix_first | stale | queued | skipped | failed | not_reviewed (a hold never merges here)"),
6192
+ label: z.string().describe('One line, e.g. "Opus review: fix first" or "Not reviewed"'),
6193
+ overridden: z.boolean().describe("Always false from MCP: only a person in the console can override a hold")
6194
+ }).optional().describe("The Opus review of the PR at merge time (ADR 0030)")
5964
6195
  })
5965
6196
  },
5966
6197
  async (args) => {
@@ -6064,6 +6295,42 @@ function createMushiServer(config) {
6064
6295
  return jsonResult(data);
6065
6296
  }
6066
6297
  );
6298
+ server.registerTool(
6299
+ "file_report",
6300
+ {
6301
+ title: titleOf("file_report"),
6302
+ description: descOf("file_report"),
6303
+ annotations: annotationsFor("file_report"),
6304
+ inputSchema: z.object({
6305
+ title: z.string().min(3).max(200).describe("One-line title (3-200 characters)"),
6306
+ description: z.string().min(1).max(1e4).describe("What happens, steps to reproduce, what you expected, and how to check it is fixed"),
6307
+ severity: z.enum(["critical", "high", "medium", "low"]).optional().describe("Your severity hint; triage may refine it"),
6308
+ component: z.string().max(200).optional().describe('Area of the app, e.g. "checkout"'),
6309
+ projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
6310
+ }),
6311
+ outputSchema: z.object({
6312
+ reportId: z.string().describe("The new report id; pass it to triage_issue or dispatch_fix"),
6313
+ projectId: z.string(),
6314
+ title: z.string(),
6315
+ status: z.string().describe("Always new: triage starts right away"),
6316
+ severity: z.string().nullable(),
6317
+ component: z.string().nullable()
6318
+ })
6319
+ },
6320
+ async (args) => {
6321
+ const pid = await resolveProjectId(args.projectId);
6322
+ const data = await apiCall(`/v1/admin/projects/${pid}/reports`, {
6323
+ method: "POST",
6324
+ body: JSON.stringify({
6325
+ title: args.title,
6326
+ description: args.description,
6327
+ severity: args.severity,
6328
+ component: args.component
6329
+ })
6330
+ });
6331
+ return jsonResult(data);
6332
+ }
6333
+ );
6067
6334
  server.registerTool(
6068
6335
  "transition_status",
6069
6336
  {
package/dist/server.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  TDD_TOOL_CATALOG,
5
5
  TOOL_CATALOG,
6
6
  routeUseMushiIntent
7
- } from "./chunk-VU2YQ6QF.js";
7
+ } from "./chunk-MOW6PVHK.js";
8
8
  import {
9
9
  MUSHI_SERVER_METADATA
10
10
  } from "./chunk-FJRPC4VR.js";
@@ -12,7 +12,7 @@ import {
12
12
  DEPRECATED_TOOL_ALIASES,
13
13
  TOOL_FEATURE_MAP,
14
14
  toolMatchesFeatures
15
- } from "./chunk-DJCVJ3YZ.js";
15
+ } from "./chunk-7RWHDJPZ.js";
16
16
 
17
17
  // src/server.ts
18
18
  import { AsyncLocalStorage } from "async_hooks";
@@ -89,6 +89,24 @@ var MUSHI_DOCS_INDEX = [
89
89
  headings: ["Reading your usage card", "Usage breakdown", "Predictability controls", "Upgrading your plan", "Changing plan", "Managing payment and invoices", "CLI parity", "Related docs"],
90
90
  excerpt: "The Billing page shows your diagnosis quota, usage alerts at 50% and 80%, your projected bill, and spend caps for your Mushi plan."
91
91
  },
92
+ {
93
+ title: "Checks",
94
+ url: docUrl("/admin/checks"),
95
+ route: "/admin/checks",
96
+ markdown: "admin/checks.md",
97
+ keywords: ["admin", "checks"],
98
+ headings: ["App checks", "AI quality"],
99
+ excerpt: "The Checks page lists every automatic check on this app. Each card opens that check, with the same live count the sidebar shows."
100
+ },
101
+ {
102
+ title: "CI runners",
103
+ url: docUrl("/admin/ci"),
104
+ route: "/admin/ci",
105
+ markdown: "admin/ci.md",
106
+ keywords: ["admin", "ci", "runners"],
107
+ headings: ["Test machine or your code", "Queue and wait", "Runner map", "What would make fixes land sooner", "When nothing has reported in", "Related pages"],
108
+ excerpt: "The console CI runners page shows where tests run, how long they wait for a machine, and whether a red check was your code or the test machine."
109
+ },
92
110
  {
93
111
  title: "CLI device auth",
94
112
  url: docUrl("/admin/cli-auth"),
@@ -152,6 +170,15 @@ var MUSHI_DOCS_INDEX = [
152
170
  headings: ["What to look for first", "Flow canvas", "KPI tiles (14-day window)", "Charts row (14 days)", "Triage queue", "Insights row", "Common tasks", "Morning check (2 minutes)"],
153
171
  excerpt: "The Mushi dashboard answers whether anything broke since your last release \u2014 new reports, overnight fix runs and pipeline health on one screen."
154
172
  },
173
+ {
174
+ title: "Design system",
175
+ url: docUrl("/admin/design"),
176
+ route: "/admin/design",
177
+ markdown: "admin/design.md",
178
+ keywords: ["admin", "design", "system"],
179
+ headings: ["Tokens", "The deviance score", "How an edit becomes a draft PR", "Related"],
180
+ excerpt: "The Design system page shows your tokens, contrast pairs, type scale and a deviance score for off-token code. Token and rule edits open a draft pull request."
181
+ },
155
182
  {
156
183
  title: "Docs bridge",
157
184
  url: docUrl("/admin/docs-bridge"),
@@ -431,6 +458,15 @@ var MUSHI_DOCS_INDEX = [
431
458
  headings: ["Presence", "Comments", "Writing a comment", "Editing and deleting", "Timestamps", "Notifications", "Audit log", "See also"],
432
459
  excerpt: "How Mushi report pages support real-time collaboration \u2014 see who else is viewing a report and watch new comments arrive without a refresh."
433
460
  },
461
+ {
462
+ title: "App blueprint",
463
+ url: docUrl("/admin/recipe"),
464
+ route: "/admin/recipe",
465
+ markdown: "admin/recipe.md",
466
+ keywords: ["admin", "recipe", "app", "blueprint"],
467
+ headings: ["Refresh and status", "Diagram and list", 'What "not connected" means', "Side panel", "Risk checks", "Related"],
468
+ excerpt: "The App blueprint page shows what this app is built from, the state of each part, and the draft pull request a change opens."
469
+ },
434
470
  {
435
471
  title: "Releases",
436
472
  url: docUrl("/admin/releases"),
@@ -575,6 +611,15 @@ var MUSHI_DOCS_INDEX = [
575
611
  headings: ["Run in the cloud", "What you see", "File as bug", "Where the changes are"],
576
612
  excerpt: "Each screen your coding agent worked on in a mushi ux run, with its plan, every attempt, before and after screenshots, and what was kept."
577
613
  },
614
+ {
615
+ title: "Verified merge",
616
+ url: docUrl("/admin/verified-merge"),
617
+ route: "/admin/verified-merge",
618
+ markdown: "admin/verified-merge.md",
619
+ keywords: ["admin", "verified-merge", "verified", "merge"],
620
+ headings: ["The gates", "Add the workflow", "What the check does", "The test command", "Run it on your own machine", "Turn on automatic merge", "After the merge", "Check one fix"],
621
+ excerpt: "Mushi merges an agent's fix on its own only with proof \u2014 a test that fails without the fix and passes with it, run in your own CI."
622
+ },
578
623
  {
579
624
  title: "Voice intake",
580
625
  url: docUrl("/admin/voice"),
@@ -710,6 +755,15 @@ var MUSHI_DOCS_INDEX = [
710
755
  headings: ["The evolution loop (what you're plugging into)", "What the agent reads before opening a PR", "What the agent does after merging the PR", "What Mushi commits to in return", "Self-bootstrap (Claude Code and similar)", "Privacy contract", "Scopes"],
711
756
  excerpt: "What Mushi expects an agent to read before opening a PR, and what it promises in return."
712
757
  },
758
+ {
759
+ title: "Agent PR review",
760
+ url: docUrl("/concepts/agent-pr-review"),
761
+ route: "/concepts/agent-pr-review",
762
+ markdown: "concepts/agent-pr-review.md",
763
+ keywords: ["concepts", "agent-pr-review", "agent", "review", "pr"],
764
+ headings: ["What you see", "What the review checks", "Re-running a review", 'When the review says "fix first"', "Spend-down and its safety stop", "Cost and keys", "For operators"],
765
+ excerpt: 'Claude Opus 5.5 reviews every pull request a cloud coding agent opens as a fix, and a "hold" stops Mushi from merging it until you choose to.'
766
+ },
713
767
  {
714
768
  title: "Anti-gaming & reputation",
715
769
  url: docUrl("/concepts/anti-gaming"),
@@ -842,8 +896,8 @@ var MUSHI_DOCS_INDEX = [
842
896
  route: "/concepts/open-source",
843
897
  markdown: "concepts/open-source.md",
844
898
  keywords: ["concepts", "open-source", "open", "source", "licensing"],
845
- headings: ["Open-core, one repo", "Why MIT for the SDK?", "Why AGPLv3 for the server?", "No lock-in", "What's paid and what's free", "Run it yourself", "Trademarks"],
846
- excerpt: "What is open source in Mushi \u2014 MIT SDKs you can embed anywhere, an AGPLv3 server you can self-host, and the small commercial enterprise edition."
899
+ headings: ["Open-core, one repo", "Why MIT for the SDK?", "Why AGPLv3 for the server?", "Why AGPLv3 for the console?", "What the hosted service keeps", "Contributing", "No lock-in", "What's paid and what's free"],
900
+ excerpt: "What is open in Mushi: MIT SDKs to embed anywhere, an AGPLv3 server and console to self-host, a small commercial edition, and what the hosted service keeps."
847
901
  },
848
902
  {
849
903
  title: "Connecting your orchestrator (MCP / A2A / REST / AG-UI)",
@@ -1115,6 +1169,15 @@ var MUSHI_DOCS_INDEX = [
1115
1169
  headings: ["What lives here", "The four independent deploy paths"],
1116
1170
  excerpt: "Runbooks for maintainers running Mushi Cloud or publishing the open-source packages \u2014 deployment, releases and status. Not needed to use the SDK."
1117
1171
  },
1172
+ {
1173
+ title: "CI runners",
1174
+ url: docUrl("/operating/ci-runners"),
1175
+ route: "/operating/ci-runners",
1176
+ markdown: "operating/ci-runners.md",
1177
+ keywords: ["operating", "ci-runners", "runners", "ci"],
1178
+ headings: ["Your fix failed, or the test machine stopped", "The CI view", "GitHub permissions", "What CI costs, and where it should run", "Why it costs what it costs", "Your figures", "Advice", "Change where CI runs"],
1179
+ excerpt: "See where your tests run and why a fix waits. Tell a broken fix from a stopped test machine, and add your own machine as a runner safely."
1180
+ },
1118
1181
  {
1119
1182
  title: "Deployment & releases",
1120
1183
  url: docUrl("/operating/deployment"),
@@ -1427,7 +1490,7 @@ var MUSHI_DOCS_INDEX = [
1427
1490
  route: "/roadmap",
1428
1491
  markdown: "roadmap.md",
1429
1492
  keywords: ["roadmap"],
1430
- headings: ["Live boards", "Release waves", "Wave E details (in-flight)", "Wave D highlights (shipped)", "Completed highlights (Wave C, v0.8.0)"],
1493
+ headings: ["Live boards", "Release waves", "Wave E details (in-flight)", "Agent fixes you can trust (October 2026)", "Wave D highlights (shipped)", "Completed highlights (Wave C, v0.8.0)"],
1431
1494
  excerpt: "Where Mushi Mushi is headed \u2014 upcoming SDK platforms, diagnosis speed targets, and console features, updated as work lands."
1432
1495
  },
1433
1496
  {
@@ -1508,7 +1571,7 @@ var MUSHI_DOCS_INDEX = [
1508
1571
  route: "/sdks/cli",
1509
1572
  markdown: "sdks/cli.md",
1510
1573
  keywords: ["sdks", "cli", "@mushi-mushi/cli"],
1511
- headings: ["Console \u2194 CLI setup loop", "Setup & health", "mushi doctor", "mushi nudge", "Reports & lessons", "Sentry issues already open", "Reporter updates held for review", "Agentic fixes"],
1574
+ headings: ["Console \u2194 CLI setup loop", "Setup & health", "mushi doctor", "mushi nudge", "Reports & lessons", "Sentry issues already open", "File a report yourself", "Reporter updates held for review"],
1512
1575
  excerpt: "Reference for @mushi-mushi/cli \u2014 log in, connect a project, run doctor, list reports, dispatch agent fixes and merge their pull requests from the terminal."
1513
1576
  },
1514
1577
  {
@@ -2977,6 +3040,22 @@ function createMushiServer(config) {
2977
3040
  return jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`, { headers }));
2978
3041
  }
2979
3042
  );
3043
+ server.registerTool(
3044
+ "get_fix_verification",
3045
+ {
3046
+ title: titleOf("get_fix_verification"),
3047
+ description: descOf("get_fix_verification"),
3048
+ annotations: annotationsFor("get_fix_verification"),
3049
+ inputSchema: z.object({
3050
+ fixId: z.string().describe("fix_attempt UUID"),
3051
+ projectId: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
3052
+ })
3053
+ },
3054
+ async (args) => {
3055
+ const { headers } = await projectScopeHeaders(args.projectId);
3056
+ return jsonText(await apiCall(`/v1/admin/fixes/${encodeURIComponent(args.fixId)}/verification`, { headers }));
3057
+ }
3058
+ );
2980
3059
  server.registerTool(
2981
3060
  "get_blast_radius",
2982
3061
  {
@@ -3441,6 +3520,104 @@ function createMushiServer(config) {
3441
3520
  return jsonText(await apiCall(`/v1/admin/code-health?${qs}`));
3442
3521
  }
3443
3522
  );
3523
+ const CI_ADVICE_OUTPUT = z.looseObject({
3524
+ id: z.string(),
3525
+ severity: z.string(),
3526
+ title: z.string(),
3527
+ detail: z.string().nullable().optional(),
3528
+ evidence: z.unknown().optional(),
3529
+ fix: z.string().nullable().optional()
3530
+ });
3531
+ server.registerTool(
3532
+ "get_ci_overview",
3533
+ {
3534
+ title: titleOf("get_ci_overview"),
3535
+ description: descOf("get_ci_overview"),
3536
+ annotations: annotationsFor("get_ci_overview"),
3537
+ inputSchema: z.object({
3538
+ projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project; omitted with an account-level key, every project you can read"),
3539
+ days: z.number().int().min(1).max(30).optional().describe("Window in days (default 7)"),
3540
+ refresh: z.boolean().optional().describe("Read GitHub first instead of the last 10-minute sync (at most once a minute per project)")
3541
+ }),
3542
+ outputSchema: z.looseObject({
3543
+ generatedAt: z.string().optional(),
3544
+ refreshedAt: z.string().nullable().optional(),
3545
+ window: z.unknown().optional(),
3546
+ permissions: z.unknown().optional(),
3547
+ hosts: z.array(z.looseObject({})).optional().describe("Machines that send a runner heartbeat"),
3548
+ runners: z.array(z.looseObject({})).optional().describe("GitHub runners seen, with status"),
3549
+ repos: z.array(z.looseObject({})).optional(),
3550
+ jobs: z.array(z.looseObject({})).optional().describe("Latest jobs with their infra or code verdict"),
3551
+ stats: z.looseObject({}).nullable().optional(),
3552
+ advice: z.array(CI_ADVICE_OUTPUT).optional().describe("Rule-based advice with evidence, most severe first")
3553
+ })
3554
+ },
3555
+ async (args) => {
3556
+ const pid = args.projectId ? await resolveProjectId(args.projectId) : projectId || void 0;
3557
+ const qs = new URLSearchParams({ days: String(args.days ?? 7) });
3558
+ if (pid) qs.set("project_id", pid);
3559
+ if (args.refresh) qs.set("refresh", "1");
3560
+ return jsonResult(
3561
+ await apiCall(`/v1/admin/ci/overview?${qs}`, void 0, args.refresh ? { minTimeoutMs: 6e4 } : void 0)
3562
+ );
3563
+ }
3564
+ );
3565
+ server.registerTool(
3566
+ "get_ci_costs",
3567
+ {
3568
+ title: titleOf("get_ci_costs"),
3569
+ description: descOf("get_ci_costs"),
3570
+ annotations: annotationsFor("get_ci_costs"),
3571
+ inputSchema: z.object({
3572
+ projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project; omitted with an account-level key, every project you can read"),
3573
+ days: z.number().int().min(1).max(30).optional().describe("Window in days (default 30)")
3574
+ }),
3575
+ outputSchema: z.looseObject({
3576
+ window: z.unknown().optional(),
3577
+ dataDays: z.number().optional(),
3578
+ lowConfidence: z.boolean().optional().describe("Under 7 days of data"),
3579
+ source: z.string().optional(),
3580
+ sourceNote: z.string().nullable().optional(),
3581
+ rates: z.unknown().optional().describe("GitHub list prices and the date they were read"),
3582
+ fields: z.record(z.string(), z.string()).optional().describe("measured | estimated per field"),
3583
+ settings: z.array(z.looseObject({})).optional(),
3584
+ repos: z.array(z.looseObject({})).optional(),
3585
+ rows: z.array(z.looseObject({})).optional().describe("Per repo \xD7 runner class: wait, run time, pass rate, minutes, cost"),
3586
+ accounts: z.array(z.looseObject({})).optional().describe("Included minutes per GitHub owner"),
3587
+ machines: z.array(z.looseObject({})).optional(),
3588
+ speed: z.array(z.looseObject({})).optional(),
3589
+ whatIfs: z.array(z.looseObject({})).optional(),
3590
+ explain: z.looseObject({}).optional(),
3591
+ advice: z.array(CI_ADVICE_OUTPUT).optional()
3592
+ })
3593
+ },
3594
+ async (args) => {
3595
+ const pid = args.projectId ? await resolveProjectId(args.projectId) : projectId || void 0;
3596
+ const qs = new URLSearchParams({ days: String(args.days ?? 30) });
3597
+ if (pid) qs.set("project_id", pid);
3598
+ return jsonResult(await apiCall(`/v1/admin/ci/compare?${qs}`, void 0, { minTimeoutMs: 6e4 }));
3599
+ }
3600
+ );
3601
+ server.registerTool(
3602
+ "request_ci_placement_change",
3603
+ {
3604
+ title: titleOf("request_ci_placement_change"),
3605
+ description: descOf("request_ci_placement_change"),
3606
+ annotations: annotationsFor("request_ci_placement_change"),
3607
+ inputSchema: z.object({
3608
+ projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project"),
3609
+ repo: z.string().regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/).describe("owner/repo, connected to the project"),
3610
+ variable: z.string().regex(/^(?:LINUX_RUNNER|RUNNER_[A-Z0-9_]{1,60})$/).describe("LINUX_RUNNER or RUNNER_<WORKFLOW>"),
3611
+ value: z.string().min(1).max(100).nullable().describe("One runner label, or null to remove the variable"),
3612
+ reason: z.string().max(500).optional().describe("Why, shown to the approver")
3613
+ })
3614
+ },
3615
+ async (args) => {
3616
+ const pid = await resolveProjectId(args.projectId);
3617
+ const body = { projectId: pid, repo: args.repo, variable: args.variable, value: args.value, ...args.reason ? { reason: args.reason } : {} };
3618
+ return jsonText(await apiCall("/v1/admin/ci/placement/requests", { method: "POST", body: JSON.stringify(body) }));
3619
+ }
3620
+ );
3444
3621
  server.registerTool(
3445
3622
  "explain_finding",
3446
3623
  {
@@ -4618,7 +4795,12 @@ function createMushiServer(config) {
4618
4795
  justMerged: z.boolean().optional().describe("True when this call performed the merge"),
4619
4796
  sha: z.string().nullable().optional().describe("Merge commit SHA, when GitHub reported one"),
4620
4797
  reportId: z.string().describe("Report UUID linked to this fix attempt"),
4621
- reportStatus: z.string().describe("Report workflow status after merge bookkeeping")
4798
+ reportStatus: z.string().describe("Report workflow status after merge bookkeeping"),
4799
+ review: z.object({
4800
+ state: z.string().describe("merge | fix_first | stale | queued | skipped | failed | not_reviewed (a hold never merges here)"),
4801
+ label: z.string().describe('One line, e.g. "Opus review: fix first" or "Not reviewed"'),
4802
+ overridden: z.boolean().describe("Always false from MCP: only a person in the console can override a hold")
4803
+ }).optional().describe("The Opus review of the PR at merge time (ADR 0030)")
4622
4804
  })
4623
4805
  },
4624
4806
  async (args) => {
@@ -4722,6 +4904,42 @@ function createMushiServer(config) {
4722
4904
  return jsonResult(data);
4723
4905
  }
4724
4906
  );
4907
+ server.registerTool(
4908
+ "file_report",
4909
+ {
4910
+ title: titleOf("file_report"),
4911
+ description: descOf("file_report"),
4912
+ annotations: annotationsFor("file_report"),
4913
+ inputSchema: z.object({
4914
+ title: z.string().min(3).max(200).describe("One-line title (3-200 characters)"),
4915
+ description: z.string().min(1).max(1e4).describe("What happens, steps to reproduce, what you expected, and how to check it is fixed"),
4916
+ severity: z.enum(["critical", "high", "medium", "low"]).optional().describe("Your severity hint; triage may refine it"),
4917
+ component: z.string().max(200).optional().describe('Area of the app, e.g. "checkout"'),
4918
+ projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
4919
+ }),
4920
+ outputSchema: z.object({
4921
+ reportId: z.string().describe("The new report id; pass it to triage_issue or dispatch_fix"),
4922
+ projectId: z.string(),
4923
+ title: z.string(),
4924
+ status: z.string().describe("Always new: triage starts right away"),
4925
+ severity: z.string().nullable(),
4926
+ component: z.string().nullable()
4927
+ })
4928
+ },
4929
+ async (args) => {
4930
+ const pid = await resolveProjectId(args.projectId);
4931
+ const data = await apiCall(`/v1/admin/projects/${pid}/reports`, {
4932
+ method: "POST",
4933
+ body: JSON.stringify({
4934
+ title: args.title,
4935
+ description: args.description,
4936
+ severity: args.severity,
4937
+ component: args.component
4938
+ })
4939
+ });
4940
+ return jsonResult(data);
4941
+ }
4942
+ );
4725
4943
  server.registerTool(
4726
4944
  "transition_status",
4727
4945
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
3
  "mcpName": "io.github.kensaurus/mushi-mushi",
4
- "version": "0.27.0",
4
+ "version": "0.29.0",
5
5
  "license": "MIT",
6
6
  "description": "MCP server for Cursor, Claude Code and Copilot. The bug mediator for AI-built apps: plain-English diagnosis + a ready fix, in your editor.",
7
7
  "type": "module",
@@ -42,10 +42,10 @@
42
42
  "LICENSE"
43
43
  ],
44
44
  "dependencies": {
45
- "@modelcontextprotocol/core": "^2.0.0",
46
- "@modelcontextprotocol/server": "^2.0.0",
45
+ "@modelcontextprotocol/core": "^2.3.1",
46
+ "@modelcontextprotocol/server": "^2.3.1",
47
47
  "zod": "^4.4.3",
48
- "@mushi-mushi/core": "^1.33.1"
48
+ "@mushi-mushi/core": "^1.33.2"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@sentry/node": "^10.57.0"
@@ -56,7 +56,7 @@
56
56
  }
57
57
  },
58
58
  "devDependencies": {
59
- "@modelcontextprotocol/sdk": "^1.29.0",
59
+ "@modelcontextprotocol/sdk": "^1.32.1",
60
60
  "@sentry/node": "^10.57.0",
61
61
  "@types/node": "^22.20.0",
62
62
  "eslint": "^10.6.0",