ralph-hero-knowledge-index 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/dist/db.d.ts +67 -0
- package/dist/db.js +151 -0
- package/dist/db.js.map +1 -1
- package/dist/index.js +79 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/db.test.ts +161 -0
- package/src/__tests__/index.test.ts +6 -0
- package/src/db.ts +243 -0
- package/src/index.ts +88 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralph-knowledge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Knowledge graph for ralph-hero: semantic search, relationship traversal, and document indexing across thoughts/ documents. Optional companion to ralph-hero.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Chad Dubiel",
|
package/.mcp.json
CHANGED
package/dist/db.d.ts
CHANGED
|
@@ -14,6 +14,64 @@ export interface RelationshipRow {
|
|
|
14
14
|
targetId: string;
|
|
15
15
|
type: string;
|
|
16
16
|
}
|
|
17
|
+
export interface OutcomeEventInput {
|
|
18
|
+
eventType: string;
|
|
19
|
+
issueNumber: number;
|
|
20
|
+
sessionId?: string;
|
|
21
|
+
durationMs?: number;
|
|
22
|
+
verdict?: string;
|
|
23
|
+
componentArea?: string;
|
|
24
|
+
estimate?: string;
|
|
25
|
+
driftCount?: number;
|
|
26
|
+
model?: string;
|
|
27
|
+
agentType?: string;
|
|
28
|
+
iterationCount?: number;
|
|
29
|
+
payload?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
export interface OutcomeEventRow {
|
|
32
|
+
id: string;
|
|
33
|
+
eventType: string;
|
|
34
|
+
issueNumber: number;
|
|
35
|
+
sessionId: string | null;
|
|
36
|
+
timestamp: string;
|
|
37
|
+
durationMs: number | null;
|
|
38
|
+
verdict: string | null;
|
|
39
|
+
componentArea: string | null;
|
|
40
|
+
estimate: string | null;
|
|
41
|
+
driftCount: number | null;
|
|
42
|
+
model: string | null;
|
|
43
|
+
agentType: string | null;
|
|
44
|
+
iterationCount: number | null;
|
|
45
|
+
payload: string;
|
|
46
|
+
}
|
|
47
|
+
export interface OutcomeQueryParams {
|
|
48
|
+
issueNumber?: number;
|
|
49
|
+
eventType?: string;
|
|
50
|
+
componentArea?: string;
|
|
51
|
+
estimate?: string;
|
|
52
|
+
verdict?: string;
|
|
53
|
+
sessionId?: string;
|
|
54
|
+
since?: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface OutcomeAggregate {
|
|
58
|
+
count: number;
|
|
59
|
+
avgDriftCount: number | null;
|
|
60
|
+
avgIterationCount: number | null;
|
|
61
|
+
verdictDistribution: Record<string, number>;
|
|
62
|
+
eventTypeDistribution: Record<string, number>;
|
|
63
|
+
topComponentAreas: Array<{
|
|
64
|
+
area: string;
|
|
65
|
+
count: number;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
export interface OutcomeSummary {
|
|
69
|
+
totalEvents: number;
|
|
70
|
+
latestVerdict: string | null;
|
|
71
|
+
driftCount: number;
|
|
72
|
+
blockers: number;
|
|
73
|
+
eventsByType: Record<string, number>;
|
|
74
|
+
}
|
|
17
75
|
export declare class KnowledgeDB {
|
|
18
76
|
readonly db: DatabaseType;
|
|
19
77
|
constructor(dbPath: string);
|
|
@@ -25,6 +83,15 @@ export declare class KnowledgeDB {
|
|
|
25
83
|
addRelationship(sourceId: string, targetId: string, type: string): void;
|
|
26
84
|
getRelationshipsFrom(sourceId: string): RelationshipRow[];
|
|
27
85
|
getRelationshipsTo(targetId: string): RelationshipRow[];
|
|
86
|
+
insertOutcomeEvent(input: OutcomeEventInput): {
|
|
87
|
+
id: string;
|
|
88
|
+
eventType: string;
|
|
89
|
+
issueNumber: number;
|
|
90
|
+
timestamp: string;
|
|
91
|
+
};
|
|
92
|
+
queryOutcomeEvents(params?: OutcomeQueryParams): OutcomeEventRow[];
|
|
93
|
+
aggregateOutcomeEvents(params?: OutcomeQueryParams): OutcomeAggregate;
|
|
94
|
+
getOutcomeSummary(issueNumber: number): OutcomeSummary | null;
|
|
28
95
|
clearAll(): void;
|
|
29
96
|
close(): void;
|
|
30
97
|
}
|
package/dist/db.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { mkdirSync } from "node:fs";
|
|
3
4
|
import { dirname } from "node:path";
|
|
4
5
|
export class KnowledgeDB {
|
|
@@ -34,6 +35,29 @@ export class KnowledgeDB {
|
|
|
34
35
|
);
|
|
35
36
|
CREATE INDEX IF NOT EXISTS idx_rel_target ON relationships(target_id, type);
|
|
36
37
|
CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS outcome_events (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
event_type TEXT NOT NULL,
|
|
42
|
+
issue_number INTEGER NOT NULL,
|
|
43
|
+
session_id TEXT,
|
|
44
|
+
timestamp TEXT NOT NULL,
|
|
45
|
+
duration_ms INTEGER,
|
|
46
|
+
verdict TEXT,
|
|
47
|
+
component_area TEXT,
|
|
48
|
+
estimate TEXT,
|
|
49
|
+
drift_count INTEGER,
|
|
50
|
+
model TEXT,
|
|
51
|
+
agent_type TEXT,
|
|
52
|
+
iteration_count INTEGER,
|
|
53
|
+
payload TEXT DEFAULT '{}'
|
|
54
|
+
);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_oe_type ON outcome_events(event_type);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_oe_issue ON outcome_events(issue_number);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_oe_component ON outcome_events(component_area);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_oe_timestamp ON outcome_events(timestamp);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_oe_session ON outcome_events(session_id);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_oe_type_component ON outcome_events(event_type, component_area);
|
|
37
61
|
`);
|
|
38
62
|
}
|
|
39
63
|
upsertDocument(doc) {
|
|
@@ -66,7 +90,134 @@ export class KnowledgeDB {
|
|
|
66
90
|
getRelationshipsTo(targetId) {
|
|
67
91
|
return this.db.prepare("SELECT source_id AS sourceId, target_id AS targetId, type FROM relationships WHERE target_id = ?").all(targetId);
|
|
68
92
|
}
|
|
93
|
+
insertOutcomeEvent(input) {
|
|
94
|
+
const id = randomUUID();
|
|
95
|
+
const timestamp = new Date().toISOString();
|
|
96
|
+
const payload = JSON.stringify(input.payload ?? {});
|
|
97
|
+
this.db.prepare(`
|
|
98
|
+
INSERT INTO outcome_events (id, event_type, issue_number, session_id, timestamp, duration_ms, verdict, component_area, estimate, drift_count, model, agent_type, iteration_count, payload)
|
|
99
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
100
|
+
`).run(id, input.eventType, input.issueNumber, input.sessionId ?? null, timestamp, input.durationMs ?? null, input.verdict ?? null, input.componentArea ?? null, input.estimate ?? null, input.driftCount ?? null, input.model ?? null, input.agentType ?? null, input.iterationCount ?? null, payload);
|
|
101
|
+
return { id, eventType: input.eventType, issueNumber: input.issueNumber, timestamp };
|
|
102
|
+
}
|
|
103
|
+
queryOutcomeEvents(params = {}) {
|
|
104
|
+
const conditions = [];
|
|
105
|
+
const values = [];
|
|
106
|
+
if (params.issueNumber !== undefined) {
|
|
107
|
+
conditions.push("issue_number = ?");
|
|
108
|
+
values.push(params.issueNumber);
|
|
109
|
+
}
|
|
110
|
+
if (params.eventType !== undefined) {
|
|
111
|
+
conditions.push("event_type = ?");
|
|
112
|
+
values.push(params.eventType);
|
|
113
|
+
}
|
|
114
|
+
if (params.componentArea !== undefined) {
|
|
115
|
+
conditions.push("component_area LIKE ?");
|
|
116
|
+
values.push(`${params.componentArea}%`);
|
|
117
|
+
}
|
|
118
|
+
if (params.estimate !== undefined) {
|
|
119
|
+
conditions.push("estimate = ?");
|
|
120
|
+
values.push(params.estimate);
|
|
121
|
+
}
|
|
122
|
+
if (params.verdict !== undefined) {
|
|
123
|
+
conditions.push("verdict = ?");
|
|
124
|
+
values.push(params.verdict);
|
|
125
|
+
}
|
|
126
|
+
if (params.sessionId !== undefined) {
|
|
127
|
+
conditions.push("session_id = ?");
|
|
128
|
+
values.push(params.sessionId);
|
|
129
|
+
}
|
|
130
|
+
if (params.since !== undefined) {
|
|
131
|
+
conditions.push("timestamp >= ?");
|
|
132
|
+
values.push(params.since);
|
|
133
|
+
}
|
|
134
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
135
|
+
const limit = params.limit ?? 50;
|
|
136
|
+
const sql = `
|
|
137
|
+
SELECT id, event_type AS eventType, issue_number AS issueNumber, session_id AS sessionId,
|
|
138
|
+
timestamp, duration_ms AS durationMs, verdict, component_area AS componentArea,
|
|
139
|
+
estimate, drift_count AS driftCount, model, agent_type AS agentType,
|
|
140
|
+
iteration_count AS iterationCount, payload
|
|
141
|
+
FROM outcome_events ${where}
|
|
142
|
+
ORDER BY timestamp DESC
|
|
143
|
+
LIMIT ?
|
|
144
|
+
`;
|
|
145
|
+
return this.db.prepare(sql).all(...values, limit);
|
|
146
|
+
}
|
|
147
|
+
aggregateOutcomeEvents(params = {}) {
|
|
148
|
+
// Override limit to aggregate over all matching events, not just the caller's limit
|
|
149
|
+
const rows = this.queryOutcomeEvents({ ...params, limit: undefined });
|
|
150
|
+
const verdictDistribution = {};
|
|
151
|
+
const eventTypeDistribution = {};
|
|
152
|
+
const componentCounts = {};
|
|
153
|
+
let driftSum = 0;
|
|
154
|
+
let driftCount = 0;
|
|
155
|
+
let iterSum = 0;
|
|
156
|
+
let iterCount = 0;
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
if (row.verdict !== null) {
|
|
159
|
+
verdictDistribution[row.verdict] = (verdictDistribution[row.verdict] ?? 0) + 1;
|
|
160
|
+
}
|
|
161
|
+
eventTypeDistribution[row.eventType] = (eventTypeDistribution[row.eventType] ?? 0) + 1;
|
|
162
|
+
if (row.componentArea !== null) {
|
|
163
|
+
componentCounts[row.componentArea] = (componentCounts[row.componentArea] ?? 0) + 1;
|
|
164
|
+
}
|
|
165
|
+
if (row.driftCount !== null) {
|
|
166
|
+
driftSum += row.driftCount;
|
|
167
|
+
driftCount++;
|
|
168
|
+
}
|
|
169
|
+
if (row.iterationCount !== null) {
|
|
170
|
+
iterSum += row.iterationCount;
|
|
171
|
+
iterCount++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const topComponentAreas = Object.entries(componentCounts)
|
|
175
|
+
.map(([area, count]) => ({ area, count }))
|
|
176
|
+
.sort((a, b) => b.count - a.count)
|
|
177
|
+
.slice(0, 10);
|
|
178
|
+
return {
|
|
179
|
+
count: rows.length,
|
|
180
|
+
avgDriftCount: driftCount > 0 ? driftSum / driftCount : null,
|
|
181
|
+
avgIterationCount: iterCount > 0 ? iterSum / iterCount : null,
|
|
182
|
+
verdictDistribution,
|
|
183
|
+
eventTypeDistribution,
|
|
184
|
+
topComponentAreas,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
getOutcomeSummary(issueNumber) {
|
|
188
|
+
const rows = this.queryOutcomeEvents({ issueNumber, limit: 10000 });
|
|
189
|
+
if (rows.length === 0)
|
|
190
|
+
return null;
|
|
191
|
+
const eventsByType = {};
|
|
192
|
+
let driftCount = 0;
|
|
193
|
+
let blockers = 0;
|
|
194
|
+
let latestVerdict = null;
|
|
195
|
+
for (const row of rows) {
|
|
196
|
+
eventsByType[row.eventType] = (eventsByType[row.eventType] ?? 0) + 1;
|
|
197
|
+
if (row.driftCount !== null) {
|
|
198
|
+
driftCount += row.driftCount;
|
|
199
|
+
}
|
|
200
|
+
if (row.eventType === "blocker_recorded") {
|
|
201
|
+
blockers++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Rows are ordered by timestamp DESC, so first row has the latest verdict
|
|
205
|
+
for (const row of rows) {
|
|
206
|
+
if (row.verdict !== null) {
|
|
207
|
+
latestVerdict = row.verdict;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
totalEvents: rows.length,
|
|
213
|
+
latestVerdict,
|
|
214
|
+
driftCount,
|
|
215
|
+
blockers,
|
|
216
|
+
eventsByType,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
69
219
|
clearAll() {
|
|
220
|
+
// outcome_events is intentionally NOT cleared — outcome data is preserved across rebuilds
|
|
70
221
|
this.db.exec("DELETE FROM relationships; DELETE FROM tags; DELETE FROM documents;");
|
|
71
222
|
}
|
|
72
223
|
close() {
|
package/dist/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,17 @@ export function createServer(dbPath) {
|
|
|
39
39
|
limit: args.limit ?? 10,
|
|
40
40
|
includeSuperseded: args.includeSuperseded,
|
|
41
41
|
});
|
|
42
|
-
const enriched = results.map(r =>
|
|
42
|
+
const enriched = results.map(r => {
|
|
43
|
+
const base = { ...r, tags: db.getTags(r.id) };
|
|
44
|
+
// SearchResult does not carry githubIssue — fetch from documents table
|
|
45
|
+
const doc = db.getDocument(r.id);
|
|
46
|
+
if (doc?.githubIssue) {
|
|
47
|
+
const outcomes = db.getOutcomeSummary(doc.githubIssue);
|
|
48
|
+
if (outcomes)
|
|
49
|
+
return { ...base, outcomes_summary: outcomes };
|
|
50
|
+
}
|
|
51
|
+
return base;
|
|
52
|
+
});
|
|
43
53
|
return { content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }] };
|
|
44
54
|
}
|
|
45
55
|
catch (e) {
|
|
@@ -63,6 +73,74 @@ export function createServer(dbPath) {
|
|
|
63
73
|
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
64
74
|
}
|
|
65
75
|
});
|
|
76
|
+
server.tool("knowledge_record_outcome", "Record a pipeline outcome event (research, plan, phase, validation, etc.)", {
|
|
77
|
+
event_type: z.string().describe("Event type (e.g., 'phase_completed', 'research_started')"),
|
|
78
|
+
issue_number: z.number().describe("GitHub issue number"),
|
|
79
|
+
session_id: z.string().optional().describe("Team/hero session identifier"),
|
|
80
|
+
duration_ms: z.number().optional().describe("Duration in milliseconds"),
|
|
81
|
+
verdict: z.string().optional().describe("Outcome verdict (pass, fail, approved, needs_iteration)"),
|
|
82
|
+
component_area: z.string().optional().describe("Component path prefix (e.g., 'src/tools/')"),
|
|
83
|
+
estimate: z.string().optional().describe("Issue estimate (XS, S, M, L, XL)"),
|
|
84
|
+
drift_count: z.number().optional().describe("Files modified outside plan scope"),
|
|
85
|
+
model: z.string().optional().describe("LLM model used (opus, sonnet, haiku)"),
|
|
86
|
+
agent_type: z.string().optional().describe("Agent type (analyst, builder, integrator)"),
|
|
87
|
+
iteration_count: z.number().optional().describe("Number of retry/review cycles"),
|
|
88
|
+
payload: z.record(z.unknown()).optional().describe("Arbitrary JSON payload"),
|
|
89
|
+
}, async (args) => {
|
|
90
|
+
try {
|
|
91
|
+
const result = db.insertOutcomeEvent({
|
|
92
|
+
eventType: args.event_type,
|
|
93
|
+
issueNumber: args.issue_number,
|
|
94
|
+
sessionId: args.session_id,
|
|
95
|
+
durationMs: args.duration_ms,
|
|
96
|
+
verdict: args.verdict,
|
|
97
|
+
componentArea: args.component_area,
|
|
98
|
+
estimate: args.estimate,
|
|
99
|
+
driftCount: args.drift_count,
|
|
100
|
+
model: args.model,
|
|
101
|
+
agentType: args.agent_type,
|
|
102
|
+
iterationCount: args.iteration_count,
|
|
103
|
+
payload: args.payload,
|
|
104
|
+
});
|
|
105
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
server.tool("knowledge_query_outcomes", "Query outcome events with optional aggregation. Use to find patterns in pipeline history.", {
|
|
112
|
+
issue_number: z.number().optional().describe("Filter to specific issue"),
|
|
113
|
+
event_type: z.string().optional().describe("Filter by event type"),
|
|
114
|
+
component_area: z.string().optional().describe("Filter by component (prefix match)"),
|
|
115
|
+
estimate: z.string().optional().describe("Filter by estimate size"),
|
|
116
|
+
verdict: z.string().optional().describe("Filter by verdict"),
|
|
117
|
+
session_id: z.string().optional().describe("Filter by session"),
|
|
118
|
+
since: z.string().optional().describe("ISO date — only events after this"),
|
|
119
|
+
limit: z.number().optional().describe("Max results (default: 50)"),
|
|
120
|
+
aggregate: z.boolean().optional().describe("Return computed stats instead of raw rows"),
|
|
121
|
+
}, async (args) => {
|
|
122
|
+
try {
|
|
123
|
+
const params = {
|
|
124
|
+
issueNumber: args.issue_number,
|
|
125
|
+
eventType: args.event_type,
|
|
126
|
+
componentArea: args.component_area,
|
|
127
|
+
estimate: args.estimate,
|
|
128
|
+
verdict: args.verdict,
|
|
129
|
+
sessionId: args.session_id,
|
|
130
|
+
since: args.since,
|
|
131
|
+
limit: args.limit,
|
|
132
|
+
};
|
|
133
|
+
if (args.aggregate) {
|
|
134
|
+
const agg = db.aggregateOutcomeEvents(params);
|
|
135
|
+
return { content: [{ type: "text", text: JSON.stringify(agg, null, 2) }] };
|
|
136
|
+
}
|
|
137
|
+
const rows = db.queryOutcomeEvents(params);
|
|
138
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
142
|
+
}
|
|
143
|
+
});
|
|
66
144
|
return { server, db, fts, vec, hybrid, traverser };
|
|
67
145
|
}
|
|
68
146
|
const dbPath = resolveEnv("RALPH_KNOWLEDGE_DB") ?? DEFAULT_DB_PATH;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;AAEvE,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,gFAAgF;IAChF,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACjF,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC;IAEpC,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,gGAAgG,EAChG;QACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QACzE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAC/D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;QACtG,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;QAClE,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;KACpG,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;gBAC9C,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;gBACvB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;aAC1C,CAAC,CAAC;YACH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/__tests__/db.test.ts
CHANGED
|
@@ -49,3 +49,164 @@ describe("KnowledgeDB", () => {
|
|
|
49
49
|
expect(db.getDocument("doc-1")).toBeUndefined();
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
describe("Outcome Events", () => {
|
|
54
|
+
it("inserts and retrieves an outcome event", () => {
|
|
55
|
+
const result = db.insertOutcomeEvent({
|
|
56
|
+
eventType: "task_complete",
|
|
57
|
+
issueNumber: 42,
|
|
58
|
+
sessionId: "sess-1",
|
|
59
|
+
durationMs: 5000,
|
|
60
|
+
verdict: "success",
|
|
61
|
+
componentArea: "api/auth",
|
|
62
|
+
estimate: "S",
|
|
63
|
+
driftCount: 1,
|
|
64
|
+
model: "opus-4",
|
|
65
|
+
agentType: "coder",
|
|
66
|
+
iterationCount: 3,
|
|
67
|
+
payload: { notes: "all good" },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(result.id).toBeTruthy();
|
|
71
|
+
expect(result.eventType).toBe("task_complete");
|
|
72
|
+
expect(result.issueNumber).toBe(42);
|
|
73
|
+
expect(result.timestamp).toBeTruthy();
|
|
74
|
+
|
|
75
|
+
const rows = db.queryOutcomeEvents({ issueNumber: 42 });
|
|
76
|
+
expect(rows).toHaveLength(1);
|
|
77
|
+
expect(rows[0].eventType).toBe("task_complete");
|
|
78
|
+
expect(rows[0].sessionId).toBe("sess-1");
|
|
79
|
+
expect(rows[0].durationMs).toBe(5000);
|
|
80
|
+
expect(rows[0].verdict).toBe("success");
|
|
81
|
+
expect(rows[0].componentArea).toBe("api/auth");
|
|
82
|
+
expect(rows[0].estimate).toBe("S");
|
|
83
|
+
expect(rows[0].driftCount).toBe(1);
|
|
84
|
+
expect(rows[0].model).toBe("opus-4");
|
|
85
|
+
expect(rows[0].agentType).toBe("coder");
|
|
86
|
+
expect(rows[0].iterationCount).toBe(3);
|
|
87
|
+
expect(JSON.parse(rows[0].payload)).toEqual({ notes: "all good" });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("filters by event_type and component_area", () => {
|
|
91
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 1, componentArea: "api/auth" });
|
|
92
|
+
db.insertOutcomeEvent({ eventType: "drift", issueNumber: 2, componentArea: "api/billing" });
|
|
93
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 3, componentArea: "ui/dashboard" });
|
|
94
|
+
|
|
95
|
+
const byType = db.queryOutcomeEvents({ eventType: "task_complete" });
|
|
96
|
+
expect(byType).toHaveLength(2);
|
|
97
|
+
|
|
98
|
+
// component_area uses LIKE prefix match
|
|
99
|
+
const byComponent = db.queryOutcomeEvents({ componentArea: "api" });
|
|
100
|
+
expect(byComponent).toHaveLength(2);
|
|
101
|
+
|
|
102
|
+
const combined = db.queryOutcomeEvents({ eventType: "task_complete", componentArea: "api" });
|
|
103
|
+
expect(combined).toHaveLength(1);
|
|
104
|
+
expect(combined[0].issueNumber).toBe(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("filters by since date", () => {
|
|
108
|
+
// Insert events, then query with a since filter
|
|
109
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 1 });
|
|
110
|
+
|
|
111
|
+
// Query with a future date should return nothing
|
|
112
|
+
const futureRows = db.queryOutcomeEvents({ since: "2099-01-01T00:00:00.000Z" });
|
|
113
|
+
expect(futureRows).toHaveLength(0);
|
|
114
|
+
|
|
115
|
+
// Query with a past date should return the event
|
|
116
|
+
const pastRows = db.queryOutcomeEvents({ since: "2000-01-01T00:00:00.000Z" });
|
|
117
|
+
expect(pastRows).toHaveLength(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("filters by verdict and estimate", () => {
|
|
121
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 1, verdict: "success", estimate: "S" });
|
|
122
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 2, verdict: "blocked", estimate: "M" });
|
|
123
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 3, verdict: "success", estimate: "M" });
|
|
124
|
+
|
|
125
|
+
const byVerdict = db.queryOutcomeEvents({ verdict: "blocked" });
|
|
126
|
+
expect(byVerdict).toHaveLength(1);
|
|
127
|
+
expect(byVerdict[0].issueNumber).toBe(2);
|
|
128
|
+
|
|
129
|
+
const byEstimate = db.queryOutcomeEvents({ estimate: "M" });
|
|
130
|
+
expect(byEstimate).toHaveLength(2);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("respects limit parameter", () => {
|
|
134
|
+
for (let i = 0; i < 10; i++) {
|
|
135
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: i });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const limited = db.queryOutcomeEvents({ limit: 3 });
|
|
139
|
+
expect(limited).toHaveLength(3);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("returns most recent first", () => {
|
|
143
|
+
// Insert with explicit timestamps via direct SQL to control ordering
|
|
144
|
+
const ids = ["a", "b", "c"];
|
|
145
|
+
for (let i = 0; i < 3; i++) {
|
|
146
|
+
db.db.prepare(`
|
|
147
|
+
INSERT INTO outcome_events (id, event_type, issue_number, timestamp, payload)
|
|
148
|
+
VALUES (?, 'task_complete', 1, ?, '{}')
|
|
149
|
+
`).run(ids[i], `2026-03-0${i + 1}T00:00:00.000Z`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const rows = db.queryOutcomeEvents({ issueNumber: 1 });
|
|
153
|
+
expect(rows).toHaveLength(3);
|
|
154
|
+
expect(rows[0].id).toBe("c"); // March 3rd — most recent
|
|
155
|
+
expect(rows[2].id).toBe("a"); // March 1st — oldest
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("computes aggregation", () => {
|
|
159
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 1, verdict: "success", componentArea: "api/auth", driftCount: 2, iterationCount: 5 });
|
|
160
|
+
db.insertOutcomeEvent({ eventType: "drift", issueNumber: 2, verdict: "blocked", componentArea: "api/auth", driftCount: 4, iterationCount: 3 });
|
|
161
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 3, verdict: "success", componentArea: "ui/dashboard", driftCount: 0, iterationCount: 1 });
|
|
162
|
+
|
|
163
|
+
const agg = db.aggregateOutcomeEvents();
|
|
164
|
+
expect(agg.count).toBe(3);
|
|
165
|
+
expect(agg.avgDriftCount).toBe(2); // (2+4+0)/3
|
|
166
|
+
expect(agg.avgIterationCount).toBe(3); // (5+3+1)/3
|
|
167
|
+
expect(agg.verdictDistribution).toEqual({ success: 2, blocked: 1 });
|
|
168
|
+
expect(agg.eventTypeDistribution).toEqual({ task_complete: 2, drift: 1 });
|
|
169
|
+
expect(agg.topComponentAreas).toHaveLength(2);
|
|
170
|
+
expect(agg.topComponentAreas[0]).toEqual({ area: "api/auth", count: 2 });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("returns outcome summary for an issue", () => {
|
|
174
|
+
// Use direct SQL inserts with explicit timestamps to ensure deterministic ordering
|
|
175
|
+
db.db.prepare(`
|
|
176
|
+
INSERT INTO outcome_events (id, event_type, issue_number, timestamp, drift_count, payload)
|
|
177
|
+
VALUES (?, 'task_start', 42, '2026-03-01T00:00:00.000Z', 1, '{}')
|
|
178
|
+
`).run("oe-1");
|
|
179
|
+
db.db.prepare(`
|
|
180
|
+
INSERT INTO outcome_events (id, event_type, issue_number, timestamp, drift_count, payload)
|
|
181
|
+
VALUES (?, 'blocker_recorded', 42, '2026-03-02T00:00:00.000Z', 2, '{}')
|
|
182
|
+
`).run("oe-2");
|
|
183
|
+
db.db.prepare(`
|
|
184
|
+
INSERT INTO outcome_events (id, event_type, issue_number, timestamp, verdict, drift_count, payload)
|
|
185
|
+
VALUES (?, 'task_complete', 42, '2026-03-03T00:00:00.000Z', 'success', 0, '{}')
|
|
186
|
+
`).run("oe-3");
|
|
187
|
+
|
|
188
|
+
const summary = db.getOutcomeSummary(42);
|
|
189
|
+
expect(summary).not.toBeNull();
|
|
190
|
+
expect(summary!.totalEvents).toBe(3);
|
|
191
|
+
expect(summary!.latestVerdict).toBe("success"); // most recent with a verdict
|
|
192
|
+
expect(summary!.driftCount).toBe(3); // 1+2+0
|
|
193
|
+
expect(summary!.blockers).toBe(1); // blocker_recorded event type
|
|
194
|
+
expect(summary!.eventsByType).toEqual({ task_start: 1, blocker_recorded: 1, task_complete: 1 });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("returns null summary for missing issue", () => {
|
|
198
|
+
const summary = db.getOutcomeSummary(9999);
|
|
199
|
+
expect(summary).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("clearAll preserves outcome events", () => {
|
|
203
|
+
db.insertOutcomeEvent({ eventType: "task_complete", issueNumber: 1 });
|
|
204
|
+
db.upsertDocument({ id: "doc-1", path: "p", title: "t", date: null, type: null, status: null, githubIssue: null, content: "" });
|
|
205
|
+
|
|
206
|
+
db.clearAll();
|
|
207
|
+
|
|
208
|
+
expect(db.getDocument("doc-1")).toBeUndefined();
|
|
209
|
+
const rows = db.queryOutcomeEvents({ issueNumber: 1 });
|
|
210
|
+
expect(rows).toHaveLength(1);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -5,4 +5,10 @@ describe("knowledge-index server", () => {
|
|
|
5
5
|
const mod = await import("../index.js");
|
|
6
6
|
expect(typeof mod.createServer).toBe("function");
|
|
7
7
|
});
|
|
8
|
+
|
|
9
|
+
it("registers outcome tools without error", async () => {
|
|
10
|
+
const mod = await import("../index.js");
|
|
11
|
+
const { server } = mod.createServer(":memory:");
|
|
12
|
+
expect(server).toBeTruthy();
|
|
13
|
+
});
|
|
8
14
|
});
|
package/src/db.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
import type { Database as DatabaseType } from "better-sqlite3";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { mkdirSync } from "node:fs";
|
|
4
5
|
import { dirname } from "node:path";
|
|
5
6
|
|
|
@@ -20,6 +21,66 @@ export interface RelationshipRow {
|
|
|
20
21
|
type: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
export interface OutcomeEventInput {
|
|
25
|
+
eventType: string;
|
|
26
|
+
issueNumber: number;
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
durationMs?: number;
|
|
29
|
+
verdict?: string;
|
|
30
|
+
componentArea?: string;
|
|
31
|
+
estimate?: string;
|
|
32
|
+
driftCount?: number;
|
|
33
|
+
model?: string;
|
|
34
|
+
agentType?: string;
|
|
35
|
+
iterationCount?: number;
|
|
36
|
+
payload?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OutcomeEventRow {
|
|
40
|
+
id: string;
|
|
41
|
+
eventType: string;
|
|
42
|
+
issueNumber: number;
|
|
43
|
+
sessionId: string | null;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
durationMs: number | null;
|
|
46
|
+
verdict: string | null;
|
|
47
|
+
componentArea: string | null;
|
|
48
|
+
estimate: string | null;
|
|
49
|
+
driftCount: number | null;
|
|
50
|
+
model: string | null;
|
|
51
|
+
agentType: string | null;
|
|
52
|
+
iterationCount: number | null;
|
|
53
|
+
payload: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface OutcomeQueryParams {
|
|
57
|
+
issueNumber?: number;
|
|
58
|
+
eventType?: string;
|
|
59
|
+
componentArea?: string;
|
|
60
|
+
estimate?: string;
|
|
61
|
+
verdict?: string;
|
|
62
|
+
sessionId?: string;
|
|
63
|
+
since?: string;
|
|
64
|
+
limit?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface OutcomeAggregate {
|
|
68
|
+
count: number;
|
|
69
|
+
avgDriftCount: number | null;
|
|
70
|
+
avgIterationCount: number | null;
|
|
71
|
+
verdictDistribution: Record<string, number>;
|
|
72
|
+
eventTypeDistribution: Record<string, number>;
|
|
73
|
+
topComponentAreas: Array<{ area: string; count: number }>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface OutcomeSummary {
|
|
77
|
+
totalEvents: number;
|
|
78
|
+
latestVerdict: string | null;
|
|
79
|
+
driftCount: number;
|
|
80
|
+
blockers: number;
|
|
81
|
+
eventsByType: Record<string, number>;
|
|
82
|
+
}
|
|
83
|
+
|
|
23
84
|
export class KnowledgeDB {
|
|
24
85
|
readonly db: DatabaseType;
|
|
25
86
|
|
|
@@ -55,6 +116,29 @@ export class KnowledgeDB {
|
|
|
55
116
|
);
|
|
56
117
|
CREATE INDEX IF NOT EXISTS idx_rel_target ON relationships(target_id, type);
|
|
57
118
|
CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS outcome_events (
|
|
121
|
+
id TEXT PRIMARY KEY,
|
|
122
|
+
event_type TEXT NOT NULL,
|
|
123
|
+
issue_number INTEGER NOT NULL,
|
|
124
|
+
session_id TEXT,
|
|
125
|
+
timestamp TEXT NOT NULL,
|
|
126
|
+
duration_ms INTEGER,
|
|
127
|
+
verdict TEXT,
|
|
128
|
+
component_area TEXT,
|
|
129
|
+
estimate TEXT,
|
|
130
|
+
drift_count INTEGER,
|
|
131
|
+
model TEXT,
|
|
132
|
+
agent_type TEXT,
|
|
133
|
+
iteration_count INTEGER,
|
|
134
|
+
payload TEXT DEFAULT '{}'
|
|
135
|
+
);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_oe_type ON outcome_events(event_type);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_oe_issue ON outcome_events(issue_number);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_oe_component ON outcome_events(component_area);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_oe_timestamp ON outcome_events(timestamp);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_oe_session ON outcome_events(session_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_oe_type_component ON outcome_events(event_type, component_area);
|
|
58
142
|
`);
|
|
59
143
|
}
|
|
60
144
|
|
|
@@ -96,7 +180,166 @@ export class KnowledgeDB {
|
|
|
96
180
|
return this.db.prepare("SELECT source_id AS sourceId, target_id AS targetId, type FROM relationships WHERE target_id = ?").all(targetId) as RelationshipRow[];
|
|
97
181
|
}
|
|
98
182
|
|
|
183
|
+
insertOutcomeEvent(input: OutcomeEventInput): { id: string; eventType: string; issueNumber: number; timestamp: string } {
|
|
184
|
+
const id = randomUUID();
|
|
185
|
+
const timestamp = new Date().toISOString();
|
|
186
|
+
const payload = JSON.stringify(input.payload ?? {});
|
|
187
|
+
|
|
188
|
+
this.db.prepare(`
|
|
189
|
+
INSERT INTO outcome_events (id, event_type, issue_number, session_id, timestamp, duration_ms, verdict, component_area, estimate, drift_count, model, agent_type, iteration_count, payload)
|
|
190
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
191
|
+
`).run(
|
|
192
|
+
id,
|
|
193
|
+
input.eventType,
|
|
194
|
+
input.issueNumber,
|
|
195
|
+
input.sessionId ?? null,
|
|
196
|
+
timestamp,
|
|
197
|
+
input.durationMs ?? null,
|
|
198
|
+
input.verdict ?? null,
|
|
199
|
+
input.componentArea ?? null,
|
|
200
|
+
input.estimate ?? null,
|
|
201
|
+
input.driftCount ?? null,
|
|
202
|
+
input.model ?? null,
|
|
203
|
+
input.agentType ?? null,
|
|
204
|
+
input.iterationCount ?? null,
|
|
205
|
+
payload,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return { id, eventType: input.eventType, issueNumber: input.issueNumber, timestamp };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
queryOutcomeEvents(params: OutcomeQueryParams = {}): OutcomeEventRow[] {
|
|
212
|
+
const conditions: string[] = [];
|
|
213
|
+
const values: unknown[] = [];
|
|
214
|
+
|
|
215
|
+
if (params.issueNumber !== undefined) {
|
|
216
|
+
conditions.push("issue_number = ?");
|
|
217
|
+
values.push(params.issueNumber);
|
|
218
|
+
}
|
|
219
|
+
if (params.eventType !== undefined) {
|
|
220
|
+
conditions.push("event_type = ?");
|
|
221
|
+
values.push(params.eventType);
|
|
222
|
+
}
|
|
223
|
+
if (params.componentArea !== undefined) {
|
|
224
|
+
conditions.push("component_area LIKE ?");
|
|
225
|
+
values.push(`${params.componentArea}%`);
|
|
226
|
+
}
|
|
227
|
+
if (params.estimate !== undefined) {
|
|
228
|
+
conditions.push("estimate = ?");
|
|
229
|
+
values.push(params.estimate);
|
|
230
|
+
}
|
|
231
|
+
if (params.verdict !== undefined) {
|
|
232
|
+
conditions.push("verdict = ?");
|
|
233
|
+
values.push(params.verdict);
|
|
234
|
+
}
|
|
235
|
+
if (params.sessionId !== undefined) {
|
|
236
|
+
conditions.push("session_id = ?");
|
|
237
|
+
values.push(params.sessionId);
|
|
238
|
+
}
|
|
239
|
+
if (params.since !== undefined) {
|
|
240
|
+
conditions.push("timestamp >= ?");
|
|
241
|
+
values.push(params.since);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
245
|
+
const limit = params.limit ?? 50;
|
|
246
|
+
|
|
247
|
+
const sql = `
|
|
248
|
+
SELECT id, event_type AS eventType, issue_number AS issueNumber, session_id AS sessionId,
|
|
249
|
+
timestamp, duration_ms AS durationMs, verdict, component_area AS componentArea,
|
|
250
|
+
estimate, drift_count AS driftCount, model, agent_type AS agentType,
|
|
251
|
+
iteration_count AS iterationCount, payload
|
|
252
|
+
FROM outcome_events ${where}
|
|
253
|
+
ORDER BY timestamp DESC
|
|
254
|
+
LIMIT ?
|
|
255
|
+
`;
|
|
256
|
+
|
|
257
|
+
return this.db.prepare(sql).all(...values, limit) as OutcomeEventRow[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
aggregateOutcomeEvents(params: OutcomeQueryParams = {}): OutcomeAggregate {
|
|
261
|
+
// Override limit to aggregate over all matching events, not just the caller's limit
|
|
262
|
+
const rows = this.queryOutcomeEvents({ ...params, limit: undefined });
|
|
263
|
+
|
|
264
|
+
const verdictDistribution: Record<string, number> = {};
|
|
265
|
+
const eventTypeDistribution: Record<string, number> = {};
|
|
266
|
+
const componentCounts: Record<string, number> = {};
|
|
267
|
+
let driftSum = 0;
|
|
268
|
+
let driftCount = 0;
|
|
269
|
+
let iterSum = 0;
|
|
270
|
+
let iterCount = 0;
|
|
271
|
+
|
|
272
|
+
for (const row of rows) {
|
|
273
|
+
if (row.verdict !== null) {
|
|
274
|
+
verdictDistribution[row.verdict] = (verdictDistribution[row.verdict] ?? 0) + 1;
|
|
275
|
+
}
|
|
276
|
+
eventTypeDistribution[row.eventType] = (eventTypeDistribution[row.eventType] ?? 0) + 1;
|
|
277
|
+
if (row.componentArea !== null) {
|
|
278
|
+
componentCounts[row.componentArea] = (componentCounts[row.componentArea] ?? 0) + 1;
|
|
279
|
+
}
|
|
280
|
+
if (row.driftCount !== null) {
|
|
281
|
+
driftSum += row.driftCount;
|
|
282
|
+
driftCount++;
|
|
283
|
+
}
|
|
284
|
+
if (row.iterationCount !== null) {
|
|
285
|
+
iterSum += row.iterationCount;
|
|
286
|
+
iterCount++;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const topComponentAreas = Object.entries(componentCounts)
|
|
291
|
+
.map(([area, count]) => ({ area, count }))
|
|
292
|
+
.sort((a, b) => b.count - a.count)
|
|
293
|
+
.slice(0, 10);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
count: rows.length,
|
|
297
|
+
avgDriftCount: driftCount > 0 ? driftSum / driftCount : null,
|
|
298
|
+
avgIterationCount: iterCount > 0 ? iterSum / iterCount : null,
|
|
299
|
+
verdictDistribution,
|
|
300
|
+
eventTypeDistribution,
|
|
301
|
+
topComponentAreas,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
getOutcomeSummary(issueNumber: number): OutcomeSummary | null {
|
|
306
|
+
const rows = this.queryOutcomeEvents({ issueNumber, limit: 10000 });
|
|
307
|
+
if (rows.length === 0) return null;
|
|
308
|
+
|
|
309
|
+
const eventsByType: Record<string, number> = {};
|
|
310
|
+
let driftCount = 0;
|
|
311
|
+
let blockers = 0;
|
|
312
|
+
let latestVerdict: string | null = null;
|
|
313
|
+
|
|
314
|
+
for (const row of rows) {
|
|
315
|
+
eventsByType[row.eventType] = (eventsByType[row.eventType] ?? 0) + 1;
|
|
316
|
+
if (row.driftCount !== null) {
|
|
317
|
+
driftCount += row.driftCount;
|
|
318
|
+
}
|
|
319
|
+
if (row.eventType === "blocker_recorded") {
|
|
320
|
+
blockers++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Rows are ordered by timestamp DESC, so first row has the latest verdict
|
|
325
|
+
for (const row of rows) {
|
|
326
|
+
if (row.verdict !== null) {
|
|
327
|
+
latestVerdict = row.verdict;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
totalEvents: rows.length,
|
|
334
|
+
latestVerdict,
|
|
335
|
+
driftCount,
|
|
336
|
+
blockers,
|
|
337
|
+
eventsByType,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
99
341
|
clearAll(): void {
|
|
342
|
+
// outcome_events is intentionally NOT cleared — outcome data is preserved across rebuilds
|
|
100
343
|
this.db.exec("DELETE FROM relationships; DELETE FROM tags; DELETE FROM documents;");
|
|
101
344
|
}
|
|
102
345
|
|
package/src/index.ts
CHANGED
|
@@ -46,7 +46,16 @@ export function createServer(dbPath: string) {
|
|
|
46
46
|
limit: args.limit ?? 10,
|
|
47
47
|
includeSuperseded: args.includeSuperseded,
|
|
48
48
|
});
|
|
49
|
-
const enriched = results.map(r =>
|
|
49
|
+
const enriched = results.map(r => {
|
|
50
|
+
const base = { ...r, tags: db.getTags(r.id) };
|
|
51
|
+
// SearchResult does not carry githubIssue — fetch from documents table
|
|
52
|
+
const doc = db.getDocument(r.id);
|
|
53
|
+
if (doc?.githubIssue) {
|
|
54
|
+
const outcomes = db.getOutcomeSummary(doc.githubIssue);
|
|
55
|
+
if (outcomes) return { ...base, outcomes_summary: outcomes };
|
|
56
|
+
}
|
|
57
|
+
return base;
|
|
58
|
+
});
|
|
50
59
|
return { content: [{ type: "text" as const, text: JSON.stringify(enriched, null, 2) }] };
|
|
51
60
|
} catch (e) {
|
|
52
61
|
return { content: [{ type: "text" as const, text: `Error: ${(e as Error).message}` }], isError: true };
|
|
@@ -76,6 +85,84 @@ export function createServer(dbPath: string) {
|
|
|
76
85
|
},
|
|
77
86
|
);
|
|
78
87
|
|
|
88
|
+
server.tool(
|
|
89
|
+
"knowledge_record_outcome",
|
|
90
|
+
"Record a pipeline outcome event (research, plan, phase, validation, etc.)",
|
|
91
|
+
{
|
|
92
|
+
event_type: z.string().describe("Event type (e.g., 'phase_completed', 'research_started')"),
|
|
93
|
+
issue_number: z.number().describe("GitHub issue number"),
|
|
94
|
+
session_id: z.string().optional().describe("Team/hero session identifier"),
|
|
95
|
+
duration_ms: z.number().optional().describe("Duration in milliseconds"),
|
|
96
|
+
verdict: z.string().optional().describe("Outcome verdict (pass, fail, approved, needs_iteration)"),
|
|
97
|
+
component_area: z.string().optional().describe("Component path prefix (e.g., 'src/tools/')"),
|
|
98
|
+
estimate: z.string().optional().describe("Issue estimate (XS, S, M, L, XL)"),
|
|
99
|
+
drift_count: z.number().optional().describe("Files modified outside plan scope"),
|
|
100
|
+
model: z.string().optional().describe("LLM model used (opus, sonnet, haiku)"),
|
|
101
|
+
agent_type: z.string().optional().describe("Agent type (analyst, builder, integrator)"),
|
|
102
|
+
iteration_count: z.number().optional().describe("Number of retry/review cycles"),
|
|
103
|
+
payload: z.record(z.unknown()).optional().describe("Arbitrary JSON payload"),
|
|
104
|
+
},
|
|
105
|
+
async (args) => {
|
|
106
|
+
try {
|
|
107
|
+
const result = db.insertOutcomeEvent({
|
|
108
|
+
eventType: args.event_type,
|
|
109
|
+
issueNumber: args.issue_number,
|
|
110
|
+
sessionId: args.session_id,
|
|
111
|
+
durationMs: args.duration_ms,
|
|
112
|
+
verdict: args.verdict,
|
|
113
|
+
componentArea: args.component_area,
|
|
114
|
+
estimate: args.estimate,
|
|
115
|
+
driftCount: args.drift_count,
|
|
116
|
+
model: args.model,
|
|
117
|
+
agentType: args.agent_type,
|
|
118
|
+
iterationCount: args.iteration_count,
|
|
119
|
+
payload: args.payload as Record<string, unknown>,
|
|
120
|
+
});
|
|
121
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return { content: [{ type: "text" as const, text: `Error: ${(e as Error).message}` }], isError: true };
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
server.tool(
|
|
129
|
+
"knowledge_query_outcomes",
|
|
130
|
+
"Query outcome events with optional aggregation. Use to find patterns in pipeline history.",
|
|
131
|
+
{
|
|
132
|
+
issue_number: z.number().optional().describe("Filter to specific issue"),
|
|
133
|
+
event_type: z.string().optional().describe("Filter by event type"),
|
|
134
|
+
component_area: z.string().optional().describe("Filter by component (prefix match)"),
|
|
135
|
+
estimate: z.string().optional().describe("Filter by estimate size"),
|
|
136
|
+
verdict: z.string().optional().describe("Filter by verdict"),
|
|
137
|
+
session_id: z.string().optional().describe("Filter by session"),
|
|
138
|
+
since: z.string().optional().describe("ISO date — only events after this"),
|
|
139
|
+
limit: z.number().optional().describe("Max results (default: 50)"),
|
|
140
|
+
aggregate: z.boolean().optional().describe("Return computed stats instead of raw rows"),
|
|
141
|
+
},
|
|
142
|
+
async (args) => {
|
|
143
|
+
try {
|
|
144
|
+
const params = {
|
|
145
|
+
issueNumber: args.issue_number,
|
|
146
|
+
eventType: args.event_type,
|
|
147
|
+
componentArea: args.component_area,
|
|
148
|
+
estimate: args.estimate,
|
|
149
|
+
verdict: args.verdict,
|
|
150
|
+
sessionId: args.session_id,
|
|
151
|
+
since: args.since,
|
|
152
|
+
limit: args.limit,
|
|
153
|
+
};
|
|
154
|
+
if (args.aggregate) {
|
|
155
|
+
const agg = db.aggregateOutcomeEvents(params);
|
|
156
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(agg, null, 2) }] };
|
|
157
|
+
}
|
|
158
|
+
const rows = db.queryOutcomeEvents(params);
|
|
159
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(rows, null, 2) }] };
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return { content: [{ type: "text" as const, text: `Error: ${(e as Error).message}` }], isError: true };
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
79
166
|
return { server, db, fts, vec, hybrid, traverser };
|
|
80
167
|
}
|
|
81
168
|
|