@mushi-mushi/mcp 0.22.1 → 0.24.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/README.md +4 -3
- package/dist/catalog.js +1 -1
- package/dist/{chunk-RPSPV4FC.js → chunk-2NW2VVWV.js} +363 -5
- package/dist/{chunk-LFSAKI6Z.js → chunk-F65T4IGT.js} +48 -0
- package/dist/clients.d.ts +1 -1
- package/dist/clients.js +2 -2
- package/dist/feature-groups.js +1 -1
- package/dist/index.js +1393 -65
- package/dist/server.js +917 -22
- package/package.json +2 -2
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-
|
|
7
|
+
} from "./chunk-2NW2VVWV.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-
|
|
15
|
+
} from "./chunk-F65T4IGT.js";
|
|
16
16
|
|
|
17
17
|
// src/server.ts
|
|
18
18
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -32,7 +32,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
32
32
|
route: "/",
|
|
33
33
|
markdown: "index.md",
|
|
34
34
|
keywords: ["know", "ai-built", "app", "broke", "fix", "ready"],
|
|
35
|
-
headings: ["See it in action", "What this is (and is not)", "Get started with the SDK", "Install in your framework", "One queue. Sentry flows in, fixes flow out."],
|
|
35
|
+
headings: ["This is what a diagnosis looks like", "See it in action", "What this is (and is not)", "Get started with the SDK", "Install in your framework", "One queue. Sentry flows in, fixes flow out."],
|
|
36
36
|
excerpt: "Your AI shipped it. Mushi tells you why it broke: a plain-English diagnosis and a ready fix in your editor. Open source, Sentry optional."
|
|
37
37
|
},
|
|
38
38
|
{
|
|
@@ -185,7 +185,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
185
185
|
route: "/admin/explore",
|
|
186
186
|
markdown: "admin/explore.md",
|
|
187
187
|
keywords: ["admin", "explore", "codebase", "atlas"],
|
|
188
|
-
headings: ["Tab groups", "Map views", "Graph", "Layers", "Search", "The detail panel"
|
|
188
|
+
headings: ["Tab groups", "Copy digest", "Map views", "Diagram", "Graph", "Layers", "Search", "The detail panel"],
|
|
189
189
|
excerpt: "Visual, searchable map of every indexed source file \u2014 explore architectural layers, import dependencies, and find code by meaning."
|
|
190
190
|
},
|
|
191
191
|
{
|
|
@@ -221,7 +221,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
221
221
|
route: "/admin/fixes",
|
|
222
222
|
markdown: "admin/fixes.md",
|
|
223
223
|
keywords: ["admin", "fixes", "fix", "drafts", "prs"],
|
|
224
|
-
headings: ["The fixes list", "Streaming view", "Spec-validation warnings", "Cursor Cloud Agent runs", "Dispatching a fix", "Webhooks", "CI feedback on open PRs"
|
|
224
|
+
headings: ["The fixes list", "Streaming view", "Spec-validation warnings", "Cursor Cloud Agent runs", "Notes-only pull requests", "Dispatching a fix", "Webhooks", "CI feedback on open PRs"],
|
|
225
225
|
excerpt: "The Fixes page tracks every draft pull request Mushi opened from a bug report, from the moment a fix run starts until the PR is ready or the run fails."
|
|
226
226
|
},
|
|
227
227
|
{
|
|
@@ -230,7 +230,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
230
230
|
route: "/admin/fullstack-audit",
|
|
231
231
|
markdown: "admin/fullstack-audit.md",
|
|
232
232
|
keywords: ["admin", "fullstack-audit", "fullstack", "audit", "full-stack"],
|
|
233
|
-
headings: ["Scorecard sections", "Prerequisites", "CLI", "Related pages"],
|
|
233
|
+
headings: ["Scorecard sections", "Open findings by check", 'Mushi setup checks and "Apply suggested caps"', "Prerequisites", "CLI", "Related pages"],
|
|
234
234
|
excerpt: "The Full-stack audit runs one health check across RLS gaps, recent backend errors and API contract drift, and returns a readable scorecard."
|
|
235
235
|
},
|
|
236
236
|
{
|
|
@@ -368,6 +368,15 @@ var MUSHI_DOCS_INDEX = [
|
|
|
368
368
|
headings: ["What you see", "Data source", "Related"],
|
|
369
369
|
excerpt: "The Overview page is the organization-wide portfolio view \u2014 seven-day activity and open tickets for every connected project at a glance."
|
|
370
370
|
},
|
|
371
|
+
{
|
|
372
|
+
title: "Portfolio",
|
|
373
|
+
url: docUrl("/admin/portfolio"),
|
|
374
|
+
route: "/admin/portfolio",
|
|
375
|
+
markdown: "admin/portfolio.md",
|
|
376
|
+
keywords: ["admin", "portfolio"],
|
|
377
|
+
headings: ["What you see", "When something could not be read", "Spend per app", "Daily digest", "Accounts and resilience", "From your editor", "Who can see it"],
|
|
378
|
+
excerpt: "The Portfolio page shows every app in a team at once, with each app's worst recipe state, hole checks and SDK version, plus fixes that apply across apps."
|
|
379
|
+
},
|
|
371
380
|
{
|
|
372
381
|
title: "Projects",
|
|
373
382
|
url: docUrl("/admin/projects"),
|
|
@@ -428,7 +437,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
428
437
|
route: "/admin/releases",
|
|
429
438
|
markdown: "admin/releases.md",
|
|
430
439
|
keywords: ["admin", "releases"],
|
|
431
|
-
headings: ["What it generates", "Creating a release", "Release entry fields", "SDK surface", "Related pages"],
|
|
440
|
+
headings: ["What it generates", "Creating a release", "Release automatically when you ship", "Release entry fields", "SDK surface", "Related pages"],
|
|
432
441
|
excerpt: "The Releases page drafts a changelog for each version from the fixes that shipped, crediting the users whose reports led to each fix."
|
|
433
442
|
},
|
|
434
443
|
{
|
|
@@ -683,6 +692,15 @@ var MUSHI_DOCS_INDEX = [
|
|
|
683
692
|
headings: ["Reporter reputation", "Anti-gaming detection", "Report fatigue prevention"],
|
|
684
693
|
excerpt: "How Mushi keeps bug reports honest \u2014 reporter reputation against spam and point farming, and prompt-fatigue limits so real users are not nagged."
|
|
685
694
|
},
|
|
695
|
+
{
|
|
696
|
+
title: "App recipe and design system",
|
|
697
|
+
url: docUrl("/concepts/app-recipe"),
|
|
698
|
+
route: "/concepts/app-recipe",
|
|
699
|
+
markdown: "concepts/app-recipe.md",
|
|
700
|
+
keywords: ["concepts", "app-recipe", "app", "recipe", "design", "system"],
|
|
701
|
+
headings: ["The five states", "mushi.recipe.json", "The design system page", "Directions", "The deviance check", "The deviance score", "When the score is too high", "Hole checks"],
|
|
702
|
+
excerpt: "The App Recipe records what your app is made of, so a diagnosis can say what changed and a fix can respect your design system."
|
|
703
|
+
},
|
|
686
704
|
{
|
|
687
705
|
title: "Architecture",
|
|
688
706
|
url: docUrl("/concepts/architecture"),
|
|
@@ -827,6 +845,15 @@ var MUSHI_DOCS_INDEX = [
|
|
|
827
845
|
headings: ["Setup default", "When host init wins", "Console tuning without rebuild", "Widget draft persistence", "Troubleshooting", "Related"],
|
|
828
846
|
excerpt: "How the Mushi SDK fetches widget and capture settings from the console at startup, how they merge with your init options, and when to turn it off."
|
|
829
847
|
},
|
|
848
|
+
{
|
|
849
|
+
title: "One login and one purchase across your apps",
|
|
850
|
+
url: docUrl("/concepts/shared-login"),
|
|
851
|
+
route: "/concepts/shared-login",
|
|
852
|
+
markdown: "concepts/shared-login.md",
|
|
853
|
+
keywords: ["concepts", "shared-login", "shared", "login", "one", "purchase", "across", "apps"],
|
|
854
|
+
headings: ["Signing in once", "What Mushi checks", "Keeping a purchase in every app", "Sending users between your apps", "Comparing apps"],
|
|
855
|
+
excerpt: "How to let people sign in once and keep what they bought across several of your apps, and what Mushi checks so it keeps working."
|
|
856
|
+
},
|
|
830
857
|
{
|
|
831
858
|
title: "Where the report button lives",
|
|
832
859
|
url: docUrl("/concepts/trigger-modes"),
|
|
@@ -1043,6 +1070,15 @@ var MUSHI_DOCS_INDEX = [
|
|
|
1043
1070
|
headings: ["What changes (high level)", "API mapping (Mushi-specific)", "Migration checklist", "Common gotchas", "Mushi behaviour during the migration", "References"],
|
|
1044
1071
|
excerpt: "Upgrade a Vue 2 app to Vue 3 and move from the vanilla Mushi web SDK to the @mushi-mushi/vue plugin, with an interactive migration checklist."
|
|
1045
1072
|
},
|
|
1073
|
+
{
|
|
1074
|
+
title: "Open metrics",
|
|
1075
|
+
url: docUrl("/open"),
|
|
1076
|
+
route: "/open",
|
|
1077
|
+
markdown: "open.md",
|
|
1078
|
+
keywords: ["open", "metrics"],
|
|
1079
|
+
headings: ["What is not on this page, and why", "Where the numbers come from"],
|
|
1080
|
+
excerpt: "Mushi Mushi's public numbers (releases, commits, contributors, npm downloads), fetched from public APIs at build time, with what each one does not mean."
|
|
1081
|
+
},
|
|
1046
1082
|
{
|
|
1047
1083
|
title: "Operating (maintainers)",
|
|
1048
1084
|
url: docUrl("/operating"),
|
|
@@ -1202,7 +1238,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
1202
1238
|
route: "/plugins/slack",
|
|
1203
1239
|
markdown: "plugins/slack.md",
|
|
1204
1240
|
keywords: ["plugins", "slack", "app"],
|
|
1205
|
-
headings: ["Setup", "What's on the card", "Acting from the channel", "Package", "Events", "Troubleshooting"],
|
|
1241
|
+
headings: ["Setup", "What's on the card", "Acting from the channel", "Slash commands", "Package", "Events", "Troubleshooting"],
|
|
1206
1242
|
excerpt: "Triage Mushi bug reports from Slack \u2014 each classified report posts as a card with its root cause and buttons to dispatch a fix, resolve or dismiss."
|
|
1207
1243
|
},
|
|
1208
1244
|
{
|
|
@@ -1328,7 +1364,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
1328
1364
|
route: "/quickstart/react-native",
|
|
1329
1365
|
markdown: "quickstart/react-native.md",
|
|
1330
1366
|
keywords: ["quickstart", "react-native", "react", "native"],
|
|
1331
|
-
headings: ["1. Install", "2. Mount the provider", "3. Verify"],
|
|
1367
|
+
headings: ["1. Install", "2. Mount the provider", "Optional native modules", "3. Verify"],
|
|
1332
1368
|
excerpt: "Add shake-to-report bug capture to a React Native or Expo app with @mushi-mushi/react-native \u2014 install, mount the provider, and verify the first report."
|
|
1333
1369
|
},
|
|
1334
1370
|
{
|
|
@@ -1445,7 +1481,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
1445
1481
|
route: "/sdks/cli",
|
|
1446
1482
|
markdown: "sdks/cli.md",
|
|
1447
1483
|
keywords: ["sdks", "cli", "@mushi-mushi/cli"],
|
|
1448
|
-
headings: ["Console \u2194 CLI setup loop", "Setup & health", "mushi doctor", "mushi nudge", "Reports & lessons", "
|
|
1484
|
+
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"],
|
|
1449
1485
|
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."
|
|
1450
1486
|
},
|
|
1451
1487
|
{
|
|
@@ -1607,7 +1643,7 @@ var MUSHI_DOCS_INDEX = [
|
|
|
1607
1643
|
route: "/sdks/react-native",
|
|
1608
1644
|
markdown: "sdks/react-native.md",
|
|
1609
1645
|
keywords: ["sdks", "react-native", "react", "native", "@mushi-mushi/react-native"],
|
|
1610
|
-
headings: ["Install", "Mount the provider", "Submit a report from a screen", "Offline queue", "What gets captured (v0.19+)", "Screenshots (react-native-view-shot)", "
|
|
1646
|
+
headings: ["Install", "Mount the provider", "Submit a report from a screen", "Offline queue", "What gets captured (v0.19+)", "Screenshots (react-native-view-shot)", "Theming the report sheet", "Manual breadcrumbs"],
|
|
1611
1647
|
excerpt: "Reference for @mushi-mushi/react-native \u2014 MushiProvider, hooks, shake-to-report, screenshots, identity and the offline queue for React Native and Expo."
|
|
1612
1648
|
},
|
|
1613
1649
|
{
|
|
@@ -2131,6 +2167,43 @@ function fixContextOf(report) {
|
|
|
2131
2167
|
bugOntologyTags: report.bug_ontology_tags ?? null
|
|
2132
2168
|
};
|
|
2133
2169
|
}
|
|
2170
|
+
var RECIPE_STATES = ["ok", "drift", "unknown", "not_connected", "error"];
|
|
2171
|
+
var DESIGN_EXCERPT_MAX_FILES = 20;
|
|
2172
|
+
function designExcerptFilesOf(report) {
|
|
2173
|
+
const out = [];
|
|
2174
|
+
const add = (path) => {
|
|
2175
|
+
if (typeof path !== "string") return;
|
|
2176
|
+
const p = path.trim();
|
|
2177
|
+
if (!p || p.includes(",") || out.includes(p) || out.length >= DESIGN_EXCERPT_MAX_FILES) return;
|
|
2178
|
+
out.push(p);
|
|
2179
|
+
};
|
|
2180
|
+
const attempts = Array.isArray(report.fix_attempts) ? report.fix_attempts : [];
|
|
2181
|
+
for (const attempt of attempts) {
|
|
2182
|
+
if (Array.isArray(attempt?.files_changed)) attempt.files_changed.forEach(add);
|
|
2183
|
+
}
|
|
2184
|
+
if (typeof report.fix_packet === "string") {
|
|
2185
|
+
for (const m of report.fix_packet.matchAll(/^### `([^`\n]+)`$/gm)) add(m[1]);
|
|
2186
|
+
}
|
|
2187
|
+
return out;
|
|
2188
|
+
}
|
|
2189
|
+
function designExcerptQueryOf(report) {
|
|
2190
|
+
const params = new URLSearchParams();
|
|
2191
|
+
const files = designExcerptFilesOf(report);
|
|
2192
|
+
if (files.length > 0) params.set("files", files.join(","));
|
|
2193
|
+
if (typeof report.id === "string" && /^[0-9a-f-]{36}$/i.test(report.id)) params.set("reportId", report.id);
|
|
2194
|
+
const query = params.toString();
|
|
2195
|
+
return query ? `?${query}` : "";
|
|
2196
|
+
}
|
|
2197
|
+
function recipeFromExcerpt(excerpt) {
|
|
2198
|
+
const row = excerpt && typeof excerpt === "object" ? excerpt : null;
|
|
2199
|
+
if (!row || !RECIPE_STATES.includes(row.state)) {
|
|
2200
|
+
return recipeError("The design excerpt came back without a state.");
|
|
2201
|
+
}
|
|
2202
|
+
return typeof row.note === "string" ? row : { ...row, note: "" };
|
|
2203
|
+
}
|
|
2204
|
+
function recipeError(note) {
|
|
2205
|
+
return { state: "error", note };
|
|
2206
|
+
}
|
|
2134
2207
|
function similarityQueryOf(report) {
|
|
2135
2208
|
for (const field of ["summary", "description"]) {
|
|
2136
2209
|
const value = report[field];
|
|
@@ -2327,7 +2400,15 @@ var GATE_IDS = [
|
|
|
2327
2400
|
"orphan_endpoint",
|
|
2328
2401
|
"unknown_call",
|
|
2329
2402
|
"schema_drift",
|
|
2330
|
-
"code_health"
|
|
2403
|
+
"code_health",
|
|
2404
|
+
"design_drift",
|
|
2405
|
+
"ci_drift",
|
|
2406
|
+
"deploy_drift",
|
|
2407
|
+
"env_drift",
|
|
2408
|
+
"radar",
|
|
2409
|
+
"portfolio_radar",
|
|
2410
|
+
"portfolio_radar_ci",
|
|
2411
|
+
"store_review"
|
|
2331
2412
|
];
|
|
2332
2413
|
var GATE_FINDING_SEVERITIES = ["info", "warn", "error"];
|
|
2333
2414
|
var SKILL_CATEGORIES = [
|
|
@@ -2392,6 +2473,22 @@ var FIX_CONTEXT_SHAPE = {
|
|
|
2392
2473
|
rootCause: z.unknown().describe("Stage-2 root cause, or null before Stage 2 runs"),
|
|
2393
2474
|
bugOntologyTags: z.unknown().describe("Bug ontology tags, or null")
|
|
2394
2475
|
};
|
|
2476
|
+
var RECIPE_EXCERPT_OUTPUT = z.looseObject({
|
|
2477
|
+
state: z.enum(RECIPE_STATES).describe("ok | drift | unknown | not_connected | error; unknown never means healthy"),
|
|
2478
|
+
note: z.string().describe("Why this state, in plain English"),
|
|
2479
|
+
set: z.string().nullable().optional().describe("The token set the excerpt was taken from"),
|
|
2480
|
+
tokens: z.array(z.unknown()).optional().describe("Most useful tokens first: { path, value, cssVar, ts }. Use these names, not literals."),
|
|
2481
|
+
findings: z.array(z.unknown()).optional().describe("Open design deviance findings in the files this fix touches"),
|
|
2482
|
+
score: z.number().nullable().optional().describe("Design deviance score 0\u2013100, lower is better; null = not scored"),
|
|
2483
|
+
truncated: z.boolean().optional().describe("True when the excerpt was cut to stay under 4 KB"),
|
|
2484
|
+
context: z.looseObject({
|
|
2485
|
+
schema: z.unknown().describe("{ state, note, snapshotAt, tables: [{ name, columns }], missing }: tables the stack trace names, from the latest schema snapshot"),
|
|
2486
|
+
deploy: z.unknown().describe("{ state: live | deployed_since_merge | not_live | probe_failed | unknown | no_merged_fix | error, note, lastFix, targets }"),
|
|
2487
|
+
radar: z.unknown().describe("{ state, note, checkedAt, findings: [{ rule, severity, message, fix }] }: open hole checks")
|
|
2488
|
+
}).optional().describe("Backend and release context for this bug; each section has its own state, and unknown never means healthy")
|
|
2489
|
+
}).describe(
|
|
2490
|
+
"Design excerpt (\u2264 4 KB) for the report's project: tokens with CSS var / TS names, the deviance score, findings in the files this fix touches, and context (tables the stack trace names, whether the last fix is live, open hole checks). Never null; when it could not be read, { state: 'error', note }."
|
|
2491
|
+
);
|
|
2395
2492
|
function isWrappedUntrusted(text) {
|
|
2396
2493
|
return text.startsWith('<mushi-data role="');
|
|
2397
2494
|
}
|
|
@@ -2414,10 +2511,11 @@ function createMushiServer(config) {
|
|
|
2414
2511
|
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
2415
2512
|
});
|
|
2416
2513
|
}
|
|
2417
|
-
async function apiCall(path, options) {
|
|
2514
|
+
async function apiCall(path, options, callOptions) {
|
|
2418
2515
|
const requestId = crypto.randomUUID().slice(0, 12);
|
|
2419
2516
|
const started = Date.now();
|
|
2420
|
-
const
|
|
2517
|
+
const effectiveTimeoutMs = Math.max(timeoutMs, callOptions?.minTimeoutMs ?? 0);
|
|
2518
|
+
const timeoutSignal = AbortSignal.timeout(effectiveTimeoutMs);
|
|
2421
2519
|
const toolCall = toolCallContext.getStore();
|
|
2422
2520
|
let res;
|
|
2423
2521
|
try {
|
|
@@ -2454,11 +2552,11 @@ function createMushiServer(config) {
|
|
|
2454
2552
|
function classifyTransportError(err) {
|
|
2455
2553
|
const durationMs2 = Date.now() - started;
|
|
2456
2554
|
if (timeoutSignal.aborted) {
|
|
2457
|
-
apiLog.warn("api.timeout", { path, requestId, durationMs: durationMs2, timeoutMs });
|
|
2555
|
+
apiLog.warn("api.timeout", { path, requestId, durationMs: durationMs2, timeoutMs: effectiveTimeoutMs });
|
|
2458
2556
|
return new MushiApiError(
|
|
2459
2557
|
504,
|
|
2460
2558
|
"MUSHI_TIMEOUT",
|
|
2461
|
-
`Timed out after ${
|
|
2559
|
+
`Timed out after ${effectiveTimeoutMs}ms waiting for ${path}. The Mushi API accepted the connection but did not answer in time \u2014 retry, or raise the budget with MUSHI_MCP_TIMEOUT_MS (ms) in your MCP env block if you self-host and cold starts are slow. Endpoint: ${apiEndpoint}`
|
|
2462
2560
|
);
|
|
2463
2561
|
}
|
|
2464
2562
|
const name = err instanceof Error ? err.name : "";
|
|
@@ -2597,6 +2695,19 @@ function createMushiServer(config) {
|
|
|
2597
2695
|
structuredContent: value
|
|
2598
2696
|
};
|
|
2599
2697
|
}
|
|
2698
|
+
async function designRecipeFor(report, fallbackProjectId, headers) {
|
|
2699
|
+
const pid = typeof report.project_id === "string" && report.project_id ? report.project_id : fallbackProjectId;
|
|
2700
|
+
const query = designExcerptQueryOf(report);
|
|
2701
|
+
try {
|
|
2702
|
+
return recipeFromExcerpt(
|
|
2703
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/design/excerpt${query}`, {
|
|
2704
|
+
headers
|
|
2705
|
+
})
|
|
2706
|
+
);
|
|
2707
|
+
} catch (err) {
|
|
2708
|
+
return recipeError(`Could not read the design excerpt: ${bareMessage(err)}`);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2600
2711
|
const server = new McpServer(
|
|
2601
2712
|
{
|
|
2602
2713
|
name: MUSHI_SERVER_METADATA.name,
|
|
@@ -2802,19 +2913,22 @@ function createMushiServer(config) {
|
|
|
2802
2913
|
outputSchema: z.object({
|
|
2803
2914
|
report: REPORT_DETAIL_OUTPUT,
|
|
2804
2915
|
...FIX_CONTEXT_SHAPE,
|
|
2805
|
-
inventoryAction: z.unknown().describe("The inventory action (with its expected_outcome contract) the report is filed against, or null")
|
|
2916
|
+
inventoryAction: z.unknown().describe("The inventory action (with its expected_outcome contract) the report is filed against, or null"),
|
|
2917
|
+
recipe: RECIPE_EXCERPT_OUTPUT
|
|
2806
2918
|
})
|
|
2807
2919
|
},
|
|
2808
2920
|
async (args) => {
|
|
2809
|
-
const { headers } = await projectScopeHeaders(args.projectId);
|
|
2921
|
+
const { projectId: scopedProjectId, headers } = await projectScopeHeaders(args.projectId);
|
|
2810
2922
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
2811
2923
|
headers
|
|
2812
2924
|
});
|
|
2925
|
+
const recipe = await designRecipeFor(report, scopedProjectId, headers);
|
|
2813
2926
|
return wrappedJsonResult(
|
|
2814
2927
|
{
|
|
2815
2928
|
report: projectReportDetail(report, false),
|
|
2816
2929
|
...fixContextOf(report),
|
|
2817
|
-
inventoryAction: report.inventory_action ?? null
|
|
2930
|
+
inventoryAction: report.inventory_action ?? null,
|
|
2931
|
+
recipe
|
|
2818
2932
|
},
|
|
2819
2933
|
"fix context"
|
|
2820
2934
|
);
|
|
@@ -2989,6 +3103,598 @@ function createMushiServer(config) {
|
|
|
2989
3103
|
return jsonResult(data);
|
|
2990
3104
|
}
|
|
2991
3105
|
);
|
|
3106
|
+
server.registerTool(
|
|
3107
|
+
"get_app_recipe",
|
|
3108
|
+
{
|
|
3109
|
+
title: titleOf("get_app_recipe"),
|
|
3110
|
+
description: descOf("get_app_recipe"),
|
|
3111
|
+
annotations: annotationsFor("get_app_recipe"),
|
|
3112
|
+
inputSchema: z.object({
|
|
3113
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
3114
|
+
})
|
|
3115
|
+
},
|
|
3116
|
+
async (args) => {
|
|
3117
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3118
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/recipe`));
|
|
3119
|
+
}
|
|
3120
|
+
);
|
|
3121
|
+
server.registerTool(
|
|
3122
|
+
"get_design_tokens",
|
|
3123
|
+
{
|
|
3124
|
+
title: titleOf("get_design_tokens"),
|
|
3125
|
+
description: descOf("get_design_tokens"),
|
|
3126
|
+
annotations: annotationsFor("get_design_tokens"),
|
|
3127
|
+
inputSchema: z.object({
|
|
3128
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3129
|
+
group: z.string().max(80).optional().describe("Only tokens in this group (first path segment, e.g. color, space, radius)."),
|
|
3130
|
+
type: z.string().max(40).optional().describe("Only tokens of this DTCG $type (color, dimension, fontFamily, \u2026)."),
|
|
3131
|
+
direction: z.string().max(80).optional().describe("A named token set (directions/<name>/) instead of the active one.")
|
|
3132
|
+
})
|
|
3133
|
+
},
|
|
3134
|
+
async (args) => {
|
|
3135
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3136
|
+
const q = new URLSearchParams();
|
|
3137
|
+
if (args.group) q.set("group", args.group);
|
|
3138
|
+
if (args.type) q.set("type", args.type);
|
|
3139
|
+
if (args.direction) q.set("direction", args.direction);
|
|
3140
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
3141
|
+
return jsonText(
|
|
3142
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/design/tokens${suffix}`)
|
|
3143
|
+
);
|
|
3144
|
+
}
|
|
3145
|
+
);
|
|
3146
|
+
server.registerTool(
|
|
3147
|
+
"get_design_deviance",
|
|
3148
|
+
{
|
|
3149
|
+
title: titleOf("get_design_deviance"),
|
|
3150
|
+
description: descOf("get_design_deviance"),
|
|
3151
|
+
annotations: annotationsFor("get_design_deviance"),
|
|
3152
|
+
inputSchema: z.object({
|
|
3153
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3154
|
+
limit: z.number().int().min(1).max(200).optional().describe("Findings to return, 1\u2013200 (default 25).")
|
|
3155
|
+
})
|
|
3156
|
+
},
|
|
3157
|
+
async (args) => {
|
|
3158
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3159
|
+
const suffix = args.limit !== void 0 ? `?${new URLSearchParams({ limit: String(args.limit) })}` : "";
|
|
3160
|
+
return jsonText(
|
|
3161
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/design/deviance${suffix}`)
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
3164
|
+
);
|
|
3165
|
+
const ORG_ID_INPUT = z.object({
|
|
3166
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization when omitted")
|
|
3167
|
+
});
|
|
3168
|
+
server.registerTool(
|
|
3169
|
+
"get_portfolio",
|
|
3170
|
+
{
|
|
3171
|
+
title: titleOf("get_portfolio"),
|
|
3172
|
+
description: descOf("get_portfolio"),
|
|
3173
|
+
annotations: annotationsFor("get_portfolio"),
|
|
3174
|
+
inputSchema: ORG_ID_INPUT
|
|
3175
|
+
},
|
|
3176
|
+
async (args) => {
|
|
3177
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3178
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/portfolio`));
|
|
3179
|
+
}
|
|
3180
|
+
);
|
|
3181
|
+
server.registerTool(
|
|
3182
|
+
"list_portfolio_findings",
|
|
3183
|
+
{
|
|
3184
|
+
title: titleOf("list_portfolio_findings"),
|
|
3185
|
+
description: descOf("list_portfolio_findings"),
|
|
3186
|
+
annotations: annotationsFor("list_portfolio_findings"),
|
|
3187
|
+
inputSchema: ORG_ID_INPUT
|
|
3188
|
+
},
|
|
3189
|
+
async (args) => {
|
|
3190
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3191
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/portfolio/findings`));
|
|
3192
|
+
}
|
|
3193
|
+
);
|
|
3194
|
+
server.registerTool(
|
|
3195
|
+
"get_recipe_drift",
|
|
3196
|
+
{
|
|
3197
|
+
title: titleOf("get_recipe_drift"),
|
|
3198
|
+
description: descOf("get_recipe_drift"),
|
|
3199
|
+
annotations: annotationsFor("get_recipe_drift"),
|
|
3200
|
+
inputSchema: z.object({
|
|
3201
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
3202
|
+
})
|
|
3203
|
+
},
|
|
3204
|
+
async (args) => {
|
|
3205
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3206
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/recipe/drift`));
|
|
3207
|
+
}
|
|
3208
|
+
);
|
|
3209
|
+
server.registerTool(
|
|
3210
|
+
"list_connectors",
|
|
3211
|
+
{
|
|
3212
|
+
title: titleOf("list_connectors"),
|
|
3213
|
+
description: descOf("list_connectors"),
|
|
3214
|
+
annotations: annotationsFor("list_connectors"),
|
|
3215
|
+
inputSchema: ORG_ID_INPUT
|
|
3216
|
+
},
|
|
3217
|
+
async (args) => {
|
|
3218
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3219
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/connectors`));
|
|
3220
|
+
}
|
|
3221
|
+
);
|
|
3222
|
+
const EDIT_INPUT = z.object({
|
|
3223
|
+
path: z.string().min(1).max(400).describe("Repo-relative path the recipe allows"),
|
|
3224
|
+
content: z.string().max(512 * 1024).describe("The full new file content"),
|
|
3225
|
+
reason: z.string().max(200).optional(),
|
|
3226
|
+
baseSha: z.string().min(1).max(80).nullable().optional().describe("The file's baseSha from the dry run (null = it did not exist). The confirm is refused if the file changed since, so it never reverts someone else's edit")
|
|
3227
|
+
});
|
|
3228
|
+
const ELEMENT_INPUT = z.enum(["design", "gates", "env", "routes", "store", "release"]).describe("Which part of the recipe the edit belongs to");
|
|
3229
|
+
server.registerTool(
|
|
3230
|
+
"propose_recipe_change",
|
|
3231
|
+
{
|
|
3232
|
+
title: titleOf("propose_recipe_change"),
|
|
3233
|
+
description: descOf("propose_recipe_change"),
|
|
3234
|
+
annotations: annotationsFor("propose_recipe_change"),
|
|
3235
|
+
inputSchema: z.object({
|
|
3236
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3237
|
+
element: ELEMENT_INPUT,
|
|
3238
|
+
edits: z.array(EDIT_INPUT).min(1).max(30),
|
|
3239
|
+
title: z.string().min(1).max(120).optional(),
|
|
3240
|
+
confirm: z.boolean().optional().describe("true opens the draft PR; omitted or false is a dry run")
|
|
3241
|
+
})
|
|
3242
|
+
},
|
|
3243
|
+
async (args) => {
|
|
3244
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3245
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/recipe/changes`, {
|
|
3246
|
+
method: "POST",
|
|
3247
|
+
body: JSON.stringify({ element: args.element, edits: args.edits, title: args.title, dryRun: args.confirm !== true })
|
|
3248
|
+
}));
|
|
3249
|
+
}
|
|
3250
|
+
);
|
|
3251
|
+
server.registerTool(
|
|
3252
|
+
"propose_portfolio_change",
|
|
3253
|
+
{
|
|
3254
|
+
title: titleOf("propose_portfolio_change"),
|
|
3255
|
+
description: descOf("propose_portfolio_change"),
|
|
3256
|
+
annotations: annotationsFor("propose_portfolio_change"),
|
|
3257
|
+
inputSchema: z.object({
|
|
3258
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3259
|
+
element: ELEMENT_INPUT,
|
|
3260
|
+
changes: z.array(z.object({ projectId: z.string().uuid(), edits: z.array(EDIT_INPUT).min(1).max(30) })).min(1).max(10),
|
|
3261
|
+
title: z.string().min(1).max(120).optional(),
|
|
3262
|
+
confirm: z.boolean().optional().describe("true opens one draft PR per repo; omitted or false is a dry run")
|
|
3263
|
+
})
|
|
3264
|
+
},
|
|
3265
|
+
async (args) => {
|
|
3266
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3267
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/portfolio/changes`, {
|
|
3268
|
+
method: "POST",
|
|
3269
|
+
body: JSON.stringify({ element: args.element, changes: args.changes, title: args.title, dryRun: args.confirm !== true })
|
|
3270
|
+
}));
|
|
3271
|
+
}
|
|
3272
|
+
);
|
|
3273
|
+
server.registerTool(
|
|
3274
|
+
"request_connector_action",
|
|
3275
|
+
{
|
|
3276
|
+
title: titleOf("request_connector_action"),
|
|
3277
|
+
description: descOf("request_connector_action"),
|
|
3278
|
+
annotations: annotationsFor("request_connector_action"),
|
|
3279
|
+
inputSchema: z.object({
|
|
3280
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3281
|
+
connectorId: z.string().uuid().describe("Connector instance id (list_connectors)"),
|
|
3282
|
+
action: z.string().min(1).max(60).describe("For example set_rollout or promote_track"),
|
|
3283
|
+
payload: z.record(z.string(), z.unknown()).describe("The exact action input; the approval binds its hash"),
|
|
3284
|
+
projectId: z.string().uuid().optional(),
|
|
3285
|
+
reason: z.string().max(500).optional().describe("Why, shown to the approver")
|
|
3286
|
+
})
|
|
3287
|
+
},
|
|
3288
|
+
async (args) => {
|
|
3289
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3290
|
+
const body = { connectorId: args.connectorId, action: args.action, payload: args.payload, projectId: args.projectId, reason: args.reason };
|
|
3291
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/connector-actions`, { method: "POST", body: JSON.stringify(body) }));
|
|
3292
|
+
}
|
|
3293
|
+
);
|
|
3294
|
+
server.registerTool(
|
|
3295
|
+
"get_store_status",
|
|
3296
|
+
{
|
|
3297
|
+
title: titleOf("get_store_status"),
|
|
3298
|
+
description: descOf("get_store_status"),
|
|
3299
|
+
annotations: annotationsFor("get_store_status"),
|
|
3300
|
+
inputSchema: z.object({
|
|
3301
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
3302
|
+
})
|
|
3303
|
+
},
|
|
3304
|
+
async (args) => {
|
|
3305
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3306
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/store`));
|
|
3307
|
+
}
|
|
3308
|
+
);
|
|
3309
|
+
server.registerTool(
|
|
3310
|
+
"get_radar",
|
|
3311
|
+
{
|
|
3312
|
+
title: titleOf("get_radar"),
|
|
3313
|
+
description: descOf("get_radar"),
|
|
3314
|
+
annotations: annotationsFor("get_radar"),
|
|
3315
|
+
inputSchema: z.object({
|
|
3316
|
+
scope: z.enum(["project", "organization"]).optional().describe("project (default) or organization"),
|
|
3317
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project (scope project)"),
|
|
3318
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization (scope organization)")
|
|
3319
|
+
})
|
|
3320
|
+
},
|
|
3321
|
+
async (args) => {
|
|
3322
|
+
if (args.scope === "organization") {
|
|
3323
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3324
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/radar`));
|
|
3325
|
+
}
|
|
3326
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3327
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/radar`));
|
|
3328
|
+
}
|
|
3329
|
+
);
|
|
3330
|
+
const PROJECT_ID_INPUT = z.object({
|
|
3331
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
3332
|
+
});
|
|
3333
|
+
server.registerTool(
|
|
3334
|
+
"run_radar",
|
|
3335
|
+
{
|
|
3336
|
+
title: titleOf("run_radar"),
|
|
3337
|
+
description: descOf("run_radar"),
|
|
3338
|
+
annotations: annotationsFor("run_radar"),
|
|
3339
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3340
|
+
},
|
|
3341
|
+
async (args) => {
|
|
3342
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3343
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/radar/run`, { method: "POST" }));
|
|
3344
|
+
}
|
|
3345
|
+
);
|
|
3346
|
+
server.registerTool(
|
|
3347
|
+
"refresh_recipe",
|
|
3348
|
+
{
|
|
3349
|
+
title: titleOf("refresh_recipe"),
|
|
3350
|
+
description: descOf("refresh_recipe"),
|
|
3351
|
+
annotations: annotationsFor("refresh_recipe"),
|
|
3352
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3353
|
+
},
|
|
3354
|
+
async (args) => {
|
|
3355
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3356
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/recipe/refresh`, { method: "POST" }));
|
|
3357
|
+
}
|
|
3358
|
+
);
|
|
3359
|
+
server.registerTool(
|
|
3360
|
+
"run_design_deviance",
|
|
3361
|
+
{
|
|
3362
|
+
title: titleOf("run_design_deviance"),
|
|
3363
|
+
description: descOf("run_design_deviance"),
|
|
3364
|
+
annotations: annotationsFor("run_design_deviance"),
|
|
3365
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3366
|
+
},
|
|
3367
|
+
async (args) => {
|
|
3368
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3369
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/design/deviance/run`, { method: "POST" }));
|
|
3370
|
+
}
|
|
3371
|
+
);
|
|
3372
|
+
server.registerTool(
|
|
3373
|
+
"run_store_review",
|
|
3374
|
+
{
|
|
3375
|
+
title: titleOf("run_store_review"),
|
|
3376
|
+
description: descOf("run_store_review"),
|
|
3377
|
+
annotations: annotationsFor("run_store_review"),
|
|
3378
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3379
|
+
},
|
|
3380
|
+
async (args) => {
|
|
3381
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3382
|
+
return jsonText(
|
|
3383
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/store/review`, { method: "POST" }, { minTimeoutMs: 9e4 })
|
|
3384
|
+
);
|
|
3385
|
+
}
|
|
3386
|
+
);
|
|
3387
|
+
server.registerTool(
|
|
3388
|
+
"get_release_calendar",
|
|
3389
|
+
{
|
|
3390
|
+
title: titleOf("get_release_calendar"),
|
|
3391
|
+
description: descOf("get_release_calendar"),
|
|
3392
|
+
annotations: annotationsFor("get_release_calendar"),
|
|
3393
|
+
inputSchema: ORG_ID_INPUT
|
|
3394
|
+
},
|
|
3395
|
+
async (args) => {
|
|
3396
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3397
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/releases`));
|
|
3398
|
+
}
|
|
3399
|
+
);
|
|
3400
|
+
server.registerTool(
|
|
3401
|
+
"get_code_health",
|
|
3402
|
+
{
|
|
3403
|
+
title: titleOf("get_code_health"),
|
|
3404
|
+
description: descOf("get_code_health"),
|
|
3405
|
+
annotations: annotationsFor("get_code_health"),
|
|
3406
|
+
inputSchema: z.object({
|
|
3407
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3408
|
+
days: z.number().int().min(1).max(365).optional().describe("Trend window in days (default 30)")
|
|
3409
|
+
})
|
|
3410
|
+
},
|
|
3411
|
+
async (args) => {
|
|
3412
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3413
|
+
const qs = new URLSearchParams({ project_id: pid, days: String(args.days ?? 30) });
|
|
3414
|
+
return jsonText(await apiCall(`/v1/admin/code-health?${qs}`));
|
|
3415
|
+
}
|
|
3416
|
+
);
|
|
3417
|
+
server.registerTool(
|
|
3418
|
+
"explain_finding",
|
|
3419
|
+
{
|
|
3420
|
+
title: titleOf("explain_finding"),
|
|
3421
|
+
description: descOf("explain_finding"),
|
|
3422
|
+
annotations: annotationsFor("explain_finding"),
|
|
3423
|
+
inputSchema: z.object({
|
|
3424
|
+
findingId: z.string().uuid().describe("Gate finding id (the id field of a finding in list_gate_findings, get_radar, get_code_health\u2026)")
|
|
3425
|
+
})
|
|
3426
|
+
},
|
|
3427
|
+
async (args) => {
|
|
3428
|
+
return jsonText(await apiCall(`/v1/admin/findings/${encodeURIComponent(args.findingId)}`));
|
|
3429
|
+
}
|
|
3430
|
+
);
|
|
3431
|
+
server.registerTool(
|
|
3432
|
+
"get_auto_release_status",
|
|
3433
|
+
{
|
|
3434
|
+
title: titleOf("get_auto_release_status"),
|
|
3435
|
+
description: descOf("get_auto_release_status"),
|
|
3436
|
+
annotations: annotationsFor("get_auto_release_status"),
|
|
3437
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3438
|
+
},
|
|
3439
|
+
async (args) => {
|
|
3440
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3441
|
+
return jsonText(await apiCall(`/v1/admin/releases/auto-release?${new URLSearchParams({ project_id: pid })}`));
|
|
3442
|
+
}
|
|
3443
|
+
);
|
|
3444
|
+
server.registerTool(
|
|
3445
|
+
"get_design_settings",
|
|
3446
|
+
{
|
|
3447
|
+
title: titleOf("get_design_settings"),
|
|
3448
|
+
description: descOf("get_design_settings"),
|
|
3449
|
+
annotations: annotationsFor("get_design_settings"),
|
|
3450
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3451
|
+
},
|
|
3452
|
+
async (args) => {
|
|
3453
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3454
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/design/settings`));
|
|
3455
|
+
}
|
|
3456
|
+
);
|
|
3457
|
+
server.registerTool(
|
|
3458
|
+
"set_design_settings",
|
|
3459
|
+
{
|
|
3460
|
+
title: titleOf("set_design_settings"),
|
|
3461
|
+
description: descOf("set_design_settings"),
|
|
3462
|
+
annotations: annotationsFor("set_design_settings"),
|
|
3463
|
+
inputSchema: z.object({
|
|
3464
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3465
|
+
threshold: z.number().int().min(0).max(100).optional().describe("0\u2013100; the actions fire when the score is above it"),
|
|
3466
|
+
failCi: z.boolean().optional().describe("true: `mushi recipe check --push` fails CI above the threshold"),
|
|
3467
|
+
autofix: z.boolean().optional().describe("false turns the design auto-fix off; an API key cannot turn it on")
|
|
3468
|
+
})
|
|
3469
|
+
},
|
|
3470
|
+
async (args) => {
|
|
3471
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3472
|
+
const body = { threshold: args.threshold, failCi: args.failCi, autofix: args.autofix };
|
|
3473
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/design/settings`, { method: "PUT", body: JSON.stringify(body) }));
|
|
3474
|
+
}
|
|
3475
|
+
);
|
|
3476
|
+
server.registerTool(
|
|
3477
|
+
"get_recipe_sources",
|
|
3478
|
+
{
|
|
3479
|
+
title: titleOf("get_recipe_sources"),
|
|
3480
|
+
description: descOf("get_recipe_sources"),
|
|
3481
|
+
annotations: annotationsFor("get_recipe_sources"),
|
|
3482
|
+
inputSchema: z.object({
|
|
3483
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3484
|
+
element: z.enum(["gates", "env", "routes"]).describe("gates (mushi.recipe.json budgets), env (.env.example) or routes (the inventory file)")
|
|
3485
|
+
})
|
|
3486
|
+
},
|
|
3487
|
+
async (args) => {
|
|
3488
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3489
|
+
const qs = new URLSearchParams({ element: args.element });
|
|
3490
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/recipe/sources?${qs}`));
|
|
3491
|
+
}
|
|
3492
|
+
);
|
|
3493
|
+
server.registerTool(
|
|
3494
|
+
"get_recipe_change",
|
|
3495
|
+
{
|
|
3496
|
+
title: titleOf("get_recipe_change"),
|
|
3497
|
+
description: descOf("get_recipe_change"),
|
|
3498
|
+
annotations: annotationsFor("get_recipe_change"),
|
|
3499
|
+
inputSchema: z.object({
|
|
3500
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3501
|
+
jobId: z.string().uuid().describe("The jobId propose_recipe_change returned")
|
|
3502
|
+
})
|
|
3503
|
+
},
|
|
3504
|
+
async (args) => {
|
|
3505
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3506
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/recipe/changes/${encodeURIComponent(args.jobId)}`));
|
|
3507
|
+
}
|
|
3508
|
+
);
|
|
3509
|
+
server.registerTool(
|
|
3510
|
+
"get_store_reviews",
|
|
3511
|
+
{
|
|
3512
|
+
title: titleOf("get_store_reviews"),
|
|
3513
|
+
description: descOf("get_store_reviews"),
|
|
3514
|
+
annotations: annotationsFor("get_store_reviews"),
|
|
3515
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3516
|
+
},
|
|
3517
|
+
async (args) => {
|
|
3518
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3519
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/store/reviews`));
|
|
3520
|
+
}
|
|
3521
|
+
);
|
|
3522
|
+
server.registerTool(
|
|
3523
|
+
"pull_store_reviews",
|
|
3524
|
+
{
|
|
3525
|
+
title: titleOf("pull_store_reviews"),
|
|
3526
|
+
description: descOf("pull_store_reviews"),
|
|
3527
|
+
annotations: annotationsFor("pull_store_reviews"),
|
|
3528
|
+
inputSchema: PROJECT_ID_INPUT
|
|
3529
|
+
},
|
|
3530
|
+
async (args) => {
|
|
3531
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3532
|
+
return jsonText(
|
|
3533
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/store/reviews/pull`, { method: "POST" }, { minTimeoutMs: 9e4 })
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
);
|
|
3537
|
+
server.registerTool(
|
|
3538
|
+
"set_store_review_intake",
|
|
3539
|
+
{
|
|
3540
|
+
title: titleOf("set_store_review_intake"),
|
|
3541
|
+
description: descOf("set_store_review_intake"),
|
|
3542
|
+
annotations: annotationsFor("set_store_review_intake"),
|
|
3543
|
+
inputSchema: z.object({
|
|
3544
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted"),
|
|
3545
|
+
enabled: z.boolean().describe("false turns intake off; true keeps it on (only the console can turn it on)"),
|
|
3546
|
+
maxRating: z.number().int().min(1).max(5).optional().describe("Reviews at or under this many stars are filed (1\u20135)")
|
|
3547
|
+
})
|
|
3548
|
+
},
|
|
3549
|
+
async (args) => {
|
|
3550
|
+
const pid = await resolveProjectId(args.projectId);
|
|
3551
|
+
const body = { enabled: args.enabled, maxRating: args.maxRating };
|
|
3552
|
+
return jsonText(await apiCall(`/v1/admin/projects/${encodeURIComponent(pid)}/store/reviews/settings`, { method: "PUT", body: JSON.stringify(body) }));
|
|
3553
|
+
}
|
|
3554
|
+
);
|
|
3555
|
+
const ACCOUNT_PROVIDER = z.enum(["apple", "google_play", "aws", "supabase", "vercel", "registrar", "stripe", "github", "cloudflare", "other"]);
|
|
3556
|
+
server.registerTool(
|
|
3557
|
+
"get_accounts_register",
|
|
3558
|
+
{
|
|
3559
|
+
title: titleOf("get_accounts_register"),
|
|
3560
|
+
description: descOf("get_accounts_register"),
|
|
3561
|
+
annotations: annotationsFor("get_accounts_register"),
|
|
3562
|
+
inputSchema: ORG_ID_INPUT
|
|
3563
|
+
},
|
|
3564
|
+
async (args) => {
|
|
3565
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3566
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/accounts`));
|
|
3567
|
+
}
|
|
3568
|
+
);
|
|
3569
|
+
server.registerTool(
|
|
3570
|
+
"save_register_account",
|
|
3571
|
+
{
|
|
3572
|
+
title: titleOf("save_register_account"),
|
|
3573
|
+
description: descOf("save_register_account"),
|
|
3574
|
+
annotations: annotationsFor("save_register_account"),
|
|
3575
|
+
inputSchema: z.object({
|
|
3576
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3577
|
+
id: z.string().uuid().optional().describe("Account id to change (get_accounts_register); omit to record a new account"),
|
|
3578
|
+
provider: ACCOUNT_PROVIDER.optional().describe("Required when recording a new account"),
|
|
3579
|
+
displayName: z.string().min(1).max(120).optional().describe("Required when recording a new account"),
|
|
3580
|
+
ownerEmail: z.string().email().max(254).nullable().optional(),
|
|
3581
|
+
twoFactorDeclared: z.boolean().nullable().optional(),
|
|
3582
|
+
recoveryContact: z.string().max(200).nullable().optional().describe("A person who can recover the account (never a recovery code)"),
|
|
3583
|
+
adminCount: z.number().int().min(1).max(100).optional(),
|
|
3584
|
+
autoRenew: z.boolean().nullable().optional()
|
|
3585
|
+
})
|
|
3586
|
+
},
|
|
3587
|
+
async (args) => {
|
|
3588
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3589
|
+
const { organizationId: _org, id, ...fields } = args;
|
|
3590
|
+
if (!id && (!fields.provider || !fields.displayName)) {
|
|
3591
|
+
throw new MushiApiError(400, "VALIDATION_ERROR", "provider and displayName are required to record a new account (pass id to change one).");
|
|
3592
|
+
}
|
|
3593
|
+
const path = id ? `/v1/admin/orgs/${org}/accounts/${encodeURIComponent(id)}` : `/v1/admin/orgs/${org}/accounts`;
|
|
3594
|
+
return jsonText(await apiCall(path, { method: id ? "PATCH" : "POST", body: JSON.stringify(fields) }));
|
|
3595
|
+
}
|
|
3596
|
+
);
|
|
3597
|
+
server.registerTool(
|
|
3598
|
+
"remove_register_account",
|
|
3599
|
+
{
|
|
3600
|
+
title: titleOf("remove_register_account"),
|
|
3601
|
+
description: descOf("remove_register_account"),
|
|
3602
|
+
annotations: annotationsFor("remove_register_account"),
|
|
3603
|
+
inputSchema: z.object({
|
|
3604
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3605
|
+
id: z.string().uuid().describe("Account id (get_accounts_register)")
|
|
3606
|
+
})
|
|
3607
|
+
},
|
|
3608
|
+
async (args) => {
|
|
3609
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3610
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/accounts/${encodeURIComponent(args.id)}`, { method: "DELETE" }));
|
|
3611
|
+
}
|
|
3612
|
+
);
|
|
3613
|
+
server.registerTool(
|
|
3614
|
+
"set_domain_auto_renew",
|
|
3615
|
+
{
|
|
3616
|
+
title: titleOf("set_domain_auto_renew"),
|
|
3617
|
+
description: descOf("set_domain_auto_renew"),
|
|
3618
|
+
annotations: annotationsFor("set_domain_auto_renew"),
|
|
3619
|
+
inputSchema: z.object({
|
|
3620
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3621
|
+
domainId: z.string().uuid().describe("Domain id (the id of a domain in get_accounts_register)"),
|
|
3622
|
+
autoRenew: z.boolean().nullable().describe("true, false, or null for not known")
|
|
3623
|
+
})
|
|
3624
|
+
},
|
|
3625
|
+
async (args) => {
|
|
3626
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3627
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/domains/${encodeURIComponent(args.domainId)}`, {
|
|
3628
|
+
method: "PATCH",
|
|
3629
|
+
body: JSON.stringify({ autoRenew: args.autoRenew })
|
|
3630
|
+
}));
|
|
3631
|
+
}
|
|
3632
|
+
);
|
|
3633
|
+
server.registerTool(
|
|
3634
|
+
"get_spend_ledger",
|
|
3635
|
+
{
|
|
3636
|
+
title: titleOf("get_spend_ledger"),
|
|
3637
|
+
description: descOf("get_spend_ledger"),
|
|
3638
|
+
annotations: annotationsFor("get_spend_ledger"),
|
|
3639
|
+
inputSchema: ORG_ID_INPUT
|
|
3640
|
+
},
|
|
3641
|
+
async (args) => {
|
|
3642
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3643
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/spend`));
|
|
3644
|
+
}
|
|
3645
|
+
);
|
|
3646
|
+
server.registerTool(
|
|
3647
|
+
"import_spend_bill",
|
|
3648
|
+
{
|
|
3649
|
+
title: titleOf("import_spend_bill"),
|
|
3650
|
+
description: descOf("import_spend_bill"),
|
|
3651
|
+
annotations: annotationsFor("import_spend_bill"),
|
|
3652
|
+
inputSchema: z.object({
|
|
3653
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3654
|
+
vendor: z.enum(["vercel", "aws", "supabase", "other"]),
|
|
3655
|
+
csv: z.string().min(1).describe("The bill CSV content (at most 5 MB)"),
|
|
3656
|
+
projectId: z.string().uuid().optional().describe("Put every row on this app; omit to match an app column by name or slug"),
|
|
3657
|
+
filename: z.string().max(200).optional()
|
|
3658
|
+
})
|
|
3659
|
+
},
|
|
3660
|
+
async (args) => {
|
|
3661
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3662
|
+
const body = { vendor: args.vendor, csv: args.csv, projectId: args.projectId, filename: args.filename };
|
|
3663
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/spend/imports`, { method: "POST", body: JSON.stringify(body) }, { minTimeoutMs: 6e4 }));
|
|
3664
|
+
}
|
|
3665
|
+
);
|
|
3666
|
+
server.registerTool(
|
|
3667
|
+
"remove_spend_import",
|
|
3668
|
+
{
|
|
3669
|
+
title: titleOf("remove_spend_import"),
|
|
3670
|
+
description: descOf("remove_spend_import"),
|
|
3671
|
+
annotations: annotationsFor("remove_spend_import"),
|
|
3672
|
+
inputSchema: z.object({
|
|
3673
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3674
|
+
importId: z.string().uuid().describe("Import id (get_spend_ledger imports)")
|
|
3675
|
+
})
|
|
3676
|
+
},
|
|
3677
|
+
async (args) => {
|
|
3678
|
+
const org = encodeURIComponent(args.organizationId ?? "current");
|
|
3679
|
+
return jsonText(await apiCall(`/v1/admin/orgs/${org}/spend/imports/${encodeURIComponent(args.importId)}`, { method: "DELETE" }));
|
|
3680
|
+
}
|
|
3681
|
+
);
|
|
3682
|
+
server.registerTool(
|
|
3683
|
+
"import_portfolio_resources",
|
|
3684
|
+
{
|
|
3685
|
+
title: titleOf("import_portfolio_resources"),
|
|
3686
|
+
description: descOf("import_portfolio_resources"),
|
|
3687
|
+
annotations: annotationsFor("import_portfolio_resources"),
|
|
3688
|
+
inputSchema: z.object({
|
|
3689
|
+
organizationId: z.string().uuid().optional().describe("Organization UUID \u2014 defaults to the key owner's only organization"),
|
|
3690
|
+
csv: z.string().min(1).max(256 * 1024).describe("CSV with the columns kind, external_id, project and optionally role")
|
|
3691
|
+
})
|
|
3692
|
+
},
|
|
3693
|
+
async (args) => {
|
|
3694
|
+
const body = { organizationId: args.organizationId ?? "current", csv: args.csv };
|
|
3695
|
+
return jsonText(await apiCall("/v1/ingest/recipe/csv", { method: "POST", body: JSON.stringify(body) }));
|
|
3696
|
+
}
|
|
3697
|
+
);
|
|
2992
3698
|
server.registerTool(
|
|
2993
3699
|
"suggest_fix",
|
|
2994
3700
|
{
|
|
@@ -3701,6 +4407,18 @@ function createMushiServer(config) {
|
|
|
3701
4407
|
args: { mode: "dispatch" }
|
|
3702
4408
|
});
|
|
3703
4409
|
}
|
|
4410
|
+
const waiting = reports.filter(
|
|
4411
|
+
(r) => typeof r.last_reporter_reply_at === "string" && (typeof r.admin_seen_at !== "string" || Date.parse(r.last_reporter_reply_at) > Date.parse(r.admin_seen_at))
|
|
4412
|
+
);
|
|
4413
|
+
if (waiting.length > 0) {
|
|
4414
|
+
steps.push({
|
|
4415
|
+
priority: priority++,
|
|
4416
|
+
action: `Answer ${waiting.length} reporter${waiting.length === 1 ? "" : "s"} waiting for a reply`,
|
|
4417
|
+
reason: `They replied in your app's "Your reports" thread. Latest: ${label(waiting[0])}. Read it, then answer with reply_to_reporter.`,
|
|
4418
|
+
tool: "get_report_timeline",
|
|
4419
|
+
args: { reportId: waiting[0].id }
|
|
4420
|
+
});
|
|
4421
|
+
}
|
|
3704
4422
|
const fixing = reports.filter((r) => r.status === "fixing");
|
|
3705
4423
|
for (const r of fixing.slice(0, 2)) {
|
|
3706
4424
|
steps.push({
|
|
@@ -3736,7 +4454,7 @@ function createMushiServer(config) {
|
|
|
3736
4454
|
args: { status: "classified" }
|
|
3737
4455
|
});
|
|
3738
4456
|
}
|
|
3739
|
-
const summary = steps.length === 0 ? "Inbox is clear \u2014 no blocked fixes, no open user reports, no chores. Nothing needs your attention." : `${steps.length} prioritised step${steps.length === 1 ? "" : "s"}: ` + steps.map((s) => s.action).join(" \u2192 ");
|
|
4457
|
+
const summary = steps.length === 0 ? "Inbox is clear \u2014 no blocked fixes, no reporters waiting, no open user reports, no chores. Nothing needs your attention." : `${steps.length} prioritised step${steps.length === 1 ? "" : "s"}: ` + steps.map((s) => s.action).join(" \u2192 ");
|
|
3740
4458
|
return jsonResult({ steps, summary });
|
|
3741
4459
|
}
|
|
3742
4460
|
);
|
|
@@ -3921,6 +4639,57 @@ function createMushiServer(config) {
|
|
|
3921
4639
|
return jsonResult({ report });
|
|
3922
4640
|
}
|
|
3923
4641
|
);
|
|
4642
|
+
server.registerTool(
|
|
4643
|
+
"import_sentry_issues",
|
|
4644
|
+
{
|
|
4645
|
+
title: titleOf("import_sentry_issues"),
|
|
4646
|
+
description: descOf("import_sentry_issues"),
|
|
4647
|
+
annotations: annotationsFor("import_sentry_issues"),
|
|
4648
|
+
inputSchema: z.object({
|
|
4649
|
+
issueIds: z.array(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/)).max(10).optional().describe('Sentry issue ids or short ids (e.g. "4501", "WEB-12"), at most 10'),
|
|
4650
|
+
query: z.string().max(200).optional().describe("Sentry search query within the configured Sentry project (default is:unresolved). Not with issueIds."),
|
|
4651
|
+
limit: z.number().int().min(1).max(10).optional().describe("How many issues a query imports (1-10, default 5)"),
|
|
4652
|
+
sinceDays: z.number().int().min(1).max(90).optional().describe("Only issues seen in the last N days (1-90). Search mode only."),
|
|
4653
|
+
cursor: z.string().regex(/^[0-9][0-9.:-]{0,63}$/).optional().describe("nextCursor from the previous import, to fetch the next page of a backlog. Search mode only."),
|
|
4654
|
+
sentryProject: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,49}$/).optional().describe("Which of the project's Sentry projects to search (default: the primary one). Search mode only."),
|
|
4655
|
+
projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
|
|
4656
|
+
}),
|
|
4657
|
+
outputSchema: z.object({
|
|
4658
|
+
items: z.array(
|
|
4659
|
+
z.looseObject({
|
|
4660
|
+
input: z.string(),
|
|
4661
|
+
issueId: z.string().nullable(),
|
|
4662
|
+
shortId: z.string().nullable(),
|
|
4663
|
+
outcome: z.string().describe("created | linked | deduped | ignored | error"),
|
|
4664
|
+
reportId: z.string().nullable(),
|
|
4665
|
+
error: z.string().optional()
|
|
4666
|
+
})
|
|
4667
|
+
).describe("One entry per requested or matched issue"),
|
|
4668
|
+
created: z.array(z.string().nullable()).describe("Report ids created by this import"),
|
|
4669
|
+
linked: z.array(z.string().nullable()).describe("Report ids that already existed for these issues"),
|
|
4670
|
+
failed: z.number().describe("Issues that could not be imported"),
|
|
4671
|
+
indexing: z.looseObject({ queued: z.boolean(), paths: z.number() }).describe("Whether stack-frame files were queued for codebase indexing"),
|
|
4672
|
+
sentryProject: z.string().nullable().optional().describe("The Sentry project a search ran in (null for issueIds)"),
|
|
4673
|
+
sentryProjects: z.array(z.string()).optional().describe("This project's Sentry projects, primary first"),
|
|
4674
|
+
nextCursor: z.string().nullable().optional().describe("Pass back as cursor to import the next page; null when the backlog is done")
|
|
4675
|
+
})
|
|
4676
|
+
},
|
|
4677
|
+
async (args) => {
|
|
4678
|
+
const pid = await resolveProjectId(args.projectId);
|
|
4679
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/sentry/import`, {
|
|
4680
|
+
method: "POST",
|
|
4681
|
+
body: JSON.stringify({
|
|
4682
|
+
issueIds: args.issueIds,
|
|
4683
|
+
query: args.query,
|
|
4684
|
+
limit: args.limit,
|
|
4685
|
+
sinceDays: args.sinceDays,
|
|
4686
|
+
cursor: args.cursor,
|
|
4687
|
+
sentryProject: args.sentryProject
|
|
4688
|
+
})
|
|
4689
|
+
});
|
|
4690
|
+
return jsonResult(data);
|
|
4691
|
+
}
|
|
4692
|
+
);
|
|
3924
4693
|
server.registerTool(
|
|
3925
4694
|
"transition_status",
|
|
3926
4695
|
{
|
|
@@ -3945,13 +4714,20 @@ function createMushiServer(config) {
|
|
|
3945
4714
|
"reopened",
|
|
3946
4715
|
"dismissed"
|
|
3947
4716
|
]).describe("Target status (resolved is stored as fixed)"),
|
|
3948
|
-
reason: z.string().optional().describe("Reason for the transition (audit trail)")
|
|
4717
|
+
reason: z.string().optional().describe("Reason for the transition (audit trail)"),
|
|
4718
|
+
closedReason: z.enum(["duplicate", "not_reproducible", "wont_fix", "working_as_intended", "spam"]).optional().describe("When dismissing: what the reporter is told (spam closes silently)"),
|
|
4719
|
+
reporterMessage: z.string().max(1e4).optional().describe("Optional note posted to the reporter verbatim")
|
|
3949
4720
|
})
|
|
3950
4721
|
},
|
|
3951
4722
|
async (args) => {
|
|
3952
4723
|
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
3953
4724
|
method: "PATCH",
|
|
3954
|
-
body: JSON.stringify({
|
|
4725
|
+
body: JSON.stringify({
|
|
4726
|
+
status: args.status,
|
|
4727
|
+
reason: args.reason,
|
|
4728
|
+
closed_reason: args.closedReason,
|
|
4729
|
+
reporter_message: args.reporterMessage
|
|
4730
|
+
})
|
|
3955
4731
|
});
|
|
3956
4732
|
return jsonText(data);
|
|
3957
4733
|
}
|
|
@@ -4695,6 +5471,65 @@ Be specific. No generic advice.`
|
|
|
4695
5471
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
4696
5472
|
}
|
|
4697
5473
|
);
|
|
5474
|
+
server.registerTool(
|
|
5475
|
+
"request_reporter_info",
|
|
5476
|
+
{
|
|
5477
|
+
title: titleOf("request_reporter_info", TDD_TOOL_CATALOG),
|
|
5478
|
+
description: descOf("request_reporter_info", TDD_TOOL_CATALOG),
|
|
5479
|
+
annotations: annotationsFor("request_reporter_info", TDD_TOOL_CATALOG),
|
|
5480
|
+
inputSchema: z.object({
|
|
5481
|
+
reportId: z.string().describe("Report id to ask about"),
|
|
5482
|
+
question: z.string().min(1).max(2e3).describe("The question, shown to the reporter verbatim"),
|
|
5483
|
+
authorName: z.string().optional().describe('Display name for the sender (default: "Developer")')
|
|
5484
|
+
})
|
|
5485
|
+
},
|
|
5486
|
+
async ({ reportId, question, authorName }) => {
|
|
5487
|
+
const data = await apiCall(`/v1/admin/reports/${reportId}/request-info`, {
|
|
5488
|
+
method: "POST",
|
|
5489
|
+
body: JSON.stringify({ question, author_name: authorName })
|
|
5490
|
+
});
|
|
5491
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
5492
|
+
}
|
|
5493
|
+
);
|
|
5494
|
+
server.registerTool(
|
|
5495
|
+
"list_reporter_outbox",
|
|
5496
|
+
{
|
|
5497
|
+
title: titleOf("list_reporter_outbox", TDD_TOOL_CATALOG),
|
|
5498
|
+
description: descOf("list_reporter_outbox", TDD_TOOL_CATALOG),
|
|
5499
|
+
annotations: annotationsFor("list_reporter_outbox", TDD_TOOL_CATALOG),
|
|
5500
|
+
inputSchema: z.object({
|
|
5501
|
+
projectId: z.string().optional().describe("Project UUID. Defaults to configured project.")
|
|
5502
|
+
})
|
|
5503
|
+
},
|
|
5504
|
+
async ({ projectId: pid }) => {
|
|
5505
|
+
const scoped = pid ?? config.projectId;
|
|
5506
|
+
const data = await apiCall(
|
|
5507
|
+
`/v1/admin/reporter-outbox${scoped ? `?project_id=${encodeURIComponent(scoped)}` : ""}`
|
|
5508
|
+
);
|
|
5509
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
5510
|
+
}
|
|
5511
|
+
);
|
|
5512
|
+
server.registerTool(
|
|
5513
|
+
"release_reporter_update",
|
|
5514
|
+
{
|
|
5515
|
+
title: titleOf("release_reporter_update", TDD_TOOL_CATALOG),
|
|
5516
|
+
description: descOf("release_reporter_update", TDD_TOOL_CATALOG),
|
|
5517
|
+
annotations: annotationsFor("release_reporter_update", TDD_TOOL_CATALOG),
|
|
5518
|
+
inputSchema: z.object({
|
|
5519
|
+
messageId: z.string().describe("Held message id from list_reporter_outbox"),
|
|
5520
|
+
action: z.enum(["release", "discard"]).optional().describe("release (default) sends it; discard drops it"),
|
|
5521
|
+
bodyOverride: z.string().min(1).max(1e3).optional().describe("Replacement text to send instead")
|
|
5522
|
+
})
|
|
5523
|
+
},
|
|
5524
|
+
async ({ messageId, action, bodyOverride }) => {
|
|
5525
|
+
const verb = action === "discard" ? "discard" : "release";
|
|
5526
|
+
const data = await apiCall(`/v1/admin/reporter-outbox/${messageId}/${verb}`, {
|
|
5527
|
+
method: "POST",
|
|
5528
|
+
body: JSON.stringify(verb === "release" && bodyOverride ? { body_override: bodyOverride } : {})
|
|
5529
|
+
});
|
|
5530
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
5531
|
+
}
|
|
5532
|
+
);
|
|
4698
5533
|
server.registerTool(
|
|
4699
5534
|
"list_qa_story_runs",
|
|
4700
5535
|
{
|
|
@@ -5135,6 +5970,66 @@ Be specific. No generic advice.`
|
|
|
5135
5970
|
return jsonText(data);
|
|
5136
5971
|
}
|
|
5137
5972
|
);
|
|
5973
|
+
server.registerTool(
|
|
5974
|
+
"get_repo_digest",
|
|
5975
|
+
{
|
|
5976
|
+
title: titleOf("get_repo_digest", CODEBASE_TOOL_CATALOG),
|
|
5977
|
+
description: descOf("get_repo_digest", CODEBASE_TOOL_CATALOG),
|
|
5978
|
+
annotations: annotationsFor("get_repo_digest", CODEBASE_TOOL_CATALOG),
|
|
5979
|
+
inputSchema: z.object({
|
|
5980
|
+
projectId: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
5981
|
+
reportId: z.string().optional().describe("Scope to one bug: the report's files come first, then the rest of the repo by priority"),
|
|
5982
|
+
path: z.string().optional().describe('Scope to one folder, e.g. "apps/web/src"'),
|
|
5983
|
+
budgetTokens: z.number().int().min(2e3).max(2e5).optional().describe("Estimated token budget for the whole digest (default 50,000)"),
|
|
5984
|
+
include: z.array(z.string()).max(50).optional().describe('Glob patterns a file must match, e.g. ["src/**/*.ts", "*.md"]'),
|
|
5985
|
+
exclude: z.array(z.string()).max(50).optional().describe('Glob patterns to leave out, e.g. ["tests/"]'),
|
|
5986
|
+
ref: z.string().optional().describe("Branch, tag or commit SHA (default: the repo default branch)")
|
|
5987
|
+
})
|
|
5988
|
+
},
|
|
5989
|
+
async (args) => {
|
|
5990
|
+
const pid = args.projectId ?? projectId;
|
|
5991
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
5992
|
+
const qs = new URLSearchParams();
|
|
5993
|
+
if (args.budgetTokens) qs.set("budget", String(args.budgetTokens));
|
|
5994
|
+
if (args.reportId) qs.set("report_id", args.reportId);
|
|
5995
|
+
if (args.path) qs.set("path", args.path);
|
|
5996
|
+
if (args.include?.length) qs.set("include", args.include.join(","));
|
|
5997
|
+
if (args.exclude?.length) qs.set("exclude", args.exclude.join(","));
|
|
5998
|
+
if (args.ref) qs.set("ref", args.ref);
|
|
5999
|
+
const query = qs.toString();
|
|
6000
|
+
const data = await apiCall(
|
|
6001
|
+
`/v1/admin/projects/${pid}/codebase/digest${query ? `?${query}` : ""}`,
|
|
6002
|
+
void 0,
|
|
6003
|
+
// The server reads up to a few hundred files from GitHub (60 s deadline).
|
|
6004
|
+
{ minTimeoutMs: 9e4 }
|
|
6005
|
+
);
|
|
6006
|
+
return jsonText(data);
|
|
6007
|
+
}
|
|
6008
|
+
);
|
|
6009
|
+
server.registerTool(
|
|
6010
|
+
"get_repo_diagram",
|
|
6011
|
+
{
|
|
6012
|
+
title: titleOf("get_repo_diagram", CODEBASE_TOOL_CATALOG),
|
|
6013
|
+
description: descOf("get_repo_diagram", CODEBASE_TOOL_CATALOG),
|
|
6014
|
+
annotations: annotationsFor("get_repo_diagram", CODEBASE_TOOL_CATALOG),
|
|
6015
|
+
inputSchema: z.object({
|
|
6016
|
+
projectId: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
6017
|
+
overlay: z.boolean().optional().describe("Also place the open bug reports and code findings on each part of the diagram")
|
|
6018
|
+
})
|
|
6019
|
+
},
|
|
6020
|
+
async (args) => {
|
|
6021
|
+
const pid = encodeURIComponent(await resolveProjectId(args.projectId));
|
|
6022
|
+
const data = await apiCall(
|
|
6023
|
+
`/v1/admin/projects/${pid}/codebase/diagram`
|
|
6024
|
+
);
|
|
6025
|
+
if (args.overlay !== true) return jsonText(data);
|
|
6026
|
+
const overlay = data.diagram ? await apiCall(`/v1/admin/projects/${pid}/codebase/diagram/overlay`, void 0, {
|
|
6027
|
+
// One GitHub tree read plus up to a few hundred reports.
|
|
6028
|
+
minTimeoutMs: 6e4
|
|
6029
|
+
}) : null;
|
|
6030
|
+
return jsonText({ ...data, overlay });
|
|
6031
|
+
}
|
|
6032
|
+
);
|
|
5138
6033
|
server.registerTool(
|
|
5139
6034
|
"use_mushi",
|
|
5140
6035
|
{
|