@dain-os/mcp-server 0.9.1 → 0.10.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 (53) hide show
  1. package/dist/index.js +0 -0
  2. package/dist/tools/cadence.d.ts +4 -0
  3. package/dist/tools/cadence.d.ts.map +1 -0
  4. package/dist/tools/cadence.js +245 -0
  5. package/dist/tools/cadence.js.map +1 -0
  6. package/dist/tools/comments.d.ts +3 -0
  7. package/dist/tools/comments.d.ts.map +1 -0
  8. package/dist/tools/comments.js +19 -0
  9. package/dist/tools/comments.js.map +1 -0
  10. package/dist/tools/developer.d.ts +3 -0
  11. package/dist/tools/developer.d.ts.map +1 -0
  12. package/dist/tools/developer.js +349 -0
  13. package/dist/tools/developer.js.map +1 -0
  14. package/dist/tools/iam.d.ts +3 -0
  15. package/dist/tools/iam.d.ts.map +1 -0
  16. package/dist/tools/iam.js +38 -0
  17. package/dist/tools/iam.js.map +1 -0
  18. package/dist/tools/log-time-entry.d.ts +3 -0
  19. package/dist/tools/log-time-entry.d.ts.map +1 -0
  20. package/dist/tools/log-time-entry.js +54 -0
  21. package/dist/tools/log-time-entry.js.map +1 -0
  22. package/dist/tools/pr-review.d.ts +3 -0
  23. package/dist/tools/pr-review.d.ts.map +1 -0
  24. package/dist/tools/pr-review.js +83 -0
  25. package/dist/tools/pr-review.js.map +1 -0
  26. package/dist/tools/products.d.ts +3 -0
  27. package/dist/tools/products.d.ts.map +1 -0
  28. package/dist/tools/products.js +37 -0
  29. package/dist/tools/products.js.map +1 -0
  30. package/dist/tools/projects.d.ts +3 -0
  31. package/dist/tools/projects.d.ts.map +1 -0
  32. package/dist/tools/projects.js +151 -0
  33. package/dist/tools/projects.js.map +1 -0
  34. package/dist/tools/proposal-content.d.ts +3 -0
  35. package/dist/tools/proposal-content.d.ts.map +1 -0
  36. package/dist/tools/proposal-content.js +136 -0
  37. package/dist/tools/proposal-content.js.map +1 -0
  38. package/dist/tools/proposal-options.d.ts +3 -0
  39. package/dist/tools/proposal-options.d.ts.map +1 -0
  40. package/dist/tools/proposal-options.js +132 -0
  41. package/dist/tools/proposal-options.js.map +1 -0
  42. package/dist/tools/proposals.d.ts +22 -0
  43. package/dist/tools/proposals.d.ts.map +1 -0
  44. package/dist/tools/proposals.js +179 -0
  45. package/dist/tools/proposals.js.map +1 -0
  46. package/dist/tools/registry.d.ts.map +1 -1
  47. package/dist/tools/registry.js +4 -2
  48. package/dist/tools/registry.js.map +1 -1
  49. package/dist/tools/tasks.d.ts +3 -0
  50. package/dist/tools/tasks.d.ts.map +1 -0
  51. package/dist/tools/tasks.js +255 -0
  52. package/dist/tools/tasks.js.map +1 -0
  53. package/package.json +1 -1
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from './types.js';
2
+ /** All four Cadence MCP tools. */
3
+ export declare const cadenceTools: ToolDefinition[];
4
+ //# sourceMappingURL=cadence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cadence.d.ts","sourceRoot":"","sources":["../../src/tools/cadence.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoRjD,kCAAkC;AAClC,eAAO,MAAM,YAAY,EAAE,cAAc,EAKxC,CAAC"}
@@ -0,0 +1,245 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Cadence MCP tools: AI agent feed for the Sprint + Cadence feature.
4
+ *
5
+ * Four tools for agents (Claude Code, Otto) to interact with the sprint plan:
6
+ * claim_next_task — fetch the highest-ranked open task from the sprint
7
+ * report_task_progress — append a one-liner progress note with agent identity
8
+ * mark_blocked — flag a task as blocked with a reason
9
+ * request_unblock — signal that a task needs human attention to unblock
10
+ *
11
+ * Audit trail: all writes carry metadata.via='mcp' and metadata.client so the
12
+ * cadence_actions record shows "Claude Code" rather than "system". The API
13
+ * side uses this to attribute agent activity in the sprint timeline.
14
+ *
15
+ * @builder ship-api-builder (Round 4, R4-T1)
16
+ */
17
+ // ---------------------------------------------------------------------------
18
+ // claim_next_task
19
+ // ---------------------------------------------------------------------------
20
+ const claimNextTask = {
21
+ name: 'claim_next_task',
22
+ description: 'Fetch the highest-ranked open task from the Cadence sprint feed. ' +
23
+ 'Returns the task object with its Cadence ranking rationale and score. ' +
24
+ 'Call this at the start of an autonomous work session to find what to work on next. ' +
25
+ 'Filters by sprintId, projectId, or assigneeId when provided.',
26
+ inputSchema: z.object({
27
+ sprintId: z
28
+ .string()
29
+ .uuid()
30
+ .optional()
31
+ .describe('Restrict to tasks in a specific sprint.'),
32
+ projectId: z
33
+ .string()
34
+ .uuid()
35
+ .optional()
36
+ .describe('Restrict to tasks in a specific project.'),
37
+ assigneeId: z
38
+ .string()
39
+ .uuid()
40
+ .optional()
41
+ .describe('Restrict to tasks assigned to a specific IAM user.'),
42
+ client: z
43
+ .string()
44
+ .max(100)
45
+ .optional()
46
+ .describe('Caller identity for the audit trail (e.g. "claude-code", "otto"). ' +
47
+ 'Logged as metadata.client on the resulting cadence_actions row.'),
48
+ }),
49
+ handler: async (client, input) => {
50
+ const params = new URLSearchParams();
51
+ if (input.sprintId)
52
+ params.set('sprintId', input.sprintId);
53
+ if (input.projectId)
54
+ params.set('projectId', input.projectId);
55
+ if (input.assigneeId)
56
+ params.set('assigneeId', input.assigneeId);
57
+ const query = params.toString() ? `?${params.toString()}` : '';
58
+ // GET /api/v1/cadence/feed/next-task returns { task: RankedTask | null }
59
+ // The ranked task includes rationale and ranking score from CadenceRankerService.
60
+ return client.get(`/cadence/feed/next-task${query}`);
61
+ },
62
+ };
63
+ // ---------------------------------------------------------------------------
64
+ // report_task_progress
65
+ // ---------------------------------------------------------------------------
66
+ const reportTaskProgress = {
67
+ name: 'report_task_progress',
68
+ description: 'Append a one-line progress note to a task and record it in the Cadence audit trail. ' +
69
+ 'Use this after completing a meaningful unit of work on a task (e.g. "Implemented ' +
70
+ 'the migration file and ran npx prisma generate"). ' +
71
+ 'The note is written to the task\'s AI progress log section and creates a ' +
72
+ 'cadence_actions row with action_type=progress_note for the sprint timeline. ' +
73
+ 'Provide your client identity so the audit trail reads "Claude Code" not "system".',
74
+ inputSchema: z.object({
75
+ taskId: z
76
+ .string()
77
+ .uuid()
78
+ .describe('The task to update.'),
79
+ note: z
80
+ .string()
81
+ .min(1)
82
+ .max(500)
83
+ .describe('One-line progress note in plain English. ' +
84
+ 'Will be appended inside the AI-progress-log sentinel block in the task description. ' +
85
+ 'Example: "Added Zod schema and controller handler for POST /cadence/draft."'),
86
+ client: z
87
+ .string()
88
+ .max(100)
89
+ .optional()
90
+ .describe('Caller identity for the audit trail (e.g. "claude-code"). ' +
91
+ 'Logged as metadata.client on the cadence_actions row.'),
92
+ }),
93
+ handler: async (client, input) => {
94
+ // Step 1: fetch the current task description so we can append to the AI progress block.
95
+ const task = await client.get(`/tasks/${input.taskId}`);
96
+ const now = new Date().toISOString();
97
+ const clientLabel = input.client ?? 'mcp-agent';
98
+ const newLine = `[${now}] (${clientLabel}) ${input.note}`;
99
+ const BEGIN_MARKER = '<!-- BEGIN AI-PROGRESS-LOG -->';
100
+ const END_MARKER = '<!-- END AI-PROGRESS-LOG -->';
101
+ const existing = task.description ?? '';
102
+ let updatedDescription;
103
+ if (existing.includes(BEGIN_MARKER) && existing.includes(END_MARKER)) {
104
+ // Append before the closing marker.
105
+ updatedDescription = existing.replace(END_MARKER, `${newLine}\n${END_MARKER}`);
106
+ }
107
+ else {
108
+ // No sentinel block yet — append one at the end.
109
+ updatedDescription =
110
+ existing.trimEnd() +
111
+ `\n\n${BEGIN_MARKER}\n${newLine}\n${END_MARKER}`;
112
+ }
113
+ // Step 2: update the task description.
114
+ await client.put(`/tasks/${input.taskId}`, { description: updatedDescription });
115
+ // Step 3: write a cadence_actions row for the sprint timeline.
116
+ // action_type='progress_note', target_type='task', payload carries the note + client identity.
117
+ await client.post('/cadence/actions', {
118
+ actionType: 'progress_note',
119
+ targetType: 'task',
120
+ targetId: input.taskId,
121
+ rationale: input.note,
122
+ payload: {
123
+ via: 'mcp',
124
+ client: clientLabel,
125
+ timestamp: now,
126
+ },
127
+ });
128
+ return { success: true, taskId: input.taskId, note: input.note };
129
+ },
130
+ };
131
+ // ---------------------------------------------------------------------------
132
+ // mark_blocked
133
+ // ---------------------------------------------------------------------------
134
+ const markBlocked = {
135
+ name: 'mark_blocked',
136
+ description: 'Mark a task as blocked and record the reason in the Cadence audit trail. ' +
137
+ 'Use when you hit a genuine blocker while working on a task autonomously ' +
138
+ '(e.g. missing API key, conflicting schema, waiting on another task). ' +
139
+ 'The task status changes to "blocked" and a cadence_actions row is created ' +
140
+ 'so the sprint timeline shows the block and the human can address it. ' +
141
+ 'The reason must be specific enough for a human to act on without asking you.',
142
+ inputSchema: z.object({
143
+ taskId: z
144
+ .string()
145
+ .uuid()
146
+ .describe('The task to mark blocked.'),
147
+ reason: z
148
+ .string()
149
+ .min(1)
150
+ .max(500)
151
+ .describe('Plain English description of the blocker. Be specific: ' +
152
+ '"Waiting on OPENAI_API_KEY in the api .env" is actionable. ' +
153
+ '"It\'s blocked" is not. Minimum 1 character, maximum 500.'),
154
+ client: z
155
+ .string()
156
+ .max(100)
157
+ .optional()
158
+ .describe('Caller identity for the audit trail (e.g. "claude-code").'),
159
+ }),
160
+ handler: async (client, input) => {
161
+ const clientLabel = input.client ?? 'mcp-agent';
162
+ const now = new Date().toISOString();
163
+ // Step 1: update task status to blocked.
164
+ await client.put(`/tasks/${input.taskId}`, { status: 'blocked' });
165
+ // Step 2: create a cadence_actions row for the audit trail.
166
+ await client.post('/cadence/actions', {
167
+ actionType: 'mark_blocked',
168
+ targetType: 'task',
169
+ targetId: input.taskId,
170
+ rationale: input.reason,
171
+ payload: {
172
+ via: 'mcp',
173
+ client: clientLabel,
174
+ timestamp: now,
175
+ reason: input.reason,
176
+ },
177
+ });
178
+ return { success: true, taskId: input.taskId, reason: input.reason };
179
+ },
180
+ };
181
+ // ---------------------------------------------------------------------------
182
+ // request_unblock
183
+ // ---------------------------------------------------------------------------
184
+ const requestUnblock = {
185
+ name: 'request_unblock',
186
+ description: 'Signal that a blocked task needs human attention to unblock. ' +
187
+ 'Use after mark_blocked if you want to escalate urgency or add additional context ' +
188
+ 'beyond the original block reason. ' +
189
+ 'Creates a cadence_actions row with action_type=request_unblock which surfaces ' +
190
+ 'in the next Cadence daily scan and CadenceSidebarPanel stale items list. ' +
191
+ 'Does not change task status (task stays blocked until a human acts).',
192
+ inputSchema: z.object({
193
+ taskId: z
194
+ .string()
195
+ .uuid()
196
+ .describe('The blocked task that needs human attention.'),
197
+ context: z
198
+ .string()
199
+ .min(1)
200
+ .max(500)
201
+ .optional()
202
+ .describe('Additional context for the human — e.g. what you tried, what you need to proceed. ' +
203
+ 'Optional but strongly recommended for actionability.'),
204
+ client: z
205
+ .string()
206
+ .max(100)
207
+ .optional()
208
+ .describe('Caller identity for the audit trail (e.g. "claude-code").'),
209
+ }),
210
+ handler: async (client, input) => {
211
+ const clientLabel = input.client ?? 'mcp-agent';
212
+ const now = new Date().toISOString();
213
+ // Write a cadence_actions row with action_type='request_unblock'.
214
+ // The next daily scan will pick this up and surface it in the stale items list.
215
+ await client.post('/cadence/actions', {
216
+ actionType: 'request_unblock',
217
+ targetType: 'task',
218
+ targetId: input.taskId,
219
+ rationale: input.context ?? 'Agent requested human unblock.',
220
+ payload: {
221
+ via: 'mcp',
222
+ client: clientLabel,
223
+ timestamp: now,
224
+ context: input.context ?? null,
225
+ },
226
+ });
227
+ return {
228
+ success: true,
229
+ taskId: input.taskId,
230
+ message: 'Unblock request recorded. The next Cadence daily scan will surface this task ' +
231
+ 'in the sprint sidebar for human review.',
232
+ };
233
+ },
234
+ };
235
+ // ---------------------------------------------------------------------------
236
+ // Export
237
+ // ---------------------------------------------------------------------------
238
+ /** All four Cadence MCP tools. */
239
+ export const cadenceTools = [
240
+ claimNextTask,
241
+ reportTaskProgress,
242
+ markBlocked,
243
+ requestUnblock,
244
+ ];
245
+ //# sourceMappingURL=cadence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cadence.js","sourceRoot":"","sources":["../../src/tools/cadence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from './types.js';
2
+ export declare const commentTools: ToolDefinition[];
3
+ //# sourceMappingURL=comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/tools/comments.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoBjD,eAAO,MAAM,YAAY,EAAE,cAAc,EAAqB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ const addTaskComment = {
3
+ name: 'add_task_comment',
4
+ description: 'Add a comment to a task. Comments are internal by default (visible to the workspace, not to clients on the portal). Pass isInternal: false to make the comment client-visible.',
5
+ inputSchema: z.object({
6
+ taskId: z.string().uuid(),
7
+ content: z.string().min(1).max(10000),
8
+ isInternal: z
9
+ .boolean()
10
+ .optional()
11
+ .describe('Defaults to true. Set false to make the comment client-visible.'),
12
+ }),
13
+ handler: async (client, input) => {
14
+ const { taskId, ...body } = input;
15
+ return client.post(`/tasks/${taskId}/comments`, body);
16
+ },
17
+ };
18
+ export const commentTools = [addTaskComment];
19
+ //# sourceMappingURL=comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/tools/comments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,cAAc,GAAmB;IACrC,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,gLAAgL;IAClL,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;QACzB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;QACrC,UAAU,EAAE,CAAC;aACV,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,iEAAiE,CAAC;KAC/E,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;QAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;QAClC,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,MAAM,WAAW,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAqB,CAAC,cAAc,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from './types.js';
2
+ export declare const developerTools: ToolDefinition[];
3
+ //# sourceMappingURL=developer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"developer.d.ts","sourceRoot":"","sources":["../../src/tools/developer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAyXjD,eAAO,MAAM,cAAc,EAAE,cAAc,EAW1C,CAAC"}
@@ -0,0 +1,349 @@
1
+ import { z } from 'zod';
2
+ import { logTimeEntryTool } from './log-time-entry.js';
3
+ /**
4
+ * Developer KB tools. Five operations: log_changelog_entry, log_session_context,
5
+ * log_knowledge_base_entry, list_recent_sessions, search_knowledge_base.
6
+ *
7
+ * These back the /wrapup, /recap, and /dev-kb slash commands. The slash command
8
+ * still does the conversation analysis (parsing decisions, lessons, etc.); the
9
+ * tools here are the persistence + retrieval surface. With these in place a
10
+ * developer needs only the DainOS MCP installed — no Supabase MCP token — and
11
+ * the workflows port to any MCP client (Cursor, Continue, etc.).
12
+ *
13
+ * All tools are cross-project: the caller supplies `project` (e.g. "dain-os",
14
+ * "herbert", "mabel") on every call. The backing tables live in the developer
15
+ * schema and span tenants.
16
+ */
17
+ /**
18
+ * Some MCP clients serialise array-typed tool arguments as JSON-encoded strings
19
+ * rather than nested arrays. Caught during /wrapup on 2026-05-14:
20
+ * `log_changelog_entry` rejected a perfectly-shaped batch with "expected array,
21
+ * received string" while `log_knowledge_base_entry` accepted the same shape.
22
+ * The difference was the model's serialisation choice on that specific tool
23
+ * call, not the schema. Defending the entries field at the parse boundary
24
+ * makes both spellings work.
25
+ */
26
+ function jsonStringArray(item, min = 1, max = 50) {
27
+ return z.preprocess((val) => {
28
+ if (typeof val === 'string') {
29
+ try {
30
+ return JSON.parse(val);
31
+ }
32
+ catch {
33
+ return val;
34
+ }
35
+ }
36
+ return val;
37
+ }, z.array(item).min(min).max(max));
38
+ }
39
+ /**
40
+ * Optional string that also accepts `null`. Some clients send `null` for
41
+ * "field intentionally absent" rather than omitting the key; the previous
42
+ * `z.string().optional()` rejected null with "expected string, received null".
43
+ */
44
+ const optionalString = z.preprocess((val) => (val === null ? undefined : val), z.string().optional());
45
+ /** Optional non-string field (e.g. number, boolean, array) that accepts null too. */
46
+ function nullishOptional(schema) {
47
+ return z.preprocess((val) => (val === null ? undefined : val), schema.optional());
48
+ }
49
+ const commitTypeEnum = z.enum([
50
+ 'feat', 'fix', 'chore', 'refactor', 'test', 'docs', 'perf', 'ci', 'build', 'revert', 'style',
51
+ ]);
52
+ const sourceTypeEnum = z.enum([
53
+ 'fix_commit', 'pr_comment', 'pr_review', 'code_review',
54
+ 'ai_conversation', 'incident', 'documentation', 'debugging_session',
55
+ ]);
56
+ const severityEnum = z.enum(['critical', 'high', 'medium', 'low']);
57
+ const kbCategoryEnum = z.enum(['gotcha', 'pattern', 'lesson', 'decision', 'workaround']);
58
+ // ---------------------------------------------------------------------------
59
+ // log_changelog_entry — batch insert of commit records
60
+ // ---------------------------------------------------------------------------
61
+ const changelogEntrySchema = z.object({
62
+ commit_sha: z.string().length(40).describe('Full 40-character SHA.'),
63
+ branch: optionalString.describe('Defaults to "main" if omitted.'),
64
+ commit_type: commitTypeEnum.describe('Conventional-commit type prefix.'),
65
+ scope: optionalString,
66
+ summary: z.string().describe('Commit message subject line.'),
67
+ description: optionalString,
68
+ files_changed: nullishOptional(z.coerce.number().int()),
69
+ insertions: nullishOptional(z.coerce.number().int()),
70
+ deletions: nullishOptional(z.coerce.number().int()),
71
+ milestone: optionalString,
72
+ task_ref: optionalString.describe('Linear / Jira / Notion task id.'),
73
+ impacts_config: nullishOptional(z.boolean()).describe('Defaults false.'),
74
+ breaking_change: nullishOptional(z.boolean()).describe('Defaults false.'),
75
+ tags: nullishOptional(z.array(z.string())),
76
+ author: optionalString,
77
+ committed_at: z.string().describe('ISO 8601 timestamp.'),
78
+ pr_number: nullishOptional(z.coerce.number().int()),
79
+ pr_url: optionalString,
80
+ });
81
+ const logChangelogEntry = {
82
+ name: 'log_changelog_entry',
83
+ description: 'Append commit records to the developer changelog for a project. Use during /wrapup to persist today\'s commits. Accepts a batch (1-50 entries). Idempotent on (project, commit_sha): duplicates are silently skipped. Returns { inserted, skipped } counts.',
84
+ inputSchema: z.object({
85
+ project: z.string().describe('Project slug, e.g. "dain-os", "herbert", "mabel".'),
86
+ entries: jsonStringArray(changelogEntrySchema, 1, 50),
87
+ }),
88
+ handler: async (client, input) => {
89
+ const entries = input.entries.map((entry) => ({
90
+ ...entry,
91
+ project: input.project,
92
+ }));
93
+ return client.post('/developer/changelog', { entries });
94
+ },
95
+ };
96
+ // ---------------------------------------------------------------------------
97
+ // log_session_context — single session insert
98
+ // ---------------------------------------------------------------------------
99
+ const decisionSchema = z.object({
100
+ decision: z.string(),
101
+ reason: z.string(),
102
+ });
103
+ const logSessionContext = {
104
+ name: 'log_session_context',
105
+ description: 'Record a single development session for a project (one row per session). Captures decisions made, files touched, blockers, and handoff notes. Use during /wrapup to leave breadcrumbs for the next session. Read back via list_recent_sessions during /recap.',
106
+ inputSchema: z.object({
107
+ project: z.string().describe('Project slug, e.g. "dain-os".'),
108
+ session_name: z.string().describe('Short descriptive title.'),
109
+ session_date: optionalString.describe('YYYY-MM-DD. Defaults to today server-side.'),
110
+ operator: z.string().describe('e.g. "Dane + Claude Opus 4.7".'),
111
+ machine: optionalString,
112
+ duration_minutes: nullishOptional(z.coerce.number().int()),
113
+ summary: z.string().describe('2-4 sentence overview of WHAT and WHY.'),
114
+ decisions_made: nullishOptional(z.array(decisionSchema)).describe('Non-obvious choices future sessions need to understand.'),
115
+ files_touched: nullishOptional(z.array(z.string())),
116
+ tasks_completed: nullishOptional(z.array(z.string())),
117
+ blockers: nullishOptional(z.array(z.string())),
118
+ handoff_notes: optionalString.describe('What the next session needs to know first.'),
119
+ tags: nullishOptional(z.array(z.string())),
120
+ client_visible: nullishOptional(z.boolean()).describe('When false, the session is hidden from portal product pages and the client portal activity feed. Defaults to true. Set false for sensitive sessions (security incidents, internal escalations).'),
121
+ product_id: nullishOptional(z.string().uuid()).describe('UUID of the product this session relates to.'),
122
+ task_ids: nullishOptional(z.array(z.string().uuid()).max(100)).describe('UUIDs of DainOS tasks worked on during the session (max 100).'),
123
+ operator_iam_user_id: nullishOptional(z.string().uuid()).describe('IAM user UUID of the operator. Resolved server-side from the auth token when omitted.'),
124
+ }),
125
+ handler: async (client, input) => client.post('/developer/sessions', input),
126
+ };
127
+ // ---------------------------------------------------------------------------
128
+ // log_knowledge_base_entry — batch insert of KB findings
129
+ // ---------------------------------------------------------------------------
130
+ const kbEntrySchema = z.object({
131
+ category: kbCategoryEnum,
132
+ module: z.string().describe('System / area, e.g. "auth", "react", "prisma".'),
133
+ title: z.string().describe('Short descriptive title.'),
134
+ description: z.string().describe('What happened / what is this.'),
135
+ impact: optionalString.describe('What goes wrong if you don\'t know this.'),
136
+ prevention: optionalString.describe('How to avoid it next time.'),
137
+ severity: nullishOptional(severityEnum),
138
+ source_type: sourceTypeEnum,
139
+ source_refs: nullishOptional(z.array(z.string())).describe('Links, file paths, or commit SHAs.'),
140
+ tags: nullishOptional(z.array(z.string())),
141
+ platform: optionalString.describe('e.g. "supabase", "anthropic", "vercel".'),
142
+ });
143
+ const logKnowledgeBaseEntry = {
144
+ name: 'log_knowledge_base_entry',
145
+ description: 'Append KB findings (gotchas, patterns, lessons, decisions, workarounds) for a project. Only add entries for things that were SURPRISING, NON-OBVIOUS, or caused real problems. Use during /wrapup. Accepts a batch (1-50 entries). Check for similar existing titles via search_knowledge_base before adding to avoid duplicates.',
146
+ inputSchema: z.object({
147
+ project: z.string().describe('Project slug, e.g. "dain-os". Use "universal" for cross-product lessons.'),
148
+ entries: jsonStringArray(kbEntrySchema, 1, 50),
149
+ }),
150
+ handler: async (client, input) => {
151
+ const entries = input.entries.map((entry) => ({
152
+ ...entry,
153
+ project: input.project,
154
+ }));
155
+ return client.post('/developer/knowledge-base', { entries });
156
+ },
157
+ };
158
+ // ---------------------------------------------------------------------------
159
+ // list_recent_sessions — read recent sessions for /recap
160
+ // ---------------------------------------------------------------------------
161
+ const listRecentSessions = {
162
+ name: 'list_recent_sessions',
163
+ description: 'List the most recent development sessions for a project, newest first. Use during /recap to brief the user on what happened in the last 1-N sessions. Returns full session_context rows including decisions, blockers, and handoff_notes.',
164
+ inputSchema: z.object({
165
+ project: z.string().describe('Project slug.'),
166
+ limit: z.coerce.number().int().min(1).max(50).optional().describe('Default 10, max 50.'),
167
+ offset: z.coerce.number().int().min(0).optional().describe('For pagination.'),
168
+ }),
169
+ handler: async (client, input) => {
170
+ const params = new URLSearchParams();
171
+ params.set('project', input.project);
172
+ if (input.limit !== undefined)
173
+ params.set('limit', String(input.limit));
174
+ if (input.offset !== undefined)
175
+ params.set('offset', String(input.offset));
176
+ return client.get(`/developer/sessions?${params.toString()}`);
177
+ },
178
+ };
179
+ // ---------------------------------------------------------------------------
180
+ // search_knowledge_base — read KB by project/module/severity/keyword
181
+ // ---------------------------------------------------------------------------
182
+ const searchKnowledgeBase = {
183
+ name: 'search_knowledge_base',
184
+ description: 'Search the developer KB for relevant gotchas, patterns, lessons, decisions, and workarounds. Use before writing code in a module (catches existing gotchas), during /dev-kb invocations, and before /wrapup to dedupe. Filters AND together; `q` is a case-insensitive substring match against title and description.',
185
+ inputSchema: z.object({
186
+ project: z.string().describe('Project slug. Filter by "universal" for cross-product entries.'),
187
+ module: z.string().optional().describe('e.g. "auth", "react", "prisma".'),
188
+ category: kbCategoryEnum.optional(),
189
+ severity: severityEnum.optional(),
190
+ q: z.string().optional().describe('Free-text match against title + description.'),
191
+ limit: z.coerce.number().int().min(1).max(50).optional().describe('Default 10.'),
192
+ offset: z.coerce.number().int().min(0).optional(),
193
+ }),
194
+ handler: async (client, input) => {
195
+ const params = new URLSearchParams();
196
+ params.set('project', input.project);
197
+ if (input.module)
198
+ params.set('module', input.module);
199
+ if (input.category)
200
+ params.set('category', input.category);
201
+ if (input.severity)
202
+ params.set('severity', input.severity);
203
+ if (input.q)
204
+ params.set('q', input.q);
205
+ if (input.limit !== undefined)
206
+ params.set('limit', String(input.limit));
207
+ if (input.offset !== undefined)
208
+ params.set('offset', String(input.offset));
209
+ return client.get(`/developer/knowledge-base/search?${params.toString()}`);
210
+ },
211
+ };
212
+ // ---------------------------------------------------------------------------
213
+ // update_* — partial edits to existing developer KB rows
214
+ // ---------------------------------------------------------------------------
215
+ //
216
+ // Wraps PATCH /developer/{changelog|sessions|knowledge-base}/:id. Used when a
217
+ // /wrapup-style flow needs to amend a previously-logged entry (e.g. the user
218
+ // corrects a session summary, fills in handoff_notes after the fact, or
219
+ // reclassifies a KB entry's severity). `project` is immutable on every row —
220
+ // to "move" between projects, delete and re-insert.
221
+ const updateChangelogEntry = {
222
+ name: 'update_changelog_entry',
223
+ description: 'Partially update an existing changelog entry by id. Every field is optional; only the supplied fields are written. `project` and `commit_sha` are immutable. Returns the refreshed row. 404 if id is unknown.',
224
+ inputSchema: z.object({
225
+ id: z.string().uuid().describe('Changelog entry id (UUID).'),
226
+ branch: optionalString,
227
+ commit_type: commitTypeEnum.optional(),
228
+ scope: optionalString,
229
+ summary: optionalString,
230
+ description: optionalString,
231
+ files_changed: nullishOptional(z.coerce.number().int()),
232
+ insertions: nullishOptional(z.coerce.number().int()),
233
+ deletions: nullishOptional(z.coerce.number().int()),
234
+ milestone: optionalString,
235
+ task_ref: optionalString,
236
+ impacts_config: nullishOptional(z.boolean()),
237
+ breaking_change: nullishOptional(z.boolean()),
238
+ tags: nullishOptional(z.array(z.string())),
239
+ author: optionalString,
240
+ committed_at: optionalString.describe('ISO 8601 timestamp.'),
241
+ pr_number: nullishOptional(z.coerce.number().int()),
242
+ pr_url: optionalString,
243
+ }),
244
+ handler: async (client, input) => {
245
+ const { id, ...rest } = input;
246
+ // Drop undefined keys so the API only receives the fields the caller set.
247
+ const body = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
248
+ return client.patch(`/developer/changelog/${id}`, body);
249
+ },
250
+ };
251
+ const updateSessionContext = {
252
+ name: 'update_session_context',
253
+ description: 'Partially update an existing session_context row by id. Every field is optional. `project` is immutable. Returns the refreshed row. 404 if id is unknown.',
254
+ inputSchema: z.object({
255
+ id: z.string().uuid().describe('Session context id (UUID).'),
256
+ session_name: optionalString,
257
+ session_date: optionalString.describe('YYYY-MM-DD.'),
258
+ operator: optionalString,
259
+ machine: optionalString,
260
+ duration_minutes: nullishOptional(z.coerce.number().int()),
261
+ summary: optionalString,
262
+ decisions_made: nullishOptional(z.array(decisionSchema)),
263
+ files_touched: nullishOptional(z.array(z.string())),
264
+ tasks_completed: nullishOptional(z.array(z.string())),
265
+ blockers: nullishOptional(z.array(z.string())),
266
+ handoff_notes: optionalString,
267
+ tags: nullishOptional(z.array(z.string())),
268
+ product_id: nullishOptional(z.string().uuid()).describe('UUID of the product this session relates to.'),
269
+ task_ids: nullishOptional(z.array(z.string().uuid()).max(100)).describe('UUIDs of DainOS tasks worked on during the session (max 100).'),
270
+ operator_iam_user_id: nullishOptional(z.string().uuid()).describe('IAM user UUID of the operator.'),
271
+ }),
272
+ handler: async (client, input) => {
273
+ const { id, ...rest } = input;
274
+ const body = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
275
+ return client.patch(`/developer/sessions/${id}`, body);
276
+ },
277
+ };
278
+ const updateKnowledgeBaseEntry = {
279
+ name: 'update_knowledge_base_entry',
280
+ description: 'Partially update an existing KB entry by id. Every field is optional. `project` is immutable. Returns the refreshed row. 404 if id is unknown.',
281
+ inputSchema: z.object({
282
+ id: z.string().uuid().describe('KB entry id (UUID).'),
283
+ category: kbCategoryEnum.optional(),
284
+ module: optionalString,
285
+ title: optionalString,
286
+ description: optionalString,
287
+ impact: optionalString,
288
+ prevention: optionalString,
289
+ severity: nullishOptional(severityEnum),
290
+ source_type: sourceTypeEnum.optional(),
291
+ source_refs: nullishOptional(z.array(z.string())),
292
+ tags: nullishOptional(z.array(z.string())),
293
+ platform: optionalString,
294
+ }),
295
+ handler: async (client, input) => {
296
+ const { id, ...rest } = input;
297
+ const body = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== undefined));
298
+ return client.patch(`/developer/knowledge-base/${id}`, body);
299
+ },
300
+ };
301
+ // ---------------------------------------------------------------------------
302
+ // list_changelog — read changelog entries by project slug + date
303
+ // ---------------------------------------------------------------------------
304
+ const listChangelog = {
305
+ name: 'list_changelog',
306
+ description: 'List developer changelog entries for a project, newest first. Optionally filter by `since` (ISO 8601 date). Default limit 50, max 200. Use during /recap to review recent commits for a project without raw SQL.',
307
+ inputSchema: z.object({
308
+ project: z
309
+ .string()
310
+ .min(1)
311
+ .max(100)
312
+ .regex(/^[a-z0-9][a-z0-9-]*$/, 'project must be a lowercase slug')
313
+ .describe('Project slug, e.g. "dain-os", "herbert", "mabel".'),
314
+ since: z
315
+ .string()
316
+ .datetime({ offset: true, message: 'since must be ISO 8601 with offset' })
317
+ .optional()
318
+ .describe('ISO 8601 timestamp with offset (e.g. 2026-05-18T00:00:00Z) — only return entries with committed_at >= this value.'),
319
+ limit: z.coerce
320
+ .number()
321
+ .int()
322
+ .min(1)
323
+ .max(200)
324
+ .optional()
325
+ .describe('Default 50, max 200.'),
326
+ }),
327
+ handler: async (client, input) => {
328
+ const params = new URLSearchParams();
329
+ params.set('project', input.project);
330
+ if (input.since)
331
+ params.set('since', input.since);
332
+ if (input.limit !== undefined)
333
+ params.set('limit', String(input.limit));
334
+ return client.get(`/developer/changelog?${params.toString()}`);
335
+ },
336
+ };
337
+ export const developerTools = [
338
+ logChangelogEntry,
339
+ logSessionContext,
340
+ logKnowledgeBaseEntry,
341
+ listRecentSessions,
342
+ searchKnowledgeBase,
343
+ updateChangelogEntry,
344
+ updateSessionContext,
345
+ updateKnowledgeBaseEntry,
346
+ listChangelog,
347
+ logTimeEntryTool,
348
+ ];
349
+ //# sourceMappingURL=developer.js.map