@rigour-labs/core 6.0.0 → 6.1.0-beta.1
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/dist/context/automatic-index.d.ts +18 -0
- package/dist/context/automatic-index.js +142 -0
- package/dist/context/automatic-index.test.d.ts +1 -0
- package/dist/context/automatic-index.test.js +45 -0
- package/dist/context/cache-engine.d.ts +25 -0
- package/dist/context/dependency-graph.d.ts +13 -0
- package/dist/context/dependency-graph.js +97 -0
- package/dist/context/dependency-graph.test.d.ts +1 -0
- package/dist/context/dependency-graph.test.js +13 -0
- package/dist/context/index-bridge.js +21 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/pattern-index/embeddings.js +10 -3
- package/dist/pattern-index/indexer.d.ts +2 -0
- package/dist/pattern-index/indexer.js +72 -0
- package/dist/services/agent-history.d.ts +76 -0
- package/dist/services/agent-history.js +201 -0
- package/dist/services/agent-history.test.d.ts +1 -0
- package/dist/services/agent-history.test.js +69 -0
- package/dist/services/context-telemetry-service.d.ts +21 -0
- package/dist/services/context-telemetry-service.js +36 -7
- package/dist/services/context-telemetry-service.test.js +13 -0
- package/dist/services/engineering-knowledge-graph.d.ts +55 -0
- package/dist/services/engineering-knowledge-graph.js +177 -0
- package/dist/services/engineering-knowledge-graph.test.d.ts +1 -0
- package/dist/services/engineering-knowledge-graph.test.js +59 -0
- package/dist/services/model-pricing.d.ts +2 -0
- package/dist/services/model-pricing.js +2 -0
- package/dist/services/system-health.d.ts +34 -0
- package/dist/services/system-health.js +53 -0
- package/dist/storage/db.js +130 -1
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/lessons.d.ts +60 -0
- package/dist/storage/lessons.js +322 -0
- package/dist/storage/lessons.test.d.ts +1 -0
- package/dist/storage/lessons.test.js +50 -0
- package/dist/storage/local-encryption.d.ts +2 -0
- package/dist/storage/local-encryption.js +41 -0
- package/dist/storage/team-store.d.ts +75 -0
- package/dist/storage/team-store.js +321 -0
- package/dist/storage/team-store.test.d.ts +1 -0
- package/dist/storage/team-store.test.js +11 -0
- package/dist/storage/team-vector-store.d.ts +25 -0
- package/dist/storage/team-vector-store.js +183 -0
- package/dist/storage/team-vector-store.test.d.ts +1 -0
- package/dist/storage/team-vector-store.test.js +42 -0
- 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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
}
|
package/dist/storage/db.js
CHANGED
|
@@ -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 =
|
|
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.
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/storage/index.js
CHANGED
|
@@ -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>;
|