@rigour-labs/core 6.0.0 → 6.1.0-beta.2

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 (48) hide show
  1. package/dist/context/automatic-index.d.ts +18 -0
  2. package/dist/context/automatic-index.js +142 -0
  3. package/dist/context/automatic-index.test.d.ts +1 -0
  4. package/dist/context/automatic-index.test.js +45 -0
  5. package/dist/context/cache-engine.d.ts +25 -0
  6. package/dist/context/dependency-graph.d.ts +13 -0
  7. package/dist/context/dependency-graph.js +97 -0
  8. package/dist/context/dependency-graph.test.d.ts +1 -0
  9. package/dist/context/dependency-graph.test.js +13 -0
  10. package/dist/context/index-bridge.js +21 -2
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.js +7 -0
  13. package/dist/pattern-index/embeddings.js +10 -3
  14. package/dist/pattern-index/indexer.d.ts +2 -0
  15. package/dist/pattern-index/indexer.js +72 -0
  16. package/dist/services/agent-history.d.ts +76 -0
  17. package/dist/services/agent-history.js +201 -0
  18. package/dist/services/agent-history.test.d.ts +1 -0
  19. package/dist/services/agent-history.test.js +69 -0
  20. package/dist/services/context-telemetry-service.d.ts +21 -0
  21. package/dist/services/context-telemetry-service.js +36 -7
  22. package/dist/services/context-telemetry-service.test.js +13 -0
  23. package/dist/services/engineering-knowledge-graph.d.ts +55 -0
  24. package/dist/services/engineering-knowledge-graph.js +177 -0
  25. package/dist/services/engineering-knowledge-graph.test.d.ts +1 -0
  26. package/dist/services/engineering-knowledge-graph.test.js +59 -0
  27. package/dist/services/model-pricing.d.ts +2 -0
  28. package/dist/services/model-pricing.js +2 -0
  29. package/dist/services/system-health.d.ts +34 -0
  30. package/dist/services/system-health.js +53 -0
  31. package/dist/storage/db.js +130 -1
  32. package/dist/storage/index.d.ts +4 -0
  33. package/dist/storage/index.js +2 -0
  34. package/dist/storage/lessons.d.ts +60 -0
  35. package/dist/storage/lessons.js +322 -0
  36. package/dist/storage/lessons.test.d.ts +1 -0
  37. package/dist/storage/lessons.test.js +50 -0
  38. package/dist/storage/local-encryption.d.ts +2 -0
  39. package/dist/storage/local-encryption.js +41 -0
  40. package/dist/storage/team-store.d.ts +75 -0
  41. package/dist/storage/team-store.js +321 -0
  42. package/dist/storage/team-store.test.d.ts +1 -0
  43. package/dist/storage/team-store.test.js +11 -0
  44. package/dist/storage/team-vector-store.d.ts +25 -0
  45. package/dist/storage/team-vector-store.js +183 -0
  46. package/dist/storage/team-vector-store.test.d.ts +1 -0
  47. package/dist/storage/team-vector-store.test.js +42 -0
  48. package/package.json +8 -6
@@ -0,0 +1,177 @@
1
+ function shortPath(value) {
2
+ const parts = value.split('/');
3
+ return parts.length > 3 ? `…/${parts.slice(-3).join('/')}` : value;
4
+ }
5
+ function shortLabel(value, limit = 72) {
6
+ return value.length > limit ? `${value.slice(0, limit - 1)}…` : value;
7
+ }
8
+ export function buildEngineeringKnowledgeGraph(input) {
9
+ const nodes = new Map();
10
+ const edges = new Map();
11
+ const addNode = (node) => nodes.set(node.id, node);
12
+ const addEdge = (from, to, type) => {
13
+ const id = `${type}:${from}:${to}`;
14
+ edges.set(id, { id, from, to, type });
15
+ };
16
+ const repoId = `repository:${input.repository.id}`;
17
+ addNode({ id: repoId, type: 'repository', label: input.repository.name, detail: input.repository.id, repositoryId: input.repository.id, weight: 10 });
18
+ const eventFiles = new Set(input.events.flatMap(event => event.files));
19
+ const graph = input.dependencyGraph;
20
+ const degree = new Map();
21
+ for (const edge of graph?.edges ?? []) {
22
+ degree.set(edge.from, (degree.get(edge.from) ?? 0) + 1);
23
+ degree.set(edge.to, (degree.get(edge.to) ?? 0) + 1);
24
+ }
25
+ const maxFiles = input.maxFiles ?? 180;
26
+ const selectedFiles = [...(graph?.nodes ?? [])]
27
+ .sort((a, b) => Number(eventFiles.has(b)) - Number(eventFiles.has(a)) || (degree.get(b) ?? 0) - (degree.get(a) ?? 0))
28
+ .slice(0, maxFiles);
29
+ const selected = new Set(selectedFiles);
30
+ for (const file of selectedFiles) {
31
+ const id = `file:${file}`;
32
+ addNode({ id, type: 'file', label: shortPath(file), detail: file, repositoryId: input.repository.id, weight: Math.min(8, 1 + (degree.get(file) ?? 0)) });
33
+ addEdge(repoId, id, 'contains');
34
+ }
35
+ for (const edge of graph?.edges ?? []) {
36
+ if (selected.has(edge.from) && selected.has(edge.to))
37
+ addEdge(`file:${edge.from}`, `file:${edge.to}`, 'imports');
38
+ }
39
+ for (const run of input.runs.slice(0, 100)) {
40
+ const agentId = `agent:${run.agentId}`;
41
+ const runId = `run:${run.id}`;
42
+ addNode({ id: agentId, type: 'agent', label: run.agentId, weight: 5 });
43
+ addNode({ id: runId, type: 'run', label: run.taskId ?? run.id, detail: `${run.eventCount} events`, state: run.status, weight: 4 });
44
+ addEdge(agentId, runId, 'performed');
45
+ addEdge(runId, repoId, 'belongs_to');
46
+ if (run.taskId) {
47
+ const taskId = `task:${run.taskId}`;
48
+ addNode({ id: taskId, type: 'task', label: run.taskId, weight: 4 });
49
+ addEdge(runId, taskId, 'belongs_to');
50
+ }
51
+ for (const file of run.files)
52
+ if (selected.has(file))
53
+ addEdge(runId, `file:${file}`, 'touched');
54
+ if (run.status !== 'active' && run.status !== 'unknown') {
55
+ const outcomeId = `outcome:${run.id}:${run.status}`;
56
+ addNode({ id: outcomeId, type: 'outcome', label: run.status, state: run.status, weight: 3 });
57
+ addEdge(runId, outcomeId, 'resulted_in');
58
+ }
59
+ }
60
+ for (const lesson of input.lessons.slice(0, 150)) {
61
+ const lessonId = `lesson:${lesson.id}`;
62
+ addNode({
63
+ id: lessonId,
64
+ type: 'lesson',
65
+ label: lesson.subject,
66
+ detail: `${lesson.visibility} · ${lesson.state} · ${Math.round(lesson.confidence * 100)}%`,
67
+ state: lesson.state,
68
+ repositoryId: lesson.repositoryId,
69
+ weight: 4,
70
+ });
71
+ const sourceRepo = lesson.repositoryId === input.repository.id ? repoId : `repository:${lesson.repositoryId}`;
72
+ if (!nodes.has(sourceRepo))
73
+ addNode({ id: sourceRepo, type: 'repository', label: lesson.repositoryName ?? `Repository ${lesson.repositoryId.slice(0, 8)}`, detail: lesson.repositoryId, repositoryId: lesson.repositoryId, weight: 7 });
74
+ addEdge(sourceRepo, lessonId, 'produced');
75
+ }
76
+ for (const pattern of [...(input.patterns ?? [])]
77
+ .sort((a, b) => (b.usageCount ?? 0) - (a.usageCount ?? 0))
78
+ .slice(0, 120)) {
79
+ const patternId = `pattern:${pattern.id}`;
80
+ addNode({
81
+ id: patternId,
82
+ type: 'pattern',
83
+ label: pattern.name,
84
+ detail: `${pattern.type} · ${pattern.description || pattern.file}`,
85
+ repositoryId: input.repository.id,
86
+ weight: Math.min(8, 2 + Math.sqrt((pattern.usageCount ?? 0) + 1)),
87
+ });
88
+ const fileId = `file:${pattern.file}`;
89
+ addEdge(nodes.has(fileId) ? fileId : repoId, patternId, nodes.has(fileId) ? 'defined_in' : 'contains');
90
+ }
91
+ for (const memory of (input.memories ?? []).slice(0, 100)) {
92
+ const memoryId = `memory:${memory.id}`;
93
+ addNode({
94
+ id: memoryId,
95
+ type: 'memory',
96
+ label: memory.label,
97
+ detail: `${memory.source ?? 'local'} · ${memory.detail ?? 'retained memory'}`,
98
+ repositoryId: input.repository.id,
99
+ weight: 3,
100
+ });
101
+ addEdge(repoId, memoryId, 'remembered');
102
+ }
103
+ for (const event of input.events) {
104
+ if (!event.guidance)
105
+ continue;
106
+ const day = event.timestamp.slice(0, 10);
107
+ const runKey = event.sessionId ?? event.taskId ?? event.requestId ?? `${event.agentId}:${day}`;
108
+ const runId = `run:${runKey}`;
109
+ if (!nodes.has(runId))
110
+ continue;
111
+ const adviceId = `advice:${event.requestId ?? event.id}`;
112
+ const telemetry = event.telemetry;
113
+ const avoidedTokens = Math.max(0, (telemetry?.candidateTokens ?? 0) - (telemetry?.returnedTokens ?? 0));
114
+ const knowledgeItems = event.guidance.patternRefs.length + event.guidance.lessonRefs.length + event.guidance.memoryRefs.length;
115
+ addNode({
116
+ id: adviceId,
117
+ type: 'advice',
118
+ label: shortLabel(event.guidance.recommendation),
119
+ detail: `${event.guidance.kind} · ${knowledgeItems} knowledge items · ${avoidedTokens} tokens avoided`,
120
+ state: telemetry?.classification ?? 'measured estimate',
121
+ repositoryId: input.repository.id,
122
+ weight: Math.min(8, 3 + Math.sqrt(knowledgeItems + 1)),
123
+ evidence: {
124
+ recommendation: event.guidance.recommendation,
125
+ candidateTokens: telemetry?.candidateTokens ?? 0,
126
+ returnedTokens: telemetry?.returnedTokens ?? 0,
127
+ avoidedTokens,
128
+ candidateFiles: telemetry?.candidateFiles ?? 0,
129
+ returnedFiles: telemetry?.returnedFiles ?? 0,
130
+ excludedFiles: event.guidance.excludedFileCount,
131
+ cacheStatus: telemetry?.cacheStatus ?? 'none',
132
+ classification: telemetry?.classification ?? 'measured estimate',
133
+ },
134
+ });
135
+ addEdge(runId, adviceId, 'guided');
136
+ for (const reference of event.guidance.patternRefs) {
137
+ const sourceId = `pattern:${reference.id}`;
138
+ if (nodes.has(sourceId))
139
+ addEdge(sourceId, adviceId, 'informed');
140
+ }
141
+ for (const reference of event.guidance.lessonRefs) {
142
+ const sourceId = `lesson:${reference.id}`;
143
+ if (nodes.has(sourceId))
144
+ addEdge(sourceId, adviceId, 'informed');
145
+ }
146
+ for (const reference of event.guidance.memoryRefs) {
147
+ const sourceId = `memory:${reference.id}`;
148
+ if (nodes.has(sourceId))
149
+ addEdge(sourceId, adviceId, 'informed');
150
+ }
151
+ }
152
+ const prevented = input.events.filter(event => event.outcome === 'rejected');
153
+ if (prevented.length > 0) {
154
+ const policyId = 'policy:rigour-firewall';
155
+ const outcomeId = 'outcome:prevented-actions';
156
+ addNode({ id: policyId, type: 'policy', label: 'Rigour transaction firewall', detail: 'Deterministic mediation boundary', state: 'active', weight: 8 });
157
+ addNode({ id: outcomeId, type: 'outcome', label: `${prevented.length} prevented actions`, detail: 'Rejected before unsafe execution', state: 'verified', weight: Math.min(10, 4 + prevented.length) });
158
+ addEdge(policyId, repoId, 'protects');
159
+ addEdge(policyId, outcomeId, 'produced');
160
+ }
161
+ const counts = [...nodes.values()].reduce((result, node) => {
162
+ result[node.type] = (result[node.type] ?? 0) + 1;
163
+ return result;
164
+ }, {});
165
+ return {
166
+ schemaVersion: 1,
167
+ generatedAt: new Date().toISOString(),
168
+ nodes: [...nodes.values()],
169
+ edges: [...edges.values()],
170
+ truncated: (graph?.nodes.length ?? 0) > selectedFiles.length
171
+ || input.runs.length > 100
172
+ || input.lessons.length > 150
173
+ || (input.patterns?.length ?? 0) > 120
174
+ || (input.memories?.length ?? 0) > 100,
175
+ counts,
176
+ };
177
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildEngineeringKnowledgeGraph } from './engineering-knowledge-graph.js';
3
+ describe('engineering knowledge graph', () => {
4
+ it('connects code, agent outcomes and cross-repository lessons', () => {
5
+ const graph = buildEngineeringKnowledgeGraph({
6
+ repository: { id: 'repo-a', name: 'payments' },
7
+ dependencyGraph: { version: 1, generatedAt: '', nodes: ['src/a.ts', 'src/b.ts'], edges: [{ from: 'src/a.ts', to: 'src/b.ts' }] },
8
+ events: [],
9
+ runs: [{ id: 'run-1', agentId: 'agent-1', startedAt: '', endedAt: '', status: 'success', eventCount: 2, files: ['src/a.ts'], tools: ['check'] }],
10
+ lessons: [{ id: 'lesson-1', repositoryId: 'repo-b', repositoryName: 'platform', visibility: 'team', state: 'promoted', kind: 'interaction', subject: 'Use bounded retries', evidence: {}, confidence: 0.9, source: 'mcp', createdAt: 1, updatedAt: 1 }],
11
+ patterns: [{ id: 'pattern-1', name: 'withTransaction', type: 'function', file: 'src/a.ts', usageCount: 8 }],
12
+ memories: [{ id: 'memory-1', label: 'migration-owner', source: 'project' }],
13
+ });
14
+ expect(graph.counts.repository).toBe(2);
15
+ expect(graph.nodes.some(node => node.type === 'outcome' && node.label === 'success')).toBe(true);
16
+ expect(graph.edges.some(edge => edge.type === 'imports')).toBe(true);
17
+ expect(graph.edges.some(edge => edge.type === 'produced')).toBe(true);
18
+ expect(graph.nodes.some(node => node.type === 'pattern')).toBe(true);
19
+ expect(graph.nodes.some(node => node.type === 'memory')).toBe(true);
20
+ });
21
+ it('represents prevented agent actions as policy evidence', () => {
22
+ const graph = buildEngineeringKnowledgeGraph({
23
+ repository: { id: 'repo-a', name: 'payments' },
24
+ events: [{ id: 'event-1', timestamp: '', type: 'tool_denied', agentId: 'agent-1', outcome: 'rejected', files: [], summary: 'write denied' }],
25
+ runs: [],
26
+ lessons: [],
27
+ });
28
+ expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'policy', state: 'active' }));
29
+ expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'outcome', state: 'verified' }));
30
+ expect(graph.edges.some(edge => edge.type === 'protects')).toBe(true);
31
+ });
32
+ it('connects issued advice to its run and supporting knowledge', () => {
33
+ const event = {
34
+ id: 'event-1', requestId: 'run-1', timestamp: '2026-01-01T00:00:00Z', type: 'tool_response',
35
+ agentId: 'agent-1', outcome: 'success', files: ['src/a.ts'], summary: 'scope returned',
36
+ guidance: {
37
+ kind: 'context-scope',
38
+ recommendation: 'Read only src/a.ts.',
39
+ patternRefs: [{ id: 'pattern-1', label: 'withTransaction' }],
40
+ lessonRefs: [], memoryRefs: [], selectedFiles: ['src/a.ts'], excludedFileCount: 9, conflicts: 0,
41
+ },
42
+ telemetry: {
43
+ candidateTokens: 1200, returnedTokens: 200, candidateFiles: 10, returnedFiles: 1,
44
+ cacheStatus: 'exact-hit', classification: 'measured estimate',
45
+ },
46
+ };
47
+ const graph = buildEngineeringKnowledgeGraph({
48
+ repository: { id: 'repo-a', name: 'payments' },
49
+ dependencyGraph: { version: 1, generatedAt: '', nodes: ['src/a.ts'], edges: [] },
50
+ events: [event],
51
+ runs: [{ id: 'run-1', agentId: 'agent-1', startedAt: event.timestamp, endedAt: event.timestamp, status: 'success', eventCount: 1, files: ['src/a.ts'], tools: ['rigour_context_scope'] }],
52
+ lessons: [],
53
+ patterns: [{ id: 'pattern-1', name: 'withTransaction', type: 'function', file: 'src/a.ts' }],
54
+ });
55
+ expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'advice', evidence: expect.objectContaining({ avoidedTokens: 1000 }) }));
56
+ expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'run:run-1', to: 'advice:run-1', type: 'guided' }));
57
+ expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'pattern:pattern-1', to: 'advice:run-1', type: 'informed' }));
58
+ });
59
+ });
@@ -3,6 +3,8 @@
3
3
  * Observed Cursor costs always take precedence; these rates estimate when absent.
4
4
  */
5
5
  import type { ModelUsage } from '../storage/context-telemetry.js';
6
+ export declare const MODEL_PRICING_SOURCE = "Rigour versioned model registry";
7
+ export declare const MODEL_PRICING_EFFECTIVE_DATE = "2026-08-01";
6
8
  export interface ModelPricingTier {
7
9
  label: string;
8
10
  inputPerMillionUsd: number;
@@ -2,6 +2,8 @@
2
2
  * Model-aware input pricing for context cost telemetry.
3
3
  * Observed Cursor costs always take precedence; these rates estimate when absent.
4
4
  */
5
+ export const MODEL_PRICING_SOURCE = 'Rigour versioned model registry';
6
+ export const MODEL_PRICING_EFFECTIVE_DATE = '2026-08-01';
5
7
  /** Per-million-token USD rates. Input rates drive avoided-context estimates. */
6
8
  export const MODEL_PRICING = {
7
9
  'composer-2.5-standard': {
@@ -0,0 +1,34 @@
1
+ import { getTeamModeStatus } from '../storage/team-store.js';
2
+ export interface SystemHealth {
3
+ schemaVersion: 1;
4
+ generatedAt: string;
5
+ index: {
6
+ status: string;
7
+ patterns: number;
8
+ files: number;
9
+ updatedAt?: string;
10
+ };
11
+ graph: {
12
+ status: string;
13
+ nodes: number;
14
+ edges: number;
15
+ updatedAt?: string;
16
+ };
17
+ semantic: {
18
+ status: string;
19
+ provider: 'local' | 'pgvector';
20
+ message?: string;
21
+ };
22
+ caches: {
23
+ status: string;
24
+ layers: Record<'static' | 'component' | 'semantic' | 'checkpoint', number>;
25
+ };
26
+ learning: {
27
+ status: string;
28
+ total: number;
29
+ observations: number;
30
+ states: Record<string, number>;
31
+ };
32
+ storage: Awaited<ReturnType<typeof getTeamModeStatus>>;
33
+ }
34
+ export declare function getSystemHealth(cwd: string): Promise<SystemHealth>;
@@ -0,0 +1,53 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { getAutomaticIndexStatus } from '../context/automatic-index.js';
4
+ import { getDefaultIndexPath, loadPatternIndex } from '../pattern-index/indexer.js';
5
+ import { listContextCacheRecords } from '../storage/context-telemetry.js';
6
+ import { countInteractionEvidence, listLessons } from '../storage/lessons.js';
7
+ import { getTeamModeStatus } from '../storage/team-store.js';
8
+ export async function getSystemHealth(cwd) {
9
+ const [automatic, index, cacheRecords, lessons, observations, storage, graph] = await Promise.all([
10
+ getAutomaticIndexStatus(cwd),
11
+ loadPatternIndex(getDefaultIndexPath(cwd)).catch(() => null),
12
+ listContextCacheRecords({ limit: 10_000 }, cwd).catch(() => []),
13
+ listLessons(cwd, 10_000).catch(() => []),
14
+ countInteractionEvidence(cwd).catch(() => 0),
15
+ getTeamModeStatus(),
16
+ fs.readJson(path.join(cwd, '.rigour', 'dependency-graph.json')).catch(() => null),
17
+ ]);
18
+ const layers = { static: 0, component: 0, semantic: 0, checkpoint: 0 };
19
+ for (const record of cacheRecords)
20
+ layers[record.cacheType]++;
21
+ const states = {};
22
+ for (const lesson of lessons)
23
+ states[lesson.state] = (states[lesson.state] ?? 0) + 1;
24
+ const graphNodes = Array.isArray(graph?.nodes) ? graph.nodes.length : 0;
25
+ const graphEdges = Array.isArray(graph?.edges) ? graph.edges.length : 0;
26
+ return {
27
+ schemaVersion: 1,
28
+ generatedAt: new Date().toISOString(),
29
+ index: {
30
+ status: index ? automatic.structural : 'missing',
31
+ patterns: index?.stats?.totalPatterns ?? index?.patterns?.length ?? 0,
32
+ files: index?.stats?.totalFiles ?? 0,
33
+ updatedAt: index?.lastUpdated,
34
+ },
35
+ graph: { status: graph ? automatic.graph : 'missing', nodes: graphNodes, edges: graphEdges, updatedAt: graph?.generatedAt },
36
+ semantic: storage.semantic
37
+ ? {
38
+ status: storage.semantic.status,
39
+ provider: 'pgvector',
40
+ message: storage.semantic.status === 'ready'
41
+ ? `pgvector · ${storage.semantic.indexedLessons ?? 0} lessons indexed${storage.semantic.missingLessons ? ` · ${storage.semantic.missingLessons} pending` : ''}`
42
+ : storage.semantic.message,
43
+ }
44
+ : {
45
+ status: automatic.semantic,
46
+ provider: 'local',
47
+ message: automatic.message ?? (automatic.semantic === 'ready' ? 'Local pattern embeddings ready.' : undefined),
48
+ },
49
+ caches: { status: Object.values(layers).every(count => count > 0) ? 'ready' : 'warming', layers },
50
+ learning: { status: observations || lessons.length ? 'active' : 'idle', total: lessons.length, observations, states },
51
+ storage,
52
+ };
53
+ }
@@ -45,7 +45,7 @@ function loadSqlite3() {
45
45
  const RIGOUR_DIR = path.join(os.homedir(), '.rigour');
46
46
  const DB_PATH = path.join(RIGOUR_DIR, 'rigour.db');
47
47
  /** Current schema version — bump when adding migrations. */
48
- const SCHEMA_VERSION = 4;
48
+ const SCHEMA_VERSION = 7;
49
49
  const SCHEMA_SQL = `
50
50
  -- Schema version tracking
51
51
  CREATE TABLE IF NOT EXISTS meta (
@@ -177,6 +177,61 @@ CREATE TABLE IF NOT EXISTS checkpoint_metrics (
177
177
  created_at INTEGER NOT NULL
178
178
  );
179
179
 
180
+ -- Evidence-gated learning produced during normal agent interaction.
181
+ CREATE TABLE IF NOT EXISTS lessons (
182
+ id TEXT PRIMARY KEY,
183
+ repository_id TEXT NOT NULL,
184
+ actor_id TEXT,
185
+ team_id TEXT,
186
+ visibility TEXT NOT NULL DEFAULT 'personal',
187
+ state TEXT NOT NULL DEFAULT 'candidate',
188
+ kind TEXT NOT NULL,
189
+ subject TEXT NOT NULL,
190
+ evidence_json TEXT NOT NULL,
191
+ confidence REAL NOT NULL DEFAULT 0.3,
192
+ source TEXT NOT NULL,
193
+ supersedes_id TEXT,
194
+ created_at INTEGER NOT NULL,
195
+ updated_at INTEGER NOT NULL
196
+ );
197
+
198
+ CREATE TABLE IF NOT EXISTS sync_outbox (
199
+ id TEXT PRIMARY KEY,
200
+ operation TEXT NOT NULL,
201
+ entity_type TEXT NOT NULL,
202
+ entity_id TEXT NOT NULL,
203
+ payload_json TEXT NOT NULL,
204
+ attempts INTEGER NOT NULL DEFAULT 0,
205
+ last_error TEXT,
206
+ created_at INTEGER NOT NULL,
207
+ synced_at INTEGER
208
+ );
209
+
210
+ -- Stable repository registry used by cross-project learning and graph views.
211
+ CREATE TABLE IF NOT EXISTS repositories (
212
+ id TEXT PRIMARY KEY,
213
+ canonical_uri TEXT NOT NULL,
214
+ display_name TEXT NOT NULL,
215
+ last_seen INTEGER NOT NULL
216
+ );
217
+
218
+ -- Append-only observations from normal governed agent work. These are evidence,
219
+ -- not reusable rules; lesson promotion remains a separate gated decision.
220
+ CREATE TABLE IF NOT EXISTS interaction_events (
221
+ id TEXT PRIMARY KEY,
222
+ repository_id TEXT NOT NULL,
223
+ actor_id TEXT,
224
+ task_id TEXT,
225
+ session_id TEXT,
226
+ request_id TEXT NOT NULL,
227
+ tool_name TEXT NOT NULL,
228
+ phase TEXT NOT NULL,
229
+ outcome TEXT NOT NULL,
230
+ deterministic INTEGER NOT NULL DEFAULT 0,
231
+ evidence_json TEXT NOT NULL,
232
+ created_at INTEGER NOT NULL
233
+ );
234
+
180
235
  -- Indexes for performance
181
236
  CREATE INDEX IF NOT EXISTS idx_scans_repo ON scans(repo);
182
237
  CREATE INDEX IF NOT EXISTS idx_scans_timestamp ON scans(timestamp);
@@ -192,6 +247,11 @@ CREATE INDEX IF NOT EXISTS idx_model_usage_task ON model_usage(task_id);
192
247
  CREATE INDEX IF NOT EXISTS idx_model_usage_created ON model_usage(created_at);
193
248
  CREATE INDEX IF NOT EXISTS idx_context_cache_type ON context_cache(cache_type);
194
249
  CREATE INDEX IF NOT EXISTS idx_checkpoint_metrics_task ON checkpoint_metrics(task_id);
250
+ CREATE INDEX IF NOT EXISTS idx_lessons_lookup ON lessons(repository_id, actor_id, team_id, state);
251
+ CREATE INDEX IF NOT EXISTS idx_sync_outbox_pending ON sync_outbox(synced_at, created_at);
252
+ CREATE INDEX IF NOT EXISTS idx_repositories_last_seen ON repositories(last_seen);
253
+ CREATE INDEX IF NOT EXISTS idx_interaction_events_repo_created ON interaction_events(repository_id, created_at);
254
+ CREATE INDEX IF NOT EXISTS idx_interaction_events_request ON interaction_events(request_id);
195
255
  `;
196
256
  function wrapDatabase(raw) {
197
257
  const db = {
@@ -369,6 +429,75 @@ async function runMigrations(db) {
369
429
  `);
370
430
  await db.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '4')");
371
431
  }
432
+ if (current < 5) {
433
+ await db.exec(`
434
+ CREATE TABLE IF NOT EXISTS lessons (
435
+ id TEXT PRIMARY KEY,
436
+ repository_id TEXT NOT NULL,
437
+ actor_id TEXT,
438
+ team_id TEXT,
439
+ visibility TEXT NOT NULL DEFAULT 'personal',
440
+ state TEXT NOT NULL DEFAULT 'candidate',
441
+ kind TEXT NOT NULL,
442
+ subject TEXT NOT NULL,
443
+ evidence_json TEXT NOT NULL,
444
+ confidence REAL NOT NULL DEFAULT 0.3,
445
+ source TEXT NOT NULL,
446
+ supersedes_id TEXT,
447
+ created_at INTEGER NOT NULL,
448
+ updated_at INTEGER NOT NULL
449
+ );
450
+ CREATE TABLE IF NOT EXISTS sync_outbox (
451
+ id TEXT PRIMARY KEY,
452
+ operation TEXT NOT NULL,
453
+ entity_type TEXT NOT NULL,
454
+ entity_id TEXT NOT NULL,
455
+ payload_json TEXT NOT NULL,
456
+ attempts INTEGER NOT NULL DEFAULT 0,
457
+ last_error TEXT,
458
+ created_at INTEGER NOT NULL,
459
+ synced_at INTEGER
460
+ );
461
+ CREATE INDEX IF NOT EXISTS idx_lessons_lookup ON lessons(repository_id, actor_id, team_id, state);
462
+ CREATE INDEX IF NOT EXISTS idx_sync_outbox_pending ON sync_outbox(synced_at, created_at);
463
+ `);
464
+ await db.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '5')");
465
+ }
466
+ if (current < 6) {
467
+ await db.exec(`
468
+ CREATE TABLE IF NOT EXISTS repositories (
469
+ id TEXT PRIMARY KEY,
470
+ canonical_uri TEXT NOT NULL,
471
+ display_name TEXT NOT NULL,
472
+ last_seen INTEGER NOT NULL
473
+ );
474
+ CREATE INDEX IF NOT EXISTS idx_repositories_last_seen ON repositories(last_seen);
475
+ `);
476
+ await db.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '6')");
477
+ }
478
+ if (current < 7) {
479
+ await db.exec(`
480
+ CREATE TABLE IF NOT EXISTS interaction_events (
481
+ id TEXT PRIMARY KEY,
482
+ repository_id TEXT NOT NULL,
483
+ actor_id TEXT,
484
+ task_id TEXT,
485
+ session_id TEXT,
486
+ request_id TEXT NOT NULL,
487
+ tool_name TEXT NOT NULL,
488
+ phase TEXT NOT NULL,
489
+ outcome TEXT NOT NULL,
490
+ deterministic INTEGER NOT NULL DEFAULT 0,
491
+ evidence_json TEXT NOT NULL,
492
+ created_at INTEGER NOT NULL
493
+ );
494
+ CREATE INDEX IF NOT EXISTS idx_interaction_events_repo_created
495
+ ON interaction_events(repository_id, created_at);
496
+ CREATE INDEX IF NOT EXISTS idx_interaction_events_request
497
+ ON interaction_events(request_id);
498
+ `);
499
+ await db.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '7')");
500
+ }
372
501
  }
373
502
  /**
374
503
  * Compact the database — prune old data, reclaim disk space.
@@ -11,3 +11,7 @@ export { checkLocalPatterns, persistAndReinforce, getProjectStats } from './loca
11
11
  export type { ProjectStats } from './local-memory.js';
12
12
  export { recordContextEvent, recordModelUsage, setContextCacheRecord, getContextCacheRecord, listContextCacheRecords, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics } from './context-telemetry.js';
13
13
  export type { ContextEvent, ModelUsage, ContextCacheRecord, CheckpointMetric } from './context-telemetry.js';
14
+ export { recordInteractionEvidence, countInteractionEvidence, recordInteractionLesson, listLessons, listKnowledgeLessons, getApplicableLessons, transitionLesson, getRepositoryId } from './lessons.js';
15
+ export type { ApplicableLessons, InteractionEvidence, LessonRecord, LessonState, LessonVisibility } from './lessons.js';
16
+ export { loadTeamConfiguration, saveTeamConfiguration, initializeTeamSchema, getTeamModeStatus, doctorTeamConnection, syncTeamOutbox, searchTeamKnowledge, backfillTeamEmbeddings, TEAM_CONFIG_PATH, validateTeamDatabaseUrl, } from './team-store.js';
17
+ export type { TeamConfiguration, TeamSemanticConfiguration, TeamModeStatus, TeamDoctorResult, SemanticKnowledgeCandidate, SemanticKnowledgeResult, } from './team-store.js';
@@ -8,3 +8,5 @@ export { insertFindings, getFindingsForScan, getDeepFindings } from './findings.
8
8
  export { reinforcePattern, decayPatterns, getStrongPatterns, getPatterns, getHardRules } from './patterns.js';
9
9
  export { checkLocalPatterns, persistAndReinforce, getProjectStats } from './local-memory.js';
10
10
  export { recordContextEvent, recordModelUsage, setContextCacheRecord, getContextCacheRecord, listContextCacheRecords, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics } from './context-telemetry.js';
11
+ export { recordInteractionEvidence, countInteractionEvidence, recordInteractionLesson, listLessons, listKnowledgeLessons, getApplicableLessons, transitionLesson, getRepositoryId } from './lessons.js';
12
+ export { loadTeamConfiguration, saveTeamConfiguration, initializeTeamSchema, getTeamModeStatus, doctorTeamConnection, syncTeamOutbox, searchTeamKnowledge, backfillTeamEmbeddings, TEAM_CONFIG_PATH, validateTeamDatabaseUrl, } from './team-store.js';
@@ -0,0 +1,60 @@
1
+ export type LessonState = 'candidate' | 'validated' | 'promoted' | 'rejected' | 'superseded';
2
+ export type LessonVisibility = 'personal' | 'team';
3
+ export interface LessonRecord {
4
+ id: string;
5
+ repositoryId: string;
6
+ actorId?: string;
7
+ teamId?: string;
8
+ visibility: LessonVisibility;
9
+ state: LessonState;
10
+ kind: string;
11
+ subject: string;
12
+ evidence: Record<string, unknown>;
13
+ confidence: number;
14
+ source: string;
15
+ supersedesId?: string;
16
+ createdAt: number;
17
+ updatedAt: number;
18
+ repositoryName?: string;
19
+ }
20
+ export interface InteractionEvidence {
21
+ tool: string;
22
+ outcome: 'success' | 'error' | 'rejected';
23
+ requestId: string;
24
+ taskId?: string;
25
+ agentId?: string;
26
+ deterministic?: boolean;
27
+ verifiedOutcome?: boolean;
28
+ phase?: 'request' | 'response';
29
+ sessionId?: string;
30
+ files?: string[];
31
+ summary?: string;
32
+ /** A concise, evidence-backed proposition that could be reused beyond this call. */
33
+ reusableClaim?: string;
34
+ }
35
+ export declare function shouldValidateInteractionLesson(evidence: InteractionEvidence, verifiedSuccessfulOutcomes?: number): boolean;
36
+ export declare function recordInteractionEvidence(cwd: string, evidence: InteractionEvidence): Promise<string | null>;
37
+ export declare function countInteractionEvidence(cwd: string): Promise<number>;
38
+ export declare function getRepositoryId(cwd: string): Promise<string>;
39
+ export declare function recordInteractionLesson(cwd: string, evidence: InteractionEvidence): Promise<string | null>;
40
+ export declare function listLessons(cwd: string, limit?: number): Promise<LessonRecord[]>;
41
+ /**
42
+ * Returns the evidence visible to the current workspace for exploration. Cross-repository
43
+ * records are candidates only; getApplicableLessons remains the enforcement boundary.
44
+ */
45
+ export declare function listKnowledgeLessons(cwd: string, limit?: number): Promise<LessonRecord[]>;
46
+ export interface ApplicableLessons {
47
+ lessons: Array<LessonRecord & {
48
+ priority: number;
49
+ provenance: string;
50
+ }>;
51
+ conflicts: Array<{
52
+ subject: string;
53
+ lessonIds: string[];
54
+ }>;
55
+ }
56
+ export declare function getApplicableLessons(cwd: string, actorId?: string, teamId?: string): Promise<ApplicableLessons>;
57
+ export declare function transitionLesson(id: string, state: LessonState, options?: {
58
+ visibility?: LessonVisibility;
59
+ queueSync?: boolean;
60
+ }): Promise<boolean>;