@oka-core/reason 0.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.
@@ -0,0 +1,304 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Enhanced read tools that prefer consolidated Learnings over raw events.
4
+ *
5
+ * These are drop-in replacements for the basic read tools in read.ts.
6
+ * They query the enhanced learnings API first, falling back to raw events
7
+ * if the enhanced endpoint isn't available.
8
+ */
9
+ const GetContextInputEnhanced = z.object({
10
+ repo: z.string().describe("Repository identifier"),
11
+ area: z
12
+ .string()
13
+ .describe("Area to get context for (e.g., 'auth', 'billing')"),
14
+ include_raw_events: z
15
+ .boolean()
16
+ .optional()
17
+ .default(false)
18
+ .describe("Also include raw recent events alongside consolidated learnings"),
19
+ min_confidence: z
20
+ .number()
21
+ .optional()
22
+ .default(0.5)
23
+ .describe("Minimum confidence for included learnings"),
24
+ });
25
+ const GetDecisionsInputEnhanced = z.object({
26
+ repo: z.string(),
27
+ module: z.string().optional().describe("Module to filter by"),
28
+ timeframe: z.string().optional().describe("Timeframe: '7d', '30d', '24h'"),
29
+ include_rationale: z
30
+ .boolean()
31
+ .optional()
32
+ .default(true)
33
+ .describe("Include full rationale and alternatives considered"),
34
+ });
35
+ const GetPatternsInputEnhanced = z.object({
36
+ repo: z.string(),
37
+ category: z
38
+ .string()
39
+ .optional()
40
+ .describe("Filter by: architecture, bug_pattern, convention, decision, performance, security, workflow"),
41
+ min_evidence: z
42
+ .number()
43
+ .optional()
44
+ .default(2)
45
+ .describe("Minimum number of corroborating sources"),
46
+ });
47
+ const SuggestPrioritiesEnhanced = z.object({
48
+ repo: z.string(),
49
+ backlog: z
50
+ .array(z.string())
51
+ .optional()
52
+ .describe("Current backlog items for context-aware ranking"),
53
+ include_auto_generated: z
54
+ .boolean()
55
+ .optional()
56
+ .default(true)
57
+ .describe("Include auto-generated PBIs from consolidation"),
58
+ });
59
+ export function registerEnhancedReadTools(server, client) {
60
+ server.tool("get_context_enhanced", "Get consolidated institutional knowledge about an area. " +
61
+ "Unlike basic get_context, this returns Learnings derived from cross-source " +
62
+ "consolidation (agent reasoning + code + docs + meetings + tickets + chat). " +
63
+ "Each learning has a confidence score and evidence count.", GetContextInputEnhanced.shape, async (params) => {
64
+ try {
65
+ // Try enhanced endpoint first
66
+ const enhancedResult = await fetchEnhancedLearnings(client, {
67
+ repo: params.repo,
68
+ area: params.area,
69
+ minConfidence: params.min_confidence,
70
+ });
71
+ if (enhancedResult && enhancedResult.length > 0) {
72
+ let text = formatEnhancedContext(params.area, enhancedResult);
73
+ // Optionally include raw events for full picture
74
+ if (params.include_raw_events) {
75
+ const rawResult = await client.query({
76
+ area: params.area,
77
+ });
78
+ if (rawResult?.results?.length > 0) {
79
+ text += "\n\n---\n\n## Recent Raw Events\n\n";
80
+ text += rawResult.results
81
+ .slice(0, 10)
82
+ .map((r) => {
83
+ const rec = r;
84
+ const payload = rec["payload"];
85
+ const desc = payload?.["description"] ||
86
+ payload?.["area"] ||
87
+ JSON.stringify(payload).slice(0, 200);
88
+ return `- [${rec["event_type"]}] ${desc}`;
89
+ })
90
+ .join("\n");
91
+ }
92
+ }
93
+ return { content: [{ type: "text", text }] };
94
+ }
95
+ // Fall back to raw events
96
+ const rawResult = await client.query({
97
+ area: params.area,
98
+ });
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: rawResult?.results?.length > 0
104
+ ? formatRawFallback(params.area, rawResult.results)
105
+ : `No knowledge found for area "${params.area}". This area hasn't been explored yet.`,
106
+ },
107
+ ],
108
+ };
109
+ }
110
+ catch (error) {
111
+ return {
112
+ content: [{ type: "text", text: `Error: ${error}` }],
113
+ isError: true,
114
+ };
115
+ }
116
+ });
117
+ server.tool("get_decisions_enhanced", "Get the decision log with full rationale and alternatives. " +
118
+ "Shows what was decided, why, and what was rejected.", GetDecisionsInputEnhanced.shape, async (params) => {
119
+ try {
120
+ const result = await client.query({
121
+ event_type: "decision",
122
+ module: params.module,
123
+ timeframe: params.timeframe,
124
+ });
125
+ if (!result?.results?.length) {
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: "No decisions recorded for this scope.",
131
+ },
132
+ ],
133
+ };
134
+ }
135
+ const formatted = result.results
136
+ .map((r) => {
137
+ const record = r;
138
+ const p = record["payload"] || {};
139
+ let entry = `### ${p["description"] || "Decision"}\n`;
140
+ if (params.include_rationale && p["rationale"]) {
141
+ entry += `**Rationale:** ${p["rationale"]}\n`;
142
+ }
143
+ const alts = p["alternatives_considered"];
144
+ if (alts && alts.length > 0) {
145
+ entry += `**Rejected alternatives:** ${alts.join(", ")}\n`;
146
+ }
147
+ if (p["confidence"]) {
148
+ entry += `**Confidence:** ${((p["confidence"] ?? 0) * 100).toFixed(0)}%\n`;
149
+ }
150
+ entry += `*Agent: ${record["agent_id"]} | Session: ${record["session_id"]}*\n`;
151
+ return entry;
152
+ })
153
+ .join("\n---\n\n");
154
+ return {
155
+ content: [
156
+ {
157
+ type: "text",
158
+ text: `## Decision Log\n\n${formatted}`,
159
+ },
160
+ ],
161
+ };
162
+ }
163
+ catch (error) {
164
+ return {
165
+ content: [{ type: "text", text: `Error: ${error}` }],
166
+ isError: true,
167
+ };
168
+ }
169
+ });
170
+ server.tool("get_patterns_enhanced", "Get recurring patterns with evidence counts and confidence. " +
171
+ "Only shows patterns corroborated by multiple sources.", GetPatternsInputEnhanced.shape, async (params) => {
172
+ try {
173
+ const learnings = await fetchEnhancedLearnings(client, {
174
+ repo: params.repo,
175
+ category: params.category,
176
+ minConfidence: 0.5,
177
+ });
178
+ const patterns = (learnings || []).filter((l) => (l.evidence_count ?? 1) >= (params.min_evidence ?? 2));
179
+ if (patterns.length === 0) {
180
+ return {
181
+ content: [
182
+ {
183
+ type: "text",
184
+ text: "No recurring patterns identified yet. Patterns emerge after multiple " +
185
+ "sessions observe the same conventions, bugs, or architectural decisions.",
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ const formatted = patterns
191
+ .map((p) => `### ${p.summary}\n` +
192
+ `**Confidence:** ${((p.confidence ?? 0) * 100).toFixed(0)}% | ` +
193
+ `**Evidence:** ${p.evidence_count} source(s) | ` +
194
+ `**Category:** ${p.category || "general"}\n` +
195
+ (p.suggested_action
196
+ ? `**Action:** ${p.suggested_action}\n`
197
+ : "") +
198
+ (p.file_patterns?.length
199
+ ? `**Files:** ${p.file_patterns.join(", ")}\n`
200
+ : ""))
201
+ .join("\n---\n\n");
202
+ return {
203
+ content: [
204
+ {
205
+ type: "text",
206
+ text: `## Recurring Patterns\n\n${formatted}`,
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ catch (error) {
212
+ return {
213
+ content: [{ type: "text", text: `Error: ${error}` }],
214
+ isError: true,
215
+ };
216
+ }
217
+ });
218
+ server.tool("suggest_priorities_enhanced", "Suggest evidence-ranked priorities combining human backlog with " +
219
+ "auto-generated PBIs from consolidation.", SuggestPrioritiesEnhanced.shape, async (params) => {
220
+ try {
221
+ // Get high-confidence learnings that suggest action
222
+ const learnings = await fetchEnhancedLearnings(client, {
223
+ repo: params.repo,
224
+ minConfidence: 0.7,
225
+ });
226
+ const actionable = (learnings || [])
227
+ .filter((l) => l.suggested_action)
228
+ .sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0));
229
+ let text = "## Suggested Priorities\n\n";
230
+ text += "> Ranked by evidence weight across all knowledge sources.\n\n";
231
+ if (actionable.length === 0) {
232
+ text +=
233
+ "No high-confidence actionable learnings yet. " +
234
+ "Continue capturing reasoning events to build the evidence base.";
235
+ }
236
+ else {
237
+ actionable.forEach((l, i) => {
238
+ text += `${i + 1}. **${l.summary}**\n`;
239
+ text += ` Confidence: ${((l.confidence ?? 0) * 100).toFixed(0)}% | `;
240
+ text += `Evidence: ${l.evidence_count ?? 1} source(s)\n`;
241
+ text += ` Action: ${l.suggested_action}\n\n`;
242
+ });
243
+ }
244
+ return { content: [{ type: "text", text }] };
245
+ }
246
+ catch (error) {
247
+ return {
248
+ content: [{ type: "text", text: `Error: ${error}` }],
249
+ isError: true,
250
+ };
251
+ }
252
+ });
253
+ }
254
+ // --- Helpers ---
255
+ async function fetchEnhancedLearnings(client, options) {
256
+ try {
257
+ // Try the enhanced learnings endpoint via the client's query method
258
+ // with a special type parameter that the API can recognize
259
+ const result = await client.query({
260
+ type: "learnings_enhanced",
261
+ area: options.area,
262
+ category: options.category,
263
+ min_confidence: options.minConfidence
264
+ ? String(options.minConfidence)
265
+ : undefined,
266
+ sort_by: "confidence",
267
+ sort_order: "desc",
268
+ limit: "30",
269
+ });
270
+ return result?.results ?? [];
271
+ }
272
+ catch {
273
+ return [];
274
+ }
275
+ }
276
+ function formatEnhancedContext(area, learnings) {
277
+ let text = `## Institutional Knowledge: ${area}\n\n`;
278
+ text += `> ${learnings.length} consolidated learning(s) from cross-source analysis.\n\n`;
279
+ for (const l of learnings) {
280
+ text += `### ${l.summary}\n`;
281
+ text += `**Confidence:** ${((l.confidence ?? 0) * 100).toFixed(0)}%`;
282
+ if (l.evidence_count > 1)
283
+ text += ` (${l.evidence_count} sources)`;
284
+ text += ` | **Category:** ${l.category || "general"}\n`;
285
+ if (l.detailed_description)
286
+ text += `\n${l.detailed_description}\n`;
287
+ if (l.suggested_action)
288
+ text += `\n**Suggested action:** ${l.suggested_action}\n`;
289
+ if (l.file_patterns?.length)
290
+ text += `**Related files:** ${l.file_patterns.join(", ")}\n`;
291
+ text += "\n";
292
+ }
293
+ return text;
294
+ }
295
+ function formatRawFallback(area, events) {
296
+ let text = `## Raw Events: ${area}\n\n`;
297
+ text += `> No consolidated learnings yet. Showing ${events.length} raw event(s).\n\n`;
298
+ for (const e of events) {
299
+ const payload = e["payload"];
300
+ const desc = payload?.["description"] || JSON.stringify(payload).slice(0, 200);
301
+ text += `- [${e["event_type"]}] ${desc}\n`;
302
+ }
303
+ return text;
304
+ }
@@ -0,0 +1,8 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { OkaClient } from "../client.js";
3
+ /**
4
+ * Register the 4 write tools on the MCP server.
5
+ * Each tool validates input, creates a reasoning event, and fires it to the API.
6
+ */
7
+ export declare function registerWriteTools(server: McpServer, client: OkaClient): void;
8
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAgJ7E"}
@@ -0,0 +1,120 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Register the 4 write tools on the MCP server.
4
+ * Each tool validates input, creates a reasoning event, and fires it to the API.
5
+ */
6
+ export function registerWriteTools(server, client) {
7
+ server.tool("decision", "Record an agent decision with rationale and alternatives considered", {
8
+ description: z.string().describe("What was decided"),
9
+ rationale: z.string().describe("Why this choice was made"),
10
+ alternatives_considered: z
11
+ .array(z.string())
12
+ .default([])
13
+ .describe("Other options that were evaluated"),
14
+ confidence: z
15
+ .number()
16
+ .min(0)
17
+ .max(1)
18
+ .describe("Confidence in the decision (0-1)"),
19
+ }, async (params) => {
20
+ await client.ingest({ event_type: "decision", payload: params });
21
+ return {
22
+ content: [{ type: "text", text: "Decision recorded." }],
23
+ };
24
+ });
25
+ server.tool("explore", "Record an area the agent explored and what was found", {
26
+ area: z.string().describe("The area or module explored"),
27
+ findings: z.string().describe("What was discovered"),
28
+ relevance_score: z
29
+ .number()
30
+ .min(0)
31
+ .max(1)
32
+ .describe("How relevant to the current task (0-1)"),
33
+ }, async (params) => {
34
+ await client.ingest({ event_type: "exploration", payload: params });
35
+ return {
36
+ content: [{ type: "text", text: "Exploration recorded." }],
37
+ };
38
+ });
39
+ server.tool("deviation", "Record when the agent deviated from the original plan", {
40
+ planned_action: z.string().describe("What was originally planned"),
41
+ actual_action: z.string().describe("What was actually done"),
42
+ reason: z.string().describe("Why the deviation occurred"),
43
+ }, async (params) => {
44
+ await client.ingest({ event_type: "deviation", payload: params });
45
+ return {
46
+ content: [{ type: "text", text: "Deviation recorded." }],
47
+ };
48
+ });
49
+ server.tool("done", "Record task completion with outcome and quality signals", {
50
+ task_id: z.string().describe("Identifier for the completed task"),
51
+ outcome: z
52
+ .enum(["success", "partial", "failed"])
53
+ .describe("Task outcome"),
54
+ artifacts: z
55
+ .array(z.string())
56
+ .default([])
57
+ .describe("Files or PRs produced"),
58
+ quality_signals: z
59
+ .record(z.string(), z.number())
60
+ .default({})
61
+ .describe("Quality metrics (e.g., test_coverage: 0.92)"),
62
+ }, async (params) => {
63
+ await client.ingest({ event_type: "completion", payload: params });
64
+ return {
65
+ content: [{ type: "text", text: "Completion recorded." }],
66
+ };
67
+ });
68
+ // ─── observe (general-purpose) ─────────────────────────────────
69
+ server.tool("observe", "Record a general observation that doesn't fit decision/explore/deviation/done. " +
70
+ "Use this for architectural insights, code quality notes, dependency observations, " +
71
+ "performance findings, or any other knowledge worth preserving.", {
72
+ category: z
73
+ .enum([
74
+ "architecture",
75
+ "bug_pattern",
76
+ "convention",
77
+ "performance",
78
+ "security",
79
+ "workflow",
80
+ "dependency",
81
+ "code_quality",
82
+ "other",
83
+ ])
84
+ .describe("Category of the observation"),
85
+ summary: z.string().describe("Brief summary of the observation"),
86
+ details: z.string().optional().describe("Detailed description"),
87
+ confidence: z
88
+ .number()
89
+ .min(0)
90
+ .max(1)
91
+ .default(0.7)
92
+ .describe("Confidence in this observation (0-1)"),
93
+ file_patterns: z
94
+ .array(z.string())
95
+ .default([])
96
+ .describe("Related file patterns (e.g., 'src/auth/**')"),
97
+ tags: z.array(z.string()).default([]).describe("Tags for categorization"),
98
+ }, async (params) => {
99
+ await client.ingest({
100
+ event_type: "exploration",
101
+ payload: {
102
+ area: params.category,
103
+ findings: params.summary,
104
+ details: params.details,
105
+ relevance_score: params.confidence,
106
+ observation_category: params.category,
107
+ file_patterns: params.file_patterns,
108
+ tags: params.tags,
109
+ },
110
+ });
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: `Observation recorded: [${params.category}] ${params.summary}`,
116
+ },
117
+ ],
118
+ };
119
+ });
120
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@oka-core/reason",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for Oka Reason — institutional knowledge, semantic search, and consolidation",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "restricted"
8
+ },
9
+ "type": "module",
10
+ "bin": {
11
+ "oka-reason": "./dist/index.js"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ },
18
+ "./client": {
19
+ "import": "./dist/client.js",
20
+ "types": "./dist/client.d.ts"
21
+ },
22
+ "./auth": {
23
+ "import": "./dist/auth.js",
24
+ "types": "./dist/auth.d.ts"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "check-types": "tsc --noEmit",
34
+ "prepublishOnly": "npm run build",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "lint": "echo 'lint ok'",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "keywords": [
41
+ "mcp",
42
+ "reasoning",
43
+ "semantic-search",
44
+ "knowledge-management",
45
+ "oka"
46
+ ],
47
+ "license": "UNLICENSED",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/datacircuits/agentic.git",
51
+ "directory": "packages/reason"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ },
56
+ "dependencies": {
57
+ "@modelcontextprotocol/sdk": "^1.0.0",
58
+ "zod": "^3.23.0"
59
+ },
60
+ "devDependencies": {
61
+ "typescript": "^5.5.0",
62
+ "vitest": "^2.0.0",
63
+ "@oka/typescript-config": "workspace:*"
64
+ }
65
+ }