@bragi-gmbh/codebus 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/bin/codebus.js +2 -0
  2. package/dist/binding-guard.js +97 -0
  3. package/dist/binding-guard.js.map +1 -0
  4. package/dist/client.js +94 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/commands/ack.js +47 -0
  7. package/dist/commands/ack.js.map +1 -0
  8. package/dist/commands/archive.js +21 -0
  9. package/dist/commands/archive.js.map +1 -0
  10. package/dist/commands/as.js +299 -0
  11. package/dist/commands/as.js.map +1 -0
  12. package/dist/commands/ask.js +189 -0
  13. package/dist/commands/ask.js.map +1 -0
  14. package/dist/commands/auth.js +33 -0
  15. package/dist/commands/auth.js.map +1 -0
  16. package/dist/commands/checkin.js +235 -0
  17. package/dist/commands/checkin.js.map +1 -0
  18. package/dist/commands/decision.js +52 -0
  19. package/dist/commands/decision.js.map +1 -0
  20. package/dist/commands/digest.js +117 -0
  21. package/dist/commands/digest.js.map +1 -0
  22. package/dist/commands/doctor.js +106 -0
  23. package/dist/commands/doctor.js.map +1 -0
  24. package/dist/commands/factory.js +191 -0
  25. package/dist/commands/factory.js.map +1 -0
  26. package/dist/commands/github-login.js +205 -0
  27. package/dist/commands/github-login.js.map +1 -0
  28. package/dist/commands/graph.js +88 -0
  29. package/dist/commands/graph.js.map +1 -0
  30. package/dist/commands/health.js +34 -0
  31. package/dist/commands/health.js.map +1 -0
  32. package/dist/commands/inbox.js +83 -0
  33. package/dist/commands/inbox.js.map +1 -0
  34. package/dist/commands/ingest.js +553 -0
  35. package/dist/commands/ingest.js.map +1 -0
  36. package/dist/commands/init.js +97 -0
  37. package/dist/commands/init.js.map +1 -0
  38. package/dist/commands/install.js +520 -0
  39. package/dist/commands/install.js.map +1 -0
  40. package/dist/commands/invite.js +152 -0
  41. package/dist/commands/invite.js.map +1 -0
  42. package/dist/commands/jira.js +237 -0
  43. package/dist/commands/jira.js.map +1 -0
  44. package/dist/commands/join.js +96 -0
  45. package/dist/commands/join.js.map +1 -0
  46. package/dist/commands/links.js +66 -0
  47. package/dist/commands/links.js.map +1 -0
  48. package/dist/commands/login.js +84 -0
  49. package/dist/commands/login.js.map +1 -0
  50. package/dist/commands/morning.js +121 -0
  51. package/dist/commands/morning.js.map +1 -0
  52. package/dist/commands/next.js +66 -0
  53. package/dist/commands/next.js.map +1 -0
  54. package/dist/commands/onboarding.js +109 -0
  55. package/dist/commands/onboarding.js.map +1 -0
  56. package/dist/commands/pack.js +276 -0
  57. package/dist/commands/pack.js.map +1 -0
  58. package/dist/commands/plan-gh.js +425 -0
  59. package/dist/commands/plan-gh.js.map +1 -0
  60. package/dist/commands/plan.js +465 -0
  61. package/dist/commands/plan.js.map +1 -0
  62. package/dist/commands/pr.js +88 -0
  63. package/dist/commands/pr.js.map +1 -0
  64. package/dist/commands/question.js +142 -0
  65. package/dist/commands/question.js.map +1 -0
  66. package/dist/commands/read.js +84 -0
  67. package/dist/commands/read.js.map +1 -0
  68. package/dist/commands/refs.js +357 -0
  69. package/dist/commands/refs.js.map +1 -0
  70. package/dist/commands/reply.js +38 -0
  71. package/dist/commands/reply.js.map +1 -0
  72. package/dist/commands/repo.js +1198 -0
  73. package/dist/commands/repo.js.map +1 -0
  74. package/dist/commands/report.js +41 -0
  75. package/dist/commands/report.js.map +1 -0
  76. package/dist/commands/resolve-refs.js +21 -0
  77. package/dist/commands/resolve-refs.js.map +1 -0
  78. package/dist/commands/resolve.js +93 -0
  79. package/dist/commands/resolve.js.map +1 -0
  80. package/dist/commands/send.js +168 -0
  81. package/dist/commands/send.js.map +1 -0
  82. package/dist/commands/sent.js +22 -0
  83. package/dist/commands/sent.js.map +1 -0
  84. package/dist/commands/session.js +426 -0
  85. package/dist/commands/session.js.map +1 -0
  86. package/dist/commands/statusline.js +43 -0
  87. package/dist/commands/statusline.js.map +1 -0
  88. package/dist/commands/task.js +140 -0
  89. package/dist/commands/task.js.map +1 -0
  90. package/dist/commands/tick.js +233 -0
  91. package/dist/commands/tick.js.map +1 -0
  92. package/dist/commands/upgrade.js +76 -0
  93. package/dist/commands/upgrade.js.map +1 -0
  94. package/dist/commands/watch.js +169 -0
  95. package/dist/commands/watch.js.map +1 -0
  96. package/dist/commands/whoami.js +27 -0
  97. package/dist/commands/whoami.js.map +1 -0
  98. package/dist/config.js +103 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/directives.js +102 -0
  101. package/dist/directives.js.map +1 -0
  102. package/dist/errors.js +110 -0
  103. package/dist/errors.js.map +1 -0
  104. package/dist/git-hooks.js +116 -0
  105. package/dist/git-hooks.js.map +1 -0
  106. package/dist/graph-bootstrap.js +95 -0
  107. package/dist/graph-bootstrap.js.map +1 -0
  108. package/dist/graph-remote.js +889 -0
  109. package/dist/graph-remote.js.map +1 -0
  110. package/dist/graph.js +335 -0
  111. package/dist/graph.js.map +1 -0
  112. package/dist/index.js +154 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/kit/graph-publish.mjs +400 -0
  115. package/dist/mail-latency.js +120 -0
  116. package/dist/mail-latency.js.map +1 -0
  117. package/dist/mcp-registry.js +859 -0
  118. package/dist/mcp-registry.js.map +1 -0
  119. package/dist/mcp.js +193 -0
  120. package/dist/mcp.js.map +1 -0
  121. package/dist/output.js +12 -0
  122. package/dist/output.js.map +1 -0
  123. package/dist/positioning.js +21 -0
  124. package/dist/positioning.js.map +1 -0
  125. package/dist/rolestore.js +89 -0
  126. package/dist/rolestore.js.map +1 -0
  127. package/dist/session.js +49 -0
  128. package/dist/session.js.map +1 -0
  129. package/dist/sessionstore.js +736 -0
  130. package/dist/sessionstore.js.map +1 -0
  131. package/dist/version-check.js +97 -0
  132. package/dist/version-check.js.map +1 -0
  133. package/package.json +47 -0
@@ -0,0 +1,859 @@
1
+ /**
2
+ * MCP tool registry — pure data + handler logic for every CodeBus MCP tool.
3
+ * Deliberately side-effect-free at import time: no stdio, no transport, no
4
+ * network call fires just by loading this module. mcp.ts (the executable)
5
+ * imports TOOL_REGISTRY from here to actually serve it over stdio;
6
+ * install.ts imports it too, purely to derive a tool count for its
7
+ * messaging (F5) — that import must never risk booting a server (F1: this
8
+ * split is what makes that guarantee hold regardless of how mcp.ts's own
9
+ * entry-point guard is invoked, symlinked bin included).
10
+ */
11
+ import { askGraph } from "./commands/ask.js";
12
+ import { makeCtx, workspaceId, projectId } from "./client.js";
13
+ import { resolveConfig } from "./config.js";
14
+ import { resolveRemoteRepoSlug, graphSearchRpc, graphGapsRpc, resolveBlastRadius, } from "./graph-remote.js";
15
+ import { fetchAndCacheDigest, nextActionableItem } from "./commands/digest.js";
16
+ import { fetchInbox } from "./commands/inbox.js";
17
+ import { computeNext } from "./commands/next.js";
18
+ import { createQuestionCore, answerQuestionCore } from "./commands/question.js";
19
+ import { createTaskCore, claimTaskCore, completeTaskCore } from "./commands/task.js";
20
+ import { createDecisionCore, acceptDecisionCore } from "./commands/decision.js";
21
+ import { sendMessageCore } from "./commands/send.js";
22
+ import { planRollupCore, planDeliveryCore } from "./commands/plan.js";
23
+ import { endSessionCore } from "./commands/session.js";
24
+ import { compilePackCore } from "./commands/pack.js";
25
+ import { resolveRefsCore } from "./commands/refs.js";
26
+ import { CodebusError } from "./errors.js";
27
+ // This module builds the tool registry only — it never touches stdio or a
28
+ // transport, so importing it has zero side effects on the process (no server
29
+ // boot). That is what lets install.ts import TOOL_REGISTRY to DERIVE the
30
+ // tool count it reports to Codex/Claude users (F5, instead of hardcoding a
31
+ // number that rots every time a tool is added or removed) without also
32
+ // risking starting an MCP stdio server as a side effect of running
33
+ // `codebus install mcp`. mcp.ts — the actual executable — imports this same
34
+ // TOOL_REGISTRY to serve it over stdio; the server-boot guard lives there,
35
+ // not here, because booting is the one thing this module must never do.
36
+ export const TOOL_REGISTRY = [];
37
+ // ── ask_graph (existing tool) ──────────────────────────────────────────────
38
+ export async function askGraphHandler(args) {
39
+ const question = args?.question || "";
40
+ const budget = args?.budget || 500;
41
+ if (!question) {
42
+ throw new Error("'question' parameter is required");
43
+ }
44
+ // A6: optional repo routes through resolveGraphSource's remote leg; with no
45
+ // repo and a local snapshot present this is unchanged (zero-credential,
46
+ // zero-network) — ask_graph stays the only zero-credential MCP tool.
47
+ const result = await askGraph(question, { budget, repo: args?.repo });
48
+ if (result.error) {
49
+ throw new Error(result.error);
50
+ }
51
+ let answer = result.answer;
52
+ if (result.stalenessNote) {
53
+ answer += `\n\n[${result.stalenessNote}]`;
54
+ }
55
+ return { answer };
56
+ }
57
+ TOOL_REGISTRY.push({
58
+ name: "ask_graph",
59
+ description: "Query the CodeBus graph snapshot with a natural language question. " +
60
+ "Returns a compact cited answer from graphify-out/, budgeted to ~500 output tokens by default. " +
61
+ "Automatically detects and reports staleness vs HEAD. Optional 'repo' queries a connected " +
62
+ "repo's remote graph instead of (or in addition to) the local checkout; citations are marked " +
63
+ "[remote@sha7] when the answer came from there.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ question: {
68
+ type: "string",
69
+ description: "The question to ask about the codebase graph (e.g., 'CLI commands for identity')",
70
+ },
71
+ budget: {
72
+ type: "number",
73
+ description: "Max output tokens (optional, default 500)",
74
+ },
75
+ repo: {
76
+ type: "string",
77
+ description: "Connected repo slug to query remotely (optional; local checkout wins when present and this is omitted or matches it)",
78
+ },
79
+ },
80
+ required: ["question"],
81
+ },
82
+ handler: askGraphHandler,
83
+ });
84
+ // ── graph_search (new, remote-only) ────────────────────────────────────────
85
+ async function graphSearchHandler(args) {
86
+ const q = args?.q || "";
87
+ if (!q)
88
+ throw new CodebusError("'q' parameter is required");
89
+ const ctx = await makeCtx(resolveConfig({}));
90
+ const repoSlug = await resolveRemoteRepoSlug(ctx, process.cwd(), args?.repo);
91
+ const results = await graphSearchRpc(ctx, repoSlug, q, args?.limit ?? 20);
92
+ return { repo: repoSlug, results };
93
+ }
94
+ TOOL_REGISTRY.push({
95
+ name: "graph_search",
96
+ description: "Trigram-ranked node search over a CONNECTED repo's knowledge graph (remote; no local fallback). " +
97
+ "Optional 'repo' slug; auto-detected from this directory's git remote when omitted.",
98
+ inputSchema: {
99
+ type: "object",
100
+ properties: {
101
+ q: { type: "string", description: "Search text" },
102
+ repo: { type: "string", description: "Repo slug (optional; auto-detected from git remote if omitted)" },
103
+ limit: { type: "integer", description: "Max rows (optional, default 20, hard-capped at 100)" },
104
+ },
105
+ required: ["q"],
106
+ },
107
+ handler: graphSearchHandler,
108
+ });
109
+ // ── blast_radius (new, local-traversal-or-RPC) ─────────────────────────────
110
+ async function blastRadiusHandler(args) {
111
+ const seed = args?.seed || "";
112
+ if (!seed)
113
+ throw new CodebusError("'seed' parameter is required");
114
+ const result = await resolveBlastRadius(seed, {
115
+ repo: args?.repo,
116
+ direction: args?.direction,
117
+ depth: args?.depth,
118
+ relations: Array.isArray(args?.relations) ? args.relations : undefined,
119
+ });
120
+ return {
121
+ source: result.source,
122
+ repo: result.repoSlug,
123
+ seed,
124
+ direction: args?.direction ?? "in",
125
+ results: result.rows,
126
+ truncated: result.truncated ?? false,
127
+ };
128
+ }
129
+ TOOL_REGISTRY.push({
130
+ name: "blast_radius",
131
+ description: "What breaks if I change this: BFS from one seed (node id, source file path, or label) over a " +
132
+ "knowledge graph. direction='in' (default) finds what DEPENDS ON the seed; 'out' finds what the " +
133
+ "seed depends on; 'both' is the union. Uses the local graphify-out/ snapshot when present " +
134
+ "(no credentials needed); otherwise queries a connected repo remotely (optional 'repo', " +
135
+ "auto-detected from git remote).",
136
+ inputSchema: {
137
+ type: "object",
138
+ properties: {
139
+ seed: { type: "string", description: "Seed node id, source file path, or label to resolve" },
140
+ repo: { type: "string", description: "Repo slug for the remote leg (optional; auto-detected from git remote if omitted)" },
141
+ direction: { type: "string", enum: ["in", "out", "both"], description: "Traversal direction (optional, default 'in')" },
142
+ depth: { type: "integer", description: "Max hops (optional, default 3, capped at 6)" },
143
+ relations: { type: "array", items: { type: "string" }, description: "Edge relation types to follow (optional; default set applies when omitted)" },
144
+ },
145
+ required: ["seed"],
146
+ },
147
+ handler: blastRadiusHandler,
148
+ });
149
+ // ── graph_gaps (new, remote-only) ───────────────────────────────────────────
150
+ async function graphGapsHandler(args) {
151
+ const ctx = await makeCtx(resolveConfig({}));
152
+ const repoSlug = await resolveRemoteRepoSlug(ctx, process.cwd(), args?.repo);
153
+ const gaps = await graphGapsRpc(ctx, repoSlug);
154
+ return { repo: repoSlug, gaps, truncated: gaps.length >= 200 };
155
+ }
156
+ TOOL_REGISTRY.push({
157
+ name: "graph_gaps",
158
+ description: "Lists knowledge gaps in a CONNECTED repo's graph (remote; no local fallback): isolated nodes, " +
159
+ "low-confidence edges, undocumented symbols. Truncated to ~200 rows. Optional 'repo' slug; " +
160
+ "auto-detected from this directory's git remote when omitted.",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ repo: { type: "string", description: "Repo slug (optional; auto-detected from git remote if omitted)" },
165
+ },
166
+ required: [],
167
+ },
168
+ handler: graphGapsHandler,
169
+ });
170
+ // ── ticket_suspect_code (new, remote-only) — repo-intelligence Track D3 ────
171
+ async function ticketSuspectCodeHandler(args) {
172
+ const key = args?.ticket_key || "";
173
+ if (!key)
174
+ throw new CodebusError("'ticket_key' parameter is required");
175
+ const ctx = await makeCtx(resolveConfig({}));
176
+ // p_workspace_slug is ALWAYS passed (ctx.workspaceSlug), same convention
177
+ // graph-remote.ts uses for every graph query RPC — removes even the
178
+ // theoretical ambiguity window 0068's own header describes (a ticket key
179
+ // shared across >1 of the caller's own workspaces) rather than relying on
180
+ // that RPC's strict-fallback path ever being reached from this tool.
181
+ const { data, error } = await ctx.supabase.rpc("ticket_suspect_code", {
182
+ p_ticket_key: key,
183
+ p_repo_slug: args?.repo ?? null,
184
+ p_workspace_slug: ctx.workspaceSlug,
185
+ p_limit: args?.limit ?? undefined,
186
+ p_recent_days: args?.recent_days ?? undefined,
187
+ });
188
+ if (error)
189
+ throw new Error(error.message);
190
+ return data;
191
+ }
192
+ TOOL_REGISTRY.push({
193
+ name: "ticket_suspect_code",
194
+ description: "Ranked, LABELED evidence for which code most probably relates to a Jira ticket — never a claim of " +
195
+ "causation (every response carries a fixed disclaimer and per-item provenance). Combines the ticket's " +
196
+ "own linked-PR touched files, a blast-radius expansion from those files, a text-match against the " +
197
+ "ticket's summary, and a recency overlay. Optional 'repo' scopes every leg to one repo (required only " +
198
+ "when the ticket has no linked PRs and the workspace graphs more than one repo).",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ ticket_key: { type: "string", description: "Ticket key, e.g. PROJ-123" },
203
+ repo: { type: "string", description: "Repo slug to scope every evidence leg to (optional; required in some zero-linked-PR cases)" },
204
+ limit: { type: "integer", description: "Max ranked rows (optional, default 30, hard-clamped to [1,50])" },
205
+ recent_days: { type: "integer", description: "Recency-leg lookback window in days (optional, default 14, hard-clamped to [1,90])" },
206
+ },
207
+ required: ["ticket_key"],
208
+ },
209
+ handler: ticketSuspectCodeHandler,
210
+ });
211
+ // ── codebus_state (new read-only tool) ────────────────────────────────────
212
+ // Extracted from session start: returns the digest without session_log writes or welcome flow
213
+ async function getStateDigest(ctx) {
214
+ const snapshot = await fetchAndCacheDigest(ctx);
215
+ // review finding (0061): same rule as computeNext (next.ts) — a class-8 FYI
216
+ // message must never be reported as "the" next item; see nextActionableItem
217
+ // in digest.ts for why.
218
+ const top = nextActionableItem(snapshot.items);
219
+ // Get person for this agent
220
+ const { data } = await ctx.supabase
221
+ .from("agents")
222
+ .select("people!person_id(slug)")
223
+ .eq("slug", ctx.agentSlug)
224
+ .single();
225
+ const person = data?.people?.slug ?? null;
226
+ return {
227
+ agent: ctx.agentSlug,
228
+ person,
229
+ workspace: ctx.workspaceSlug,
230
+ counts: snapshot.counts,
231
+ next: top,
232
+ };
233
+ }
234
+ async function codebus_stateHandler(args) {
235
+ const ctx = await makeCtx(resolveConfig({}));
236
+ return await getStateDigest(ctx);
237
+ }
238
+ TOOL_REGISTRY.push({
239
+ name: "codebus_state",
240
+ description: "Get the current session-start digest: counts by priority class (ASK, BLOCKER, REVIEW, STALE, DECISION, UNCLAIMED), " +
241
+ "top NEXT item, workspace, agent, and person. Read-only (no side effects).",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {},
245
+ required: [],
246
+ },
247
+ handler: codebus_stateHandler,
248
+ });
249
+ // ── codebus_next (new read-only tool) ──────────────────────────────────────
250
+ async function codebus_nextHandler(args) {
251
+ const ctx = await makeCtx(resolveConfig({}));
252
+ return await computeNext(ctx);
253
+ }
254
+ TOOL_REGISTRY.push({
255
+ name: "codebus_next",
256
+ description: "Get the ranked NEXT item: the highest-priority work item with its reason, score, and suggested follow-up commands. " +
257
+ "Returns null if the bus is clear. Read-only.",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {},
261
+ required: [],
262
+ },
263
+ handler: codebus_nextHandler,
264
+ });
265
+ // ── codebus_inbox (new read-only tool) ────────────────────────────────────
266
+ async function codebus_inboxHandler(args) {
267
+ const ctx = await makeCtx(resolveConfig({}));
268
+ const ws = await workspaceId(ctx);
269
+ const projId = args?.project ? await projectId(ctx, ws, args.project) : null;
270
+ return await fetchInbox(ctx, {
271
+ unread: args?.unread,
272
+ type: args?.type,
273
+ projId,
274
+ ws,
275
+ });
276
+ }
277
+ TOOL_REGISTRY.push({
278
+ name: "codebus_inbox",
279
+ description: "Get inbox items (messages) with optional filters. By default returns all unarchived items. " +
280
+ "Supports unread-only filter and type filter (e.g., 'feedback', 'task_update'). Read-only.",
281
+ inputSchema: {
282
+ type: "object",
283
+ properties: {
284
+ unread: {
285
+ type: "boolean",
286
+ description: "Filter to unread messages only (optional, default false)",
287
+ },
288
+ type: {
289
+ type: "string",
290
+ description: "Filter by message type (optional, e.g., 'feedback', 'task_update')",
291
+ },
292
+ project: {
293
+ type: "string",
294
+ description: "Filter by project slug (optional)",
295
+ },
296
+ },
297
+ required: [],
298
+ },
299
+ handler: codebus_inboxHandler,
300
+ });
301
+ // ── question (act tool) ────────────────────────────────────────────────────
302
+ async function questionCreateHandler(args) {
303
+ const title = args?.title || "";
304
+ const body = args?.body || "";
305
+ if (!title || !body) {
306
+ throw new CodebusError("'title' and 'body' parameters are required");
307
+ }
308
+ const ctx = await makeCtx(resolveConfig({}));
309
+ const result = await createQuestionCore(ctx, {
310
+ title,
311
+ body,
312
+ to: args?.to,
313
+ toMentor: args?.to_mentor,
314
+ repo: args?.repo,
315
+ blocking: args?.blocking,
316
+ blocksTask: args?.blocks_task,
317
+ required: args?.required,
318
+ });
319
+ return { display_id: result.display_id, status: result.status, to: result.to };
320
+ }
321
+ async function questionAnswerHandler(args) {
322
+ const id = args?.id || "";
323
+ const body = args?.body || "";
324
+ if (!id || !body) {
325
+ throw new CodebusError("'id' and 'body' parameters are required");
326
+ }
327
+ const ctx = await makeCtx(resolveConfig({}));
328
+ const result = await answerQuestionCore(ctx, id, body);
329
+ return { display_id: result.display_id, status: result.status, answered_at: result.answered_at };
330
+ }
331
+ TOOL_REGISTRY.push({
332
+ name: "question_create",
333
+ description: "Create a new question. Questions route to specific agents or broadcast to the workspace. " +
334
+ "Returns the question display_id, status, and recipient.",
335
+ inputSchema: {
336
+ type: "object",
337
+ properties: {
338
+ title: {
339
+ type: "string",
340
+ description: "Question title (required)",
341
+ },
342
+ body: {
343
+ type: "string",
344
+ description: "Question body (required)",
345
+ },
346
+ to: {
347
+ type: "string",
348
+ description: "Recipient agent slug (optional; omit for broadcast)",
349
+ },
350
+ to_mentor: {
351
+ type: "boolean",
352
+ description: "Route to your first-week mentor instead (optional)",
353
+ },
354
+ repo: {
355
+ type: "string",
356
+ description: "Associated repo slug (optional)",
357
+ },
358
+ blocking: {
359
+ type: "boolean",
360
+ description: "Mark as blocking work (optional)",
361
+ },
362
+ blocks_task: {
363
+ type: "string",
364
+ description: "CB-TASK-… id this question blocks (optional)",
365
+ },
366
+ required: {
367
+ type: "string",
368
+ description: "What a valid answer must contain (optional)",
369
+ },
370
+ },
371
+ required: ["title", "body"],
372
+ },
373
+ handler: questionCreateHandler,
374
+ });
375
+ TOOL_REGISTRY.push({
376
+ name: "question_answer",
377
+ description: "Answer an open question. The question must have status='open'. " +
378
+ "Returns the question display_id, updated status, and answered_at timestamp.",
379
+ inputSchema: {
380
+ type: "object",
381
+ properties: {
382
+ id: {
383
+ type: "string",
384
+ description: "Question ID (CB-Q-… or uuid)",
385
+ },
386
+ body: {
387
+ type: "string",
388
+ description: "Answer text (required)",
389
+ },
390
+ },
391
+ required: ["id", "body"],
392
+ },
393
+ handler: questionAnswerHandler,
394
+ });
395
+ // ── task (act tool) ────────────────────────────────────────────────────────
396
+ async function taskCreateHandler(args) {
397
+ const title = args?.title || "";
398
+ if (!title) {
399
+ throw new CodebusError("'title' parameter is required");
400
+ }
401
+ const ctx = await makeCtx(resolveConfig({}));
402
+ const rawRefs = Array.isArray(args?.refs) ? args.refs : undefined;
403
+ const result = await createTaskCore(ctx, {
404
+ title,
405
+ body: args?.body,
406
+ owner: args?.owner,
407
+ project: args?.project,
408
+ refs: rawRefs,
409
+ });
410
+ return { display_id: result.display_id, title: result.title, status: result.status };
411
+ }
412
+ async function taskClaimHandler(args) {
413
+ const id = args?.id || "";
414
+ if (!id) {
415
+ throw new CodebusError("'id' parameter is required");
416
+ }
417
+ const ctx = await makeCtx(resolveConfig({}));
418
+ const result = await claimTaskCore(ctx, id);
419
+ return { display_id: result.display_id, status: result.status, done: result.done };
420
+ }
421
+ async function taskDoneHandler(args) {
422
+ const id = args?.id || "";
423
+ if (!id) {
424
+ throw new CodebusError("'id' parameter is required");
425
+ }
426
+ const ctx = await makeCtx(resolveConfig({}));
427
+ const result = await completeTaskCore(ctx, id, {
428
+ evidence: args?.evidence,
429
+ note: args?.note,
430
+ noEvidenceReason: args?.no_evidence_reason,
431
+ });
432
+ return { display_id: result.display_id, status: result.status, done: result.done };
433
+ }
434
+ TOOL_REGISTRY.push({
435
+ name: "task_create",
436
+ description: "Create a new task. Returns the task display_id, title, and initial status.",
437
+ inputSchema: {
438
+ type: "object",
439
+ properties: {
440
+ title: {
441
+ type: "string",
442
+ description: "Task title (required)",
443
+ },
444
+ body: {
445
+ type: "string",
446
+ description: "Task description (optional)",
447
+ },
448
+ owner: {
449
+ type: "string",
450
+ description: "Owner agent slug (optional)",
451
+ },
452
+ project: {
453
+ type: "string",
454
+ description: "Project slug (optional)",
455
+ },
456
+ refs: {
457
+ type: "array",
458
+ items: { type: "string" },
459
+ description: "Knowledge refs to attach (optional, e.g. ['CB-DEC-000001', 'commit:abc1234'])",
460
+ },
461
+ },
462
+ required: ["title"],
463
+ },
464
+ handler: taskCreateHandler,
465
+ });
466
+ TOOL_REGISTRY.push({
467
+ name: "task_claim",
468
+ description: "Claim an open task. The task must have status!='claimed'. " +
469
+ "Returns the task display_id, updated status, and done flag.",
470
+ inputSchema: {
471
+ type: "object",
472
+ properties: {
473
+ id: {
474
+ type: "string",
475
+ description: "Task ID (CB-TASK-… or uuid)",
476
+ },
477
+ },
478
+ required: ["id"],
479
+ },
480
+ handler: taskClaimHandler,
481
+ });
482
+ TOOL_REGISTRY.push({
483
+ name: "task_done",
484
+ description: "Mark a claimed task as done. Requires EITHER evidence OR an explicit " +
485
+ "no_evidence_reason — bare completion with neither is refused by the " +
486
+ "server (reliability plan §4 item 2: evidence-required done). " +
487
+ "Returns the task display_id, updated status, and done flag.",
488
+ inputSchema: {
489
+ type: "object",
490
+ properties: {
491
+ id: {
492
+ type: "string",
493
+ description: "Task ID (CB-TASK-… or uuid)",
494
+ },
495
+ evidence: {
496
+ type: "string",
497
+ description: "Evidence reference (e.g., commit sha or PR url). Required unless no_evidence_reason is given.",
498
+ },
499
+ note: {
500
+ type: "string",
501
+ description: "Completion note (optional)",
502
+ },
503
+ no_evidence_reason: {
504
+ type: "string",
505
+ description: "Explicit reason there is no evidence to attach — recorded, not silent. Required if evidence is omitted.",
506
+ },
507
+ },
508
+ required: ["id"],
509
+ },
510
+ handler: taskDoneHandler,
511
+ });
512
+ // ── decision (act tool) ────────────────────────────────────────────────────
513
+ async function decisionRecordHandler(args) {
514
+ const title = args?.title || "";
515
+ if (!title) {
516
+ throw new CodebusError("'title' parameter is required");
517
+ }
518
+ const ctx = await makeCtx(resolveConfig({}));
519
+ const result = await createDecisionCore(ctx, {
520
+ title,
521
+ slug: args?.slug,
522
+ body: args?.body,
523
+ });
524
+ return { display_id: result.display_id, slug: result.slug, status: result.status };
525
+ }
526
+ async function decisionAcceptHandler(args) {
527
+ const id = args?.id || "";
528
+ if (!id) {
529
+ throw new CodebusError("'id' parameter is required");
530
+ }
531
+ const ctx = await makeCtx(resolveConfig({}));
532
+ const result = await acceptDecisionCore(ctx, id);
533
+ return { display_id: result.display_id, status: result.status };
534
+ }
535
+ TOOL_REGISTRY.push({
536
+ name: "decision_record",
537
+ description: "Record a new decision. Decisions encode team consensus and architectural choices. " +
538
+ "Returns the decision display_id (e.g., CB-DEC-001), slug, and initial status.",
539
+ inputSchema: {
540
+ type: "object",
541
+ properties: {
542
+ title: {
543
+ type: "string",
544
+ description: "Decision title (required)",
545
+ },
546
+ slug: {
547
+ type: "string",
548
+ description: "Optional slug (auto-generated from title if omitted)",
549
+ },
550
+ body: {
551
+ type: "string",
552
+ description: "Decision rationale and details (optional)",
553
+ },
554
+ },
555
+ required: ["title"],
556
+ },
557
+ handler: decisionRecordHandler,
558
+ });
559
+ TOOL_REGISTRY.push({
560
+ name: "decision_accept",
561
+ description: "Accept a decision (owner-only). Marks it as accepted. " +
562
+ "Returns the decision display_id and updated status.",
563
+ inputSchema: {
564
+ type: "object",
565
+ properties: {
566
+ id: {
567
+ type: "string",
568
+ description: "Decision ID (CB-DEC-… or uuid)",
569
+ },
570
+ },
571
+ required: ["id"],
572
+ },
573
+ handler: decisionAcceptHandler,
574
+ });
575
+ // ── send (act tool) ────────────────────────────────────────────────────────
576
+ async function sendHandler(args) {
577
+ const to = args?.to || "";
578
+ const body = args?.body || "";
579
+ const type = args?.type || "";
580
+ if (!to || !body || !type) {
581
+ throw new CodebusError("'to', 'body', and 'type' parameters are required");
582
+ }
583
+ const ctx = await makeCtx(resolveConfig({}));
584
+ const rawRefs = Array.isArray(args?.refs) ? args.refs : undefined;
585
+ const result = await sendMessageCore(ctx, {
586
+ to,
587
+ body,
588
+ type,
589
+ subject: args?.subject,
590
+ project: args?.project,
591
+ replyTo: args?.reply_to,
592
+ refs: rawRefs,
593
+ urgency: args?.urgency,
594
+ // MINOR fix (review finding #8): the MCP send tool silently dropped
595
+ // headline/required_action — sendMessageCore/send_message support the
596
+ // structured-handover fields (0036/0048), but nothing on this path ever
597
+ // forwarded them, so an MCP-originated send could never carry one.
598
+ headline: args?.headline,
599
+ requiredAction: args?.required_action,
600
+ });
601
+ return {
602
+ display_id: result.display_id, type: result.type, to: result.to, urgency: result.urgency,
603
+ headline: result.headline, required_action: result.required_action,
604
+ // RC2 sender-side nudge: present (non-null) when a `note` with no
605
+ // required_action reads like a request — advisory only, never blocks the
606
+ // send. The MCP path has no stderr the calling agent would see, so this
607
+ // travels in the tool result itself instead (see send.ts sendMessageCore).
608
+ warning: result.warning,
609
+ };
610
+ }
611
+ TOOL_REGISTRY.push({
612
+ name: "send",
613
+ description: "Send a message to one or more agents. Recipients are comma-separated agent slugs. " +
614
+ "Valid message types: ask, handoff, note, plan. Optionally attach a structured handover " +
615
+ "(headline + required_action). " +
616
+ "Returns the message display_id, type, recipient list, urgency, headline, and required_action. " +
617
+ "A non-null `warning` in the result means this note reads like a work order — " +
618
+ "consider resending with type=ask/handoff or required_action so it surfaces as unhandled work.",
619
+ inputSchema: {
620
+ type: "object",
621
+ properties: {
622
+ to: {
623
+ type: "string",
624
+ description: "Recipient agent slugs, comma-separated (required)",
625
+ },
626
+ body: {
627
+ type: "string",
628
+ description: "Message body (required)",
629
+ },
630
+ type: {
631
+ type: "string",
632
+ description: "Message type: ask|handoff|note|plan (required)",
633
+ enum: ["ask", "handoff", "note", "plan"],
634
+ },
635
+ subject: {
636
+ type: "string",
637
+ description: "Message subject line (optional)",
638
+ },
639
+ project: {
640
+ type: "string",
641
+ description: "Project slug (optional)",
642
+ },
643
+ reply_to: {
644
+ type: "string",
645
+ description: "Message ID to reply to (optional)",
646
+ },
647
+ headline: {
648
+ type: "string",
649
+ description: "One-line summary of the handover, surfaced ahead of the body by read/inbox (optional)",
650
+ },
651
+ required_action: {
652
+ type: "string",
653
+ description: "The concrete action the recipient owes in response (optional)",
654
+ },
655
+ refs: {
656
+ type: "array",
657
+ items: { type: "string" },
658
+ description: "Knowledge refs to attach (optional, e.g. ['CB-DEC-000001', 'commit:abc1234'])",
659
+ },
660
+ urgency: {
661
+ type: "string",
662
+ description: "Urgency tier: digest (default) | speak_now (S5)",
663
+ enum: ["digest", "speak_now"],
664
+ },
665
+ },
666
+ required: ["to", "body", "type"],
667
+ },
668
+ handler: sendHandler,
669
+ });
670
+ // ── session_end (act tool) ─────────────────────────────────────────────────
671
+ async function sessionEndHandler(args) {
672
+ const ctx = await makeCtx(resolveConfig({}));
673
+ // Validate optional type parameter early
674
+ const type = args?.type;
675
+ if (type && !["handoff", "note"].includes(type)) {
676
+ throw new CodebusError(`type must be 'handoff' or 'note', got: ${type}`);
677
+ }
678
+ const result = await endSessionCore(ctx, {
679
+ summary: args?.summary,
680
+ to: args?.to,
681
+ type: args?.type,
682
+ task: args?.task,
683
+ pr: args?.pr,
684
+ question: args?.question,
685
+ openQuestion: args?.open_question,
686
+ caveat: args?.caveat,
687
+ next: args?.next,
688
+ });
689
+ return { display_id: result.display_id, type: result.type, linked: result.linked };
690
+ }
691
+ TOOL_REGISTRY.push({
692
+ name: "session_end",
693
+ description: "End a session with an optional handoff/note. Posts a structured message with git context, " +
694
+ "linked entities (tasks, PRs, questions), and next steps. " +
695
+ "Returns the message display_id, type, and linked entities.",
696
+ inputSchema: {
697
+ type: "object",
698
+ properties: {
699
+ summary: {
700
+ type: "string",
701
+ description: "Session summary (optional)",
702
+ },
703
+ to: {
704
+ type: "string",
705
+ description: "Recipients (comma-separated agent slugs, optional)",
706
+ },
707
+ type: {
708
+ type: "string",
709
+ description: "Message type: handoff|note (optional, default 'handoff')",
710
+ enum: ["handoff", "note"],
711
+ },
712
+ task: {
713
+ type: "string",
714
+ description: "Linked task IDs (CB-TASK-…, comma-separated, optional)",
715
+ },
716
+ pr: {
717
+ type: "string",
718
+ description: "Linked PR IDs (CB-PR-…, comma-separated, optional)",
719
+ },
720
+ question: {
721
+ type: "string",
722
+ description: "Linked question IDs (CB-Q-…, comma-separated, optional)",
723
+ },
724
+ open_question: {
725
+ type: "string",
726
+ description: "Open questions text (optional)",
727
+ },
728
+ caveat: {
729
+ type: "string",
730
+ description: "Caveats and warnings (optional)",
731
+ },
732
+ next: {
733
+ type: "string",
734
+ description: "What to do next (optional)",
735
+ },
736
+ },
737
+ required: [],
738
+ },
739
+ handler: sessionEndHandler,
740
+ });
741
+ // ── compile_pack (read tool) ───────────────────────────────────────────────
742
+ async function compilePackHandler(args) {
743
+ const ctx = await makeCtx(resolveConfig({}));
744
+ const budget = args?.budget ?? 800;
745
+ const result = await compilePackCore(ctx, {
746
+ query: args?.query,
747
+ taskId: args?.task,
748
+ budget,
749
+ });
750
+ return result;
751
+ }
752
+ TOOL_REGISTRY.push({
753
+ name: "compile_pack",
754
+ description: "Compile a cited, budget-trimmed context pack of accepted decisions + open questions + locked constraints " +
755
+ "relevant to a query or task. Computed live from the CodeBus database. No LLM required. " +
756
+ "Every item is cited with its display_id (CB-DEC-…, CB-Q-…, CB-TASK-…). " +
757
+ "Superseded decisions surface only when adjacent, prefixed 're-litigation'.",
758
+ inputSchema: {
759
+ type: "object",
760
+ properties: {
761
+ query: {
762
+ type: "string",
763
+ description: "Natural language query to focus the pack (optional)",
764
+ },
765
+ task: {
766
+ type: "string",
767
+ description: "CB-TASK-… id; uses graph adjacency to focus items (optional)",
768
+ },
769
+ budget: {
770
+ type: "number",
771
+ description: "Max output tokens (optional, default 800)",
772
+ },
773
+ },
774
+ required: [],
775
+ },
776
+ handler: compilePackHandler,
777
+ });
778
+ // ── plan_status / delivery_status (read tools, Repo Intelligence B5+D4) ────
779
+ // plan_status is a BRAND NEW MCP tool with no prior callers — unlike the
780
+ // CLI's `plan status` verb (restored to its pre-0069 alias behavior per
781
+ // the F3 compatibility ruling), this tool calls the NEW bus_plan_status
782
+ // rollup (planRollupCore) directly; there is no compatibility surface to
783
+ // protect on a tool that never existed before this migration.
784
+ async function planStatusHandler(args) {
785
+ const ctx = await makeCtx(resolveConfig({}));
786
+ return await planRollupCore(ctx, args?.plan || undefined);
787
+ }
788
+ TOOL_REGISTRY.push({
789
+ name: "plan_status",
790
+ description: "Per-plan task/ticket/PR pipeline + Jira coverage + delivery/environment + derived-health rollup " +
791
+ "(bus_plan_status). Omit 'plan' to list every plan visible to the caller in this workspace. Read-only.",
792
+ inputSchema: {
793
+ type: "object",
794
+ properties: {
795
+ plan: {
796
+ type: "string",
797
+ description: "Plan uuid to scope to a single plan (optional; omit to list every visible plan)",
798
+ },
799
+ },
800
+ required: [],
801
+ },
802
+ handler: planStatusHandler,
803
+ });
804
+ async function deliveryStatusHandler(args) {
805
+ const ctx = await makeCtx(resolveConfig({}));
806
+ return await planDeliveryCore(ctx, {
807
+ cursor: args?.cursor,
808
+ limit: args?.limit,
809
+ });
810
+ }
811
+ TOOL_REGISTRY.push({
812
+ name: "delivery_status",
813
+ description: "Keyset-paginated per-plan delivery rollup (bus_delivery_status): three_greens, environment " +
814
+ "containment, and the graph_status quality leg forwarded verbatim. Hard-capped at 100 rows per page; " +
815
+ "pass the previous call's next_cursor to continue. Read-only.",
816
+ inputSchema: {
817
+ type: "object",
818
+ properties: {
819
+ cursor: {
820
+ type: "string",
821
+ description: "Pagination cursor from a previous call's next_cursor (optional)",
822
+ },
823
+ limit: {
824
+ type: "integer",
825
+ description: "Page size (optional, default 50, hard-capped at 100)",
826
+ },
827
+ },
828
+ required: [],
829
+ },
830
+ handler: deliveryStatusHandler,
831
+ });
832
+ // ── resolve_refs (read tool, S4) ───────────────────────────────────────────
833
+ async function resolveRefsHandler(args) {
834
+ const id = args?.id || "";
835
+ if (!id)
836
+ throw new CodebusError("'id' parameter is required");
837
+ const ctx = await makeCtx(resolveConfig({}));
838
+ const refs = await resolveRefsCore(ctx, id);
839
+ return { refs };
840
+ }
841
+ TOOL_REGISTRY.push({
842
+ name: "resolve_refs",
843
+ description: "List the typed SHA-pinned knowledge refs attached to a task or message (CB-TASK-… or CB-MSG-…). " +
844
+ "Each ref includes target_display_id, pinned_sha, head_sha, and state (fresh|drifted|unknown|unresolvable). " +
845
+ "A drifted ref means the pinned sha differs from HEAD. An unresolvable ref means the target " +
846
+ "is in another workspace or was deleted. Read-only.",
847
+ inputSchema: {
848
+ type: "object",
849
+ properties: {
850
+ id: {
851
+ type: "string",
852
+ description: "Task or message display_id (CB-TASK-… or CB-MSG-…) or uuid",
853
+ },
854
+ },
855
+ required: ["id"],
856
+ },
857
+ handler: resolveRefsHandler,
858
+ });
859
+ //# sourceMappingURL=mcp-registry.js.map