@kentwynn/kgraph 0.2.19 → 0.2.20
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.
|
@@ -2,7 +2,7 @@ import { refreshCognitionReferenceStatuses, updateCognition, } from '../../cogni
|
|
|
2
2
|
import { concludeTopic } from '../../cognition/conclusion.js';
|
|
3
3
|
import { loadConfig } from '../../config/config.js';
|
|
4
4
|
import { queryContext } from '../../context/context-query.js';
|
|
5
|
-
import {
|
|
5
|
+
import { refreshKnowledgeAtomStatuses } from '../../knowledge/atom-store.js';
|
|
6
6
|
import { getWorkingTreeChanges } from '../../scanner/git-utils.js';
|
|
7
7
|
import { scanRepository } from '../../scanner/repo-scanner.js';
|
|
8
8
|
import { listInboxNotes } from '../../storage/cognition-store.js';
|
|
@@ -59,10 +59,22 @@ export async function runDefaultWorkflow(query, options = {}) {
|
|
|
59
59
|
console.error(`Warning: ${warning}`);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
const
|
|
62
|
+
const refreshedAtoms = await refreshKnowledgeAtomStatuses(workspace, {
|
|
63
|
+
fileMap: {
|
|
64
|
+
generatedAt: new Date().toISOString(),
|
|
65
|
+
files: scan.files,
|
|
66
|
+
},
|
|
67
|
+
symbolMap: {
|
|
68
|
+
generatedAt: new Date().toISOString(),
|
|
69
|
+
symbols: scan.symbols,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
const atoms = refreshedAtoms.atoms;
|
|
63
73
|
const pendingInbox = await listInboxNotes(workspace);
|
|
64
74
|
const activeAtoms = atoms.filter((atom) => atom.status === 'active');
|
|
65
75
|
const captureCheck = await buildCaptureCheck(workspace.rootPath, {
|
|
76
|
+
topic,
|
|
77
|
+
previousFiles: previousMaps.fileMap.files,
|
|
66
78
|
files: scan.files,
|
|
67
79
|
atoms,
|
|
68
80
|
});
|
|
@@ -111,10 +123,21 @@ export async function runDefaultWorkflow(query, options = {}) {
|
|
|
111
123
|
}
|
|
112
124
|
async function buildCaptureCheck(rootPath, input) {
|
|
113
125
|
const knownFiles = new Set(input.files.map((file) => file.path));
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
const previousByPath = new Map(input.previousFiles.map((file) => [file.path, file]));
|
|
127
|
+
const currentPaths = new Set(input.files.map((file) => file.path));
|
|
128
|
+
const mapChangedFiles = input.files
|
|
129
|
+
.filter((file) => {
|
|
130
|
+
const previous = previousByPath.get(file.path);
|
|
131
|
+
return !previous || previous.contentHash !== file.contentHash;
|
|
132
|
+
})
|
|
133
|
+
.map((file) => file.path);
|
|
134
|
+
const deletedFiles = input.previousFiles
|
|
135
|
+
.filter((file) => !currentPaths.has(file.path))
|
|
136
|
+
.map((file) => file.path);
|
|
137
|
+
const gitChangedFiles = (await getWorkingTreeChanges(rootPath)).filter((file) => knownFiles.has(file) || previousByPath.has(file));
|
|
138
|
+
const changedFiles = [
|
|
139
|
+
...new Set([...mapChangedFiles, ...deletedFiles, ...gitChangedFiles]),
|
|
140
|
+
];
|
|
118
141
|
const recentCutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
119
142
|
const recentActiveAtoms = input.atoms.filter((atom) => {
|
|
120
143
|
if (atom.status !== 'active')
|
|
@@ -122,6 +145,7 @@ async function buildCaptureCheck(rootPath, input) {
|
|
|
122
145
|
const createdAt = Date.parse(atom.provenance.createdAt);
|
|
123
146
|
return Number.isFinite(createdAt) && createdAt >= recentCutoff;
|
|
124
147
|
});
|
|
148
|
+
const invalidatedAtoms = matchingInvalidatedAtoms(input.atoms, input.topic).filter((atom) => !isInvalidatedAtomCovered(atom, recentActiveAtoms));
|
|
125
149
|
const covered = new Set();
|
|
126
150
|
for (const atom of recentActiveAtoms) {
|
|
127
151
|
for (const ref of atom.evidenceRefs) {
|
|
@@ -136,16 +160,85 @@ async function buildCaptureCheck(rootPath, input) {
|
|
|
136
160
|
}
|
|
137
161
|
}
|
|
138
162
|
return {
|
|
139
|
-
required: changedFiles.some((file) => !covered.has(file))
|
|
163
|
+
required: changedFiles.some((file) => !covered.has(file)) ||
|
|
164
|
+
invalidatedAtoms.length > 0,
|
|
140
165
|
changedFiles,
|
|
141
166
|
coveredFiles: [...covered],
|
|
167
|
+
invalidatedAtoms,
|
|
142
168
|
};
|
|
143
169
|
}
|
|
170
|
+
function isInvalidatedAtomCovered(invalidated, recentActiveAtoms) {
|
|
171
|
+
const invalidatedFiles = new Set(invalidated.scopeRefs.files);
|
|
172
|
+
const invalidatedSymbols = new Set(invalidated.scopeRefs.symbols);
|
|
173
|
+
for (const ref of invalidated.evidenceRefs) {
|
|
174
|
+
if (ref.type === 'file')
|
|
175
|
+
invalidatedFiles.add(ref.path);
|
|
176
|
+
if (ref.type === 'symbol')
|
|
177
|
+
invalidatedSymbols.add(ref.name);
|
|
178
|
+
}
|
|
179
|
+
return recentActiveAtoms.some((atom) => {
|
|
180
|
+
const atomFiles = new Set(atom.scopeRefs.files);
|
|
181
|
+
const atomSymbols = new Set(atom.scopeRefs.symbols);
|
|
182
|
+
for (const ref of atom.evidenceRefs) {
|
|
183
|
+
if (ref.type === 'file')
|
|
184
|
+
atomFiles.add(ref.path);
|
|
185
|
+
if (ref.type === 'symbol')
|
|
186
|
+
atomSymbols.add(ref.name);
|
|
187
|
+
}
|
|
188
|
+
const fileOverlap = [...invalidatedFiles].some((file) => atomFiles.has(file));
|
|
189
|
+
const symbolOverlap = [...invalidatedSymbols].some((symbol) => atomSymbols.has(symbol));
|
|
190
|
+
if (fileOverlap || symbolOverlap)
|
|
191
|
+
return true;
|
|
192
|
+
return tokenOverlap(invalidated.topic, atom.topic);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function tokenOverlap(a, b) {
|
|
196
|
+
const aTokens = new Set(a
|
|
197
|
+
.toLowerCase()
|
|
198
|
+
.split(/[^a-z0-9]+/)
|
|
199
|
+
.filter(Boolean));
|
|
200
|
+
return b
|
|
201
|
+
.toLowerCase()
|
|
202
|
+
.split(/[^a-z0-9]+/)
|
|
203
|
+
.filter(Boolean)
|
|
204
|
+
.some((token) => aTokens.has(token));
|
|
205
|
+
}
|
|
206
|
+
function matchingInvalidatedAtoms(atoms, topic) {
|
|
207
|
+
const tokens = new Set((topic ?? '')
|
|
208
|
+
.toLowerCase()
|
|
209
|
+
.split(/[^a-z0-9]+/)
|
|
210
|
+
.filter(Boolean));
|
|
211
|
+
return atoms.filter((atom) => {
|
|
212
|
+
if (atom.status !== 'needs-review' && atom.status !== 'stale')
|
|
213
|
+
return false;
|
|
214
|
+
if (tokens.size === 0)
|
|
215
|
+
return true;
|
|
216
|
+
const haystack = [
|
|
217
|
+
atom.topic,
|
|
218
|
+
atom.claim,
|
|
219
|
+
atom.summary,
|
|
220
|
+
...atom.scopeRefs.files,
|
|
221
|
+
...atom.scopeRefs.symbols,
|
|
222
|
+
]
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.join(' ')
|
|
225
|
+
.toLowerCase();
|
|
226
|
+
return [...tokens].some((token) => haystack.includes(token));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
144
229
|
function renderFinalCaptureCheck(check, topic) {
|
|
145
230
|
console.log('KGraph Final Check');
|
|
146
231
|
if (check.changedFiles.length === 0) {
|
|
232
|
+
if (check.required) {
|
|
233
|
+
console.log(' status capture-required');
|
|
234
|
+
console.log(' changed files 0');
|
|
235
|
+
console.log(` invalid atoms ${check.invalidatedAtoms.length}`);
|
|
236
|
+
console.log(' conclusion missing for needs-review or stale knowledge');
|
|
237
|
+
console.log(` next kgraph "${topic || '<topic>'}" --capture "<durable conclusion>" --capture-file <path>`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
147
240
|
console.log(' status clean');
|
|
148
|
-
console.log(' reason no mapped repo files changed');
|
|
241
|
+
console.log(' reason no mapped repo files changed or invalidated matching atoms');
|
|
149
242
|
return;
|
|
150
243
|
}
|
|
151
244
|
if (!check.required) {
|
|
@@ -156,6 +249,9 @@ function renderFinalCaptureCheck(check, topic) {
|
|
|
156
249
|
}
|
|
157
250
|
console.log(' status capture-required');
|
|
158
251
|
console.log(` changed files ${check.changedFiles.length}`);
|
|
252
|
+
if (check.invalidatedAtoms.length > 0) {
|
|
253
|
+
console.log(` invalid atoms ${check.invalidatedAtoms.length}`);
|
|
254
|
+
}
|
|
159
255
|
console.log(' conclusion missing for one or more changed files');
|
|
160
256
|
console.log(` next kgraph "${topic || '<topic>'}" --capture "<durable conclusion>" --capture-file <path>`);
|
|
161
257
|
}
|
|
@@ -209,6 +209,8 @@ export async function validateKnowledgeStore(workspace, maps) {
|
|
|
209
209
|
const symbolNames = new Set(maps.symbolMap.symbols.map((symbol) => symbol.name));
|
|
210
210
|
const symbolIds = new Set(maps.symbolMap.symbols.map((symbol) => symbol.id));
|
|
211
211
|
for (const atom of atoms) {
|
|
212
|
+
if (atom.status === 'archived')
|
|
213
|
+
continue;
|
|
212
214
|
for (const ref of atom.evidenceRefs) {
|
|
213
215
|
if (ref.type === 'file') {
|
|
214
216
|
const file = fileByPath.get(ref.path);
|