@kentwynn/kgraph 0.2.20 → 0.2.21

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.
@@ -4,6 +4,7 @@ import { loadConfig } from '../../config/config.js';
4
4
  import { queryContext } from '../../context/context-query.js';
5
5
  import { refreshKnowledgeAtomStatuses } from '../../knowledge/atom-store.js';
6
6
  import { getWorkingTreeChanges } from '../../scanner/git-utils.js';
7
+ import { shouldExclude } from '../../scanner/file-classifier.js';
7
8
  import { scanRepository } from '../../scanner/repo-scanner.js';
8
9
  import { listInboxNotes } from '../../storage/cognition-store.js';
9
10
  import { assertWorkspace, pathExists, resolveWorkspace, } from '../../storage/kgraph-paths.js';
@@ -74,7 +75,7 @@ export async function runDefaultWorkflow(query, options = {}) {
74
75
  const activeAtoms = atoms.filter((atom) => atom.status === 'active');
75
76
  const captureCheck = await buildCaptureCheck(workspace.rootPath, {
76
77
  topic,
77
- previousFiles: previousMaps.fileMap.files,
78
+ previousFiles: previousMaps.fileMap.files.filter((file) => !shouldExclude(file.path, config)),
78
79
  files: scan.files,
79
80
  atoms,
80
81
  });
@@ -107,7 +108,7 @@ export async function runDefaultWorkflow(query, options = {}) {
107
108
  if (options.final) {
108
109
  console.log('');
109
110
  renderFinalCaptureCheck(captureCheck, topic);
110
- if (captureCheck.required) {
111
+ if (captureCheck.required || captureCheck.unresolvedAtoms.length > 0) {
111
112
  process.exitCode = 1;
112
113
  }
113
114
  return;
@@ -146,6 +147,11 @@ async function buildCaptureCheck(rootPath, input) {
146
147
  return Number.isFinite(createdAt) && createdAt >= recentCutoff;
147
148
  });
148
149
  const invalidatedAtoms = matchingInvalidatedAtoms(input.atoms, input.topic).filter((atom) => !isInvalidatedAtomCovered(atom, recentActiveAtoms));
150
+ const unresolvedAtoms = input.atoms.filter((atom) => atom.status === 'needs-review' || atom.status === 'stale');
151
+ const reviewItems = unresolvedAtoms.map((atom) => ({
152
+ atom,
153
+ replacement: findReplacementAtom(atom, recentActiveAtoms),
154
+ }));
149
155
  const covered = new Set();
150
156
  for (const atom of recentActiveAtoms) {
151
157
  for (const ref of atom.evidenceRefs) {
@@ -165,32 +171,75 @@ async function buildCaptureCheck(rootPath, input) {
165
171
  changedFiles,
166
172
  coveredFiles: [...covered],
167
173
  invalidatedAtoms,
174
+ unresolvedAtoms,
175
+ reviewItems,
168
176
  };
169
177
  }
170
178
  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) {
179
+ return recentActiveAtoms.some((atom) => atomsOverlap(invalidated, atom));
180
+ }
181
+ function findReplacementAtom(invalidated, recentActiveAtoms) {
182
+ return recentActiveAtoms.find((atom) => atomsHaveReplacementSignal(invalidated, atom));
183
+ }
184
+ function atomsOverlap(a, b) {
185
+ const invalidatedFiles = new Set(a.scopeRefs.files);
186
+ const invalidatedSymbols = new Set(a.scopeRefs.symbols);
187
+ for (const ref of a.evidenceRefs) {
174
188
  if (ref.type === 'file')
175
189
  invalidatedFiles.add(ref.path);
176
190
  if (ref.type === 'symbol')
177
191
  invalidatedSymbols.add(ref.name);
178
192
  }
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
- });
193
+ const atomFiles = new Set(b.scopeRefs.files);
194
+ const atomSymbols = new Set(b.scopeRefs.symbols);
195
+ for (const ref of b.evidenceRefs) {
196
+ if (ref.type === 'file')
197
+ atomFiles.add(ref.path);
198
+ if (ref.type === 'symbol')
199
+ atomSymbols.add(ref.name);
200
+ }
201
+ const fileOverlap = [...invalidatedFiles].some((file) => atomFiles.has(file));
202
+ const symbolOverlap = [...invalidatedSymbols].some((symbol) => atomSymbols.has(symbol));
203
+ if (fileOverlap || symbolOverlap)
204
+ return true;
205
+ return tokenOverlap(a.topic, b.topic);
206
+ }
207
+ function atomsHaveReplacementSignal(a, b) {
208
+ const aSymbols = new Set(a.scopeRefs.symbols);
209
+ for (const ref of a.evidenceRefs) {
210
+ if (ref.type === 'symbol')
211
+ aSymbols.add(ref.name);
212
+ }
213
+ const bSymbols = new Set(b.scopeRefs.symbols);
214
+ for (const ref of b.evidenceRefs) {
215
+ if (ref.type === 'symbol')
216
+ bSymbols.add(ref.name);
217
+ }
218
+ const symbolOverlap = [...aSymbols].some((symbol) => bSymbols.has(symbol));
219
+ return symbolOverlap || meaningfulTopicOverlap(a.topic, b.topic);
220
+ }
221
+ function meaningfulTopicOverlap(a, b) {
222
+ const weakTokens = new Set([
223
+ 'add',
224
+ 'after',
225
+ 'behavior',
226
+ 'change',
227
+ 'changed',
228
+ 'new',
229
+ 'old',
230
+ 'review',
231
+ 'update',
232
+ 'with',
233
+ ]);
234
+ const aTokens = new Set(a
235
+ .toLowerCase()
236
+ .split(/[^a-z0-9]+/)
237
+ .filter((token) => token.length > 2 && !weakTokens.has(token)));
238
+ return b
239
+ .toLowerCase()
240
+ .split(/[^a-z0-9]+/)
241
+ .filter((token) => token.length > 2 && !weakTokens.has(token))
242
+ .some((token) => aTokens.has(token));
194
243
  }
195
244
  function tokenOverlap(a, b) {
196
245
  const aTokens = new Set(a
@@ -228,11 +277,19 @@ function matchingInvalidatedAtoms(atoms, topic) {
228
277
  }
229
278
  function renderFinalCaptureCheck(check, topic) {
230
279
  console.log('KGraph Final Check');
280
+ if (!check.required && check.unresolvedAtoms.length > 0) {
281
+ console.log(' status memory-review-required');
282
+ console.log(` unresolved ${check.unresolvedAtoms.length}`);
283
+ console.log(' conclusion stale or needs-review atoms remain');
284
+ renderMemoryReviewItems(check.reviewItems);
285
+ return;
286
+ }
231
287
  if (check.changedFiles.length === 0) {
232
288
  if (check.required) {
233
289
  console.log(' status capture-required');
234
290
  console.log(' changed files 0');
235
291
  console.log(` invalid atoms ${check.invalidatedAtoms.length}`);
292
+ renderMemoryReviewItems(check.reviewItems.filter((item) => check.invalidatedAtoms.some((atom) => atom.id === item.atom.id)));
236
293
  console.log(' conclusion missing for needs-review or stale knowledge');
237
294
  console.log(` next kgraph "${topic || '<topic>'}" --capture "<durable conclusion>" --capture-file <path>`);
238
295
  return;
@@ -251,7 +308,24 @@ function renderFinalCaptureCheck(check, topic) {
251
308
  console.log(` changed files ${check.changedFiles.length}`);
252
309
  if (check.invalidatedAtoms.length > 0) {
253
310
  console.log(` invalid atoms ${check.invalidatedAtoms.length}`);
311
+ renderMemoryReviewItems(check.reviewItems.filter((item) => check.invalidatedAtoms.some((atom) => atom.id === item.atom.id)));
254
312
  }
255
313
  console.log(' conclusion missing for one or more changed files');
256
314
  console.log(` next kgraph "${topic || '<topic>'}" --capture "<durable conclusion>" --capture-file <path>`);
257
315
  }
316
+ function renderMemoryReviewItems(items) {
317
+ const visible = items.slice(0, 3);
318
+ for (const item of visible) {
319
+ console.log(` review atom ${item.atom.id}`);
320
+ console.log(` review topic ${item.atom.status}: ${item.atom.topic}`);
321
+ if (item.replacement) {
322
+ console.log(` supersede kgraph knowledge supersede ${item.atom.id} ${item.replacement.id}`);
323
+ }
324
+ else {
325
+ console.log(` inspect kgraph knowledge get ${item.atom.id}`);
326
+ }
327
+ }
328
+ if (items.length > visible.length) {
329
+ console.log(` review more ${items.length - visible.length} more atom(s)`);
330
+ }
331
+ }
@@ -23,6 +23,7 @@ export const DEFAULT_CONFIG = {
23
23
  '.agents',
24
24
  '.specify',
25
25
  'specs',
26
+ 'tmp',
26
27
  '.cursor',
27
28
  '.claude',
28
29
  '.windsurf',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kentwynn/kgraph",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {