@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 { readKnowledgeAtoms } from '../../knowledge/atom-store.js';
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 atoms = await readKnowledgeAtoms(workspace);
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 changedFiles = (await getWorkingTreeChanges(rootPath)).filter((file) => knownFiles.has(file));
115
- if (changedFiles.length === 0) {
116
- return { required: false, changedFiles, coveredFiles: [] };
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kentwynn/kgraph",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {