memorix 1.1.13 → 1.2.0
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/CHANGELOG.md +19 -0
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/dist/cli/index.js +36313 -31189
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +50 -30
- package/dist/index.js +5348 -674
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +3661 -293
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +19 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +5346 -672
- package/dist/sdk.js.map +1 -1
- package/docs/1.2.0-CLAIM-LEDGER.md +72 -0
- package/docs/1.2.0-CODE-STATE.md +61 -0
- package/docs/1.2.0-DEVELOPMENT-CHARTER.md +255 -0
- package/docs/1.2.0-DYNAMIC-LIFECYCLE.md +71 -0
- package/docs/1.2.0-EVALUATION-HARNESS.md +51 -0
- package/docs/1.2.0-IMPLEMENTATION-PLAN.md +554 -0
- package/docs/1.2.0-KNOWLEDGE-WORKFLOW-RESEARCH.md +205 -0
- package/docs/1.2.0-KNOWLEDGE-WORKSPACE.md +68 -0
- package/docs/1.2.0-PRODUCT-STORY.md +234 -0
- package/docs/1.2.0-PROVIDER-QUALITY.md +189 -0
- package/docs/1.2.0-WORKFLOW-INHERITANCE.md +101 -0
- package/docs/1.2.0-WORKSET-RETRIEVAL.md +80 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +6 -3
- package/docs/API_REFERENCE.md +25 -6
- package/docs/CONFIGURATION.md +21 -3
- package/docs/README.md +17 -2
- package/docs/dev-log/progress.txt +120 -40
- package/llms-full.txt +16 -2
- package/llms.txt +9 -4
- package/package.json +3 -2
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/capability-map.ts +1 -0
- package/src/cli/commands/codegraph.ts +112 -9
- package/src/cli/commands/context.ts +2 -0
- package/src/cli/commands/doctor.ts +73 -4
- package/src/cli/commands/knowledge.ts +282 -0
- package/src/cli/commands/serve-http.ts +12 -1
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/App.tsx +1 -1
- package/src/cli/tui/Panels.tsx +8 -8
- package/src/cli/tui/theme.ts +1 -1
- package/src/cli/tui/views/GraphView.tsx +8 -7
- package/src/cli/tui/views/KnowledgeView.tsx +9 -9
- package/src/codegraph/auto-context.ts +171 -9
- package/src/codegraph/code-state.ts +95 -0
- package/src/codegraph/context-pack.ts +82 -1
- package/src/codegraph/external-provider.ts +581 -0
- package/src/codegraph/lite-provider.ts +64 -19
- package/src/codegraph/project-context.ts +9 -1
- package/src/codegraph/store.ts +154 -6
- package/src/codegraph/types.ts +117 -0
- package/src/config/resolved-config.ts +28 -0
- package/src/config/toml-loader.ts +3 -0
- package/src/config/yaml-loader.ts +6 -0
- package/src/dashboard/server.ts +15 -1
- package/src/evaluation/workset-evaluation.ts +120 -0
- package/src/hooks/handler.ts +48 -6
- package/src/knowledge/claim-store.ts +267 -0
- package/src/knowledge/claims.ts +537 -0
- package/src/knowledge/markdown.ts +129 -0
- package/src/knowledge/types.ts +157 -0
- package/src/knowledge/wiki.ts +524 -0
- package/src/knowledge/workflow-store.ts +168 -0
- package/src/knowledge/workflow-types.ts +95 -0
- package/src/knowledge/workflows.ts +743 -0
- package/src/knowledge/workset.ts +515 -0
- package/src/knowledge/workspace-store.ts +220 -0
- package/src/knowledge/workspace-types.ts +106 -0
- package/src/knowledge/workspace.ts +220 -0
- package/src/memory/observations.ts +19 -0
- package/src/runtime/control-plane-maintenance.ts +5 -0
- package/src/runtime/isolated-maintenance.ts +5 -0
- package/src/runtime/lifecycle-status.ts +102 -0
- package/src/runtime/lifecycle.ts +107 -0
- package/src/runtime/maintenance-jobs.ts +5 -0
- package/src/runtime/project-maintenance.ts +190 -0
- package/src/server/tool-profile.ts +3 -2
- package/src/server.ts +354 -14
- package/src/store/file-lock.ts +24 -4
- package/src/store/sqlite-db.ts +307 -0
- package/src/wiki/generator.ts +4 -2
- package/src/wiki/knowledge-graph.ts +7 -4
- package/src/wiki/types.ts +16 -4
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { existsSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getResolvedConfig } from '../config/resolved-config.js';
|
|
4
|
+
import { buildTaskWorkset, type TaskWorkset, type WorksetCaution } from '../knowledge/workset.js';
|
|
4
5
|
import type { ProjectInfo } from '../types.js';
|
|
5
6
|
import { backfillMissingObservationCodeRefs, type CodeRefBackfillResult } from './binder.js';
|
|
6
7
|
import { collectCurrentProjectFacts, type CurrentProjectFacts } from './current-facts.js';
|
|
7
8
|
import { refreshProjectLite } from './lite-provider.js';
|
|
9
|
+
import {
|
|
10
|
+
getExternalCodeGraphContext,
|
|
11
|
+
inspectExternalCodeGraph,
|
|
12
|
+
type ExternalCodeGraphRunner,
|
|
13
|
+
} from './external-provider.js';
|
|
14
|
+
import type { CodeGraphProviderQuality, ExternalCodeGraphOutline } from './types.js';
|
|
8
15
|
import {
|
|
9
16
|
buildProjectContextExplain,
|
|
10
17
|
type ProjectContextExplain,
|
|
@@ -40,6 +47,8 @@ export interface AutoProjectContext {
|
|
|
40
47
|
overview: ProjectContextOverview;
|
|
41
48
|
explain: ProjectContextExplain;
|
|
42
49
|
refresh: AutoContextRefreshResult;
|
|
50
|
+
providerQuality: CodeGraphProviderQuality;
|
|
51
|
+
workset: TaskWorkset;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
export interface AutoProjectBrief {
|
|
@@ -102,6 +111,8 @@ export async function buildAutoProjectContext(input: {
|
|
|
102
111
|
now?: Date;
|
|
103
112
|
exclude?: string[];
|
|
104
113
|
maxFileBytes?: number;
|
|
114
|
+
/** Test-only injection point; production uses the bounded local runner. */
|
|
115
|
+
externalRunner?: ExternalCodeGraphRunner;
|
|
105
116
|
/**
|
|
106
117
|
* When supplied, a needed refresh is queued instead of running in this
|
|
107
118
|
* request. MCP and hook callers use this to keep their response path fast.
|
|
@@ -161,6 +172,17 @@ export async function buildAutoProjectContext(input: {
|
|
|
161
172
|
store,
|
|
162
173
|
activeProjectObservations(input.observations, input.project.id) as any,
|
|
163
174
|
);
|
|
175
|
+
try {
|
|
176
|
+
const { enqueueClaimRequalification } = await import('../runtime/lifecycle.js');
|
|
177
|
+
enqueueClaimRequalification({
|
|
178
|
+
dataDir: input.dataDir,
|
|
179
|
+
projectId: input.project.id,
|
|
180
|
+
source: 'foreground-refresh',
|
|
181
|
+
snapshotId: store.latestSnapshot(input.project.id)?.id,
|
|
182
|
+
});
|
|
183
|
+
} catch {
|
|
184
|
+
// The completed scan remains useful even if its later maintenance cannot queue.
|
|
185
|
+
}
|
|
164
186
|
refresh = { ...refresh, backfill };
|
|
165
187
|
} catch (error) {
|
|
166
188
|
refresh = {
|
|
@@ -180,15 +202,116 @@ export async function buildAutoProjectContext(input: {
|
|
|
180
202
|
exclude,
|
|
181
203
|
});
|
|
182
204
|
const overview = explain.overview;
|
|
205
|
+
const currentFacts = collectCurrentProjectFacts({ project: input.project, now });
|
|
206
|
+
const latestSnapshot = overview.code.latestSnapshot;
|
|
207
|
+
const sourceSets = lensSourceSets({ task, lens, explain });
|
|
208
|
+
let externalOutline: ExternalCodeGraphOutline | undefined;
|
|
209
|
+
let externalCaution: string | undefined;
|
|
210
|
+
let providerQuality: CodeGraphProviderQuality;
|
|
211
|
+
if (task) {
|
|
212
|
+
const external = await getExternalCodeGraphContext({
|
|
213
|
+
projectRoot: input.project.rootPath,
|
|
214
|
+
task,
|
|
215
|
+
exclude,
|
|
216
|
+
mode: codegraphConfig.externalContext,
|
|
217
|
+
command: codegraphConfig.externalCommand,
|
|
218
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
219
|
+
...(input.externalRunner ? { runner: input.externalRunner } : {}),
|
|
220
|
+
});
|
|
221
|
+
externalOutline = external.outline;
|
|
222
|
+
externalCaution = external.caution;
|
|
223
|
+
providerQuality = external.quality;
|
|
224
|
+
} else {
|
|
225
|
+
const external = await inspectExternalCodeGraph({
|
|
226
|
+
projectRoot: input.project.rootPath,
|
|
227
|
+
mode: codegraphConfig.externalContext,
|
|
228
|
+
command: codegraphConfig.externalCommand,
|
|
229
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
230
|
+
...(input.externalRunner ? { runner: input.externalRunner } : {}),
|
|
231
|
+
});
|
|
232
|
+
providerQuality = external.quality;
|
|
233
|
+
}
|
|
234
|
+
const externalStartHere = externalOutline
|
|
235
|
+
? [...externalOutline.relatedFiles, ...externalOutline.entryPoints.map(entry => entry.path)]
|
|
236
|
+
: [];
|
|
237
|
+
const startHere = [...new Set([
|
|
238
|
+
...externalStartHere,
|
|
239
|
+
...rankLensPaths([
|
|
240
|
+
...existingLensCandidates(input.project.rootPath, lens),
|
|
241
|
+
...overview.suggestedReads,
|
|
242
|
+
], lens, task),
|
|
243
|
+
])].slice(0, 5);
|
|
244
|
+
const runtimeCautions: WorksetCaution[] = [];
|
|
245
|
+
if (refresh.reason === 'queued') {
|
|
246
|
+
runtimeCautions.push({ kind: 'codegraph-refresh-queued', message: refresh.message });
|
|
247
|
+
} else if (refresh.reason === 'failed') {
|
|
248
|
+
runtimeCautions.push({ kind: 'codegraph-refresh-failed', message: refresh.message });
|
|
249
|
+
}
|
|
250
|
+
if (externalCaution) {
|
|
251
|
+
runtimeCautions.push({ kind: 'external-codegraph-fallback', message: externalCaution });
|
|
252
|
+
}
|
|
253
|
+
const workset = await buildTaskWorkset({
|
|
254
|
+
projectId: input.project.id,
|
|
255
|
+
dataDir: input.dataDir,
|
|
256
|
+
...(task ? { task } : {}),
|
|
257
|
+
lens: lens.id,
|
|
258
|
+
startHere,
|
|
259
|
+
...(externalOutline ? { semanticCode: externalOutline } : {}),
|
|
260
|
+
providerQuality,
|
|
261
|
+
currentFacts: worksetFactLines(currentFacts),
|
|
262
|
+
codeState: codeStateLine(overview),
|
|
263
|
+
reliableMemory: sourceSets.reliableSources
|
|
264
|
+
.slice(0, lens.sourceLimit)
|
|
265
|
+
.map(source => ({
|
|
266
|
+
id: source.observationId,
|
|
267
|
+
title: source.title,
|
|
268
|
+
type: source.type,
|
|
269
|
+
status: source.status,
|
|
270
|
+
...(source.path ? { path: source.path } : {}),
|
|
271
|
+
...(source.symbol ? { symbol: source.symbol } : {}),
|
|
272
|
+
})),
|
|
273
|
+
cautionMemory: sourceSets.cautionSources
|
|
274
|
+
.slice(0, lens.cautionLimit)
|
|
275
|
+
.map(source => ({
|
|
276
|
+
id: source.observationId,
|
|
277
|
+
title: source.title,
|
|
278
|
+
type: source.type,
|
|
279
|
+
status: source.status,
|
|
280
|
+
...(source.path ? { path: source.path } : {}),
|
|
281
|
+
...(source.symbol ? { symbol: source.symbol } : {}),
|
|
282
|
+
})),
|
|
283
|
+
hiddenCautionMemoryCount: sourceSets.hiddenCautionCount,
|
|
284
|
+
verificationHints: lensVerificationHints(lens),
|
|
285
|
+
worktreeDirty: currentFacts.git.dirty,
|
|
286
|
+
...(latestSnapshot
|
|
287
|
+
? {
|
|
288
|
+
snapshot: {
|
|
289
|
+
id: latestSnapshot.id,
|
|
290
|
+
sourceEpoch: latestSnapshot.sourceEpoch,
|
|
291
|
+
worktreeState: latestSnapshot.worktreeState,
|
|
292
|
+
incomplete: latestSnapshot.completeness.skippedOversizedFiles > 0
|
|
293
|
+
|| (latestSnapshot.completeness.unreadableFiles ?? 0) > 0
|
|
294
|
+
|| latestSnapshot.completeness.removalScanDeferred,
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
: {}),
|
|
298
|
+
freshness: {
|
|
299
|
+
suspect: overview.freshness.suspect,
|
|
300
|
+
stale: overview.freshness.stale,
|
|
301
|
+
},
|
|
302
|
+
runtimeCautions,
|
|
303
|
+
});
|
|
183
304
|
|
|
184
305
|
return {
|
|
185
306
|
project: input.project,
|
|
186
307
|
...(task ? { task } : {}),
|
|
187
308
|
lens,
|
|
188
|
-
currentFacts
|
|
309
|
+
currentFacts,
|
|
189
310
|
overview,
|
|
190
311
|
explain,
|
|
191
312
|
refresh,
|
|
313
|
+
providerQuality,
|
|
314
|
+
workset,
|
|
192
315
|
};
|
|
193
316
|
}
|
|
194
317
|
|
|
@@ -198,6 +321,22 @@ function formatLanguages(overview: ProjectContextOverview): string {
|
|
|
198
321
|
: 'none indexed yet';
|
|
199
322
|
}
|
|
200
323
|
|
|
324
|
+
function codeStateLine(overview: ProjectContextOverview): string {
|
|
325
|
+
const snapshot = overview.code.latestSnapshot;
|
|
326
|
+
if (!snapshot) return '- Code state: no completed snapshot yet';
|
|
327
|
+
const revision = snapshot.baseRevision ? snapshot.baseRevision.slice(0, 12) : 'Git unavailable';
|
|
328
|
+
const scanState = snapshot.completeness.skippedOversizedFiles > 0
|
|
329
|
+
|| (snapshot.completeness.unreadableFiles ?? 0) > 0
|
|
330
|
+
|| snapshot.completeness.removalScanDeferred
|
|
331
|
+
? 'incomplete scan'
|
|
332
|
+
: 'complete scan';
|
|
333
|
+
return '- Code state: ' + revision
|
|
334
|
+
+ ', ' + snapshot.worktreeState + ' worktree'
|
|
335
|
+
+ ', ' + snapshot.changedPathCount + ' changed path(s)'
|
|
336
|
+
+ ', epoch ' + snapshot.sourceEpoch
|
|
337
|
+
+ ', ' + scanState;
|
|
338
|
+
}
|
|
339
|
+
|
|
201
340
|
function dedupeSourcesByObservation(
|
|
202
341
|
sources: ProjectContextExplain['sources'],
|
|
203
342
|
): ProjectContextExplain['sources'] {
|
|
@@ -228,18 +367,14 @@ function existingLensCandidates(rootPath: string, lens: TaskLens): string[] {
|
|
|
228
367
|
}
|
|
229
368
|
|
|
230
369
|
function rankedStartHere(context: AutoProjectContext, limit = 8): string[] {
|
|
231
|
-
|
|
232
|
-
...existingLensCandidates(context.project.rootPath, context.lens),
|
|
233
|
-
...context.overview.suggestedReads,
|
|
234
|
-
];
|
|
235
|
-
return rankLensPaths(candidates, context.lens, context.task).slice(0, limit);
|
|
370
|
+
return context.workset.startHere.slice(0, limit);
|
|
236
371
|
}
|
|
237
372
|
|
|
238
373
|
function lensLine(context: AutoProjectContext): string {
|
|
239
374
|
return `Task lens: ${context.lens.id} - ${context.lens.description}`;
|
|
240
375
|
}
|
|
241
376
|
|
|
242
|
-
function lensSourceSets(context: AutoProjectContext): {
|
|
377
|
+
function lensSourceSets(context: Pick<AutoProjectContext, 'task' | 'lens' | 'explain'>): {
|
|
243
378
|
reliableSources: ProjectContextExplain['sources'];
|
|
244
379
|
cautionSources: ProjectContextExplain['sources'];
|
|
245
380
|
hiddenReliableCount: number;
|
|
@@ -275,7 +410,7 @@ export function buildAutoProjectBrief(context: AutoProjectContext): AutoProjectB
|
|
|
275
410
|
return {
|
|
276
411
|
lens: context.lens.id,
|
|
277
412
|
lensDescription: context.lens.description,
|
|
278
|
-
startHere:
|
|
413
|
+
startHere: context.workset.startHere,
|
|
279
414
|
reliableMemoryIds: reliableSources
|
|
280
415
|
.slice(0, context.lens.sourceLimit)
|
|
281
416
|
.map(source => source.observationId),
|
|
@@ -284,7 +419,7 @@ export function buildAutoProjectBrief(context: AutoProjectContext): AutoProjectB
|
|
|
284
419
|
.map(source => source.observationId),
|
|
285
420
|
hiddenReliableCount,
|
|
286
421
|
hiddenCautionCount,
|
|
287
|
-
suggestedVerification:
|
|
422
|
+
suggestedVerification: context.workset.verification,
|
|
288
423
|
};
|
|
289
424
|
}
|
|
290
425
|
|
|
@@ -322,6 +457,27 @@ function formatCurrentFactsLines(facts: CurrentProjectFacts): string[] {
|
|
|
322
457
|
return lines;
|
|
323
458
|
}
|
|
324
459
|
|
|
460
|
+
function worksetFactLines(facts: CurrentProjectFacts): string[] {
|
|
461
|
+
const lines: string[] = [];
|
|
462
|
+
if (facts.packageVersion) lines.push('Package version: ' + facts.packageVersion);
|
|
463
|
+
if (facts.latestChangelog) {
|
|
464
|
+
lines.push('Latest changelog: ' + facts.latestChangelog.version
|
|
465
|
+
+ (facts.latestChangelog.date ? ' (' + facts.latestChangelog.date + ')' : ''));
|
|
466
|
+
}
|
|
467
|
+
const gitParts: string[] = [];
|
|
468
|
+
if (facts.git.branch) gitParts.push('branch ' + facts.git.branch);
|
|
469
|
+
if (facts.git.commit) gitParts.push('commit ' + facts.git.commit);
|
|
470
|
+
gitParts.push(facts.git.dirty ? 'dirty worktree' : 'clean worktree');
|
|
471
|
+
lines.push('Git: ' + gitParts.join(', '));
|
|
472
|
+
for (const note of facts.staleNotes.slice(0, 1)) {
|
|
473
|
+
lines.push(
|
|
474
|
+
'Historical note: ' + note.path
|
|
475
|
+
+ (note.branchHint ? ' (branch hint ' + note.branchHint + '; ' + note.reason + ')' : ' (' + note.reason + ')'),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return lines;
|
|
479
|
+
}
|
|
480
|
+
|
|
325
481
|
export function formatAutoProjectContextSummary(context: AutoProjectContext): string {
|
|
326
482
|
const reliableSources = rankLensSources(
|
|
327
483
|
dedupeSourcesByObservation(context.explain.sources.filter(source => source.status === 'current')),
|
|
@@ -337,6 +493,7 @@ export function formatAutoProjectContextSummary(context: AutoProjectContext): st
|
|
|
337
493
|
...formatCurrentFactsLines(context.currentFacts),
|
|
338
494
|
'',
|
|
339
495
|
`- Code memory: ${context.overview.code.files} files / ${context.overview.code.symbols} symbols / ${context.overview.code.refs} memory links`,
|
|
496
|
+
`- Code provider: ${context.providerQuality.selected} (${context.providerQuality.selectedQuality})`,
|
|
340
497
|
`- Languages: ${formatLanguages(context.overview)}`,
|
|
341
498
|
`- Memories: ${context.overview.memory.active} active / ${context.overview.memory.total} total`,
|
|
342
499
|
`- Freshness: ${context.overview.freshness.current} current, ${context.overview.freshness.suspect} suspect, ${context.overview.freshness.stale} stale`,
|
|
@@ -363,6 +520,10 @@ export function formatAutoProjectContextSummary(context: AutoProjectContext): st
|
|
|
363
520
|
}
|
|
364
521
|
|
|
365
522
|
export function formatAutoProjectContextPrompt(context: AutoProjectContext): string {
|
|
523
|
+
return context.workset.prompt;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function formatLegacyAutoProjectContextPrompt(context: AutoProjectContext): string {
|
|
366
527
|
const lines = [
|
|
367
528
|
`Memorix Autopilot Brief for ${context.project.name}`,
|
|
368
529
|
context.task ? `Task: ${context.task}` : '',
|
|
@@ -371,6 +532,7 @@ export function formatAutoProjectContextPrompt(context: AutoProjectContext): str
|
|
|
371
532
|
...formatCurrentFactsLines(context.currentFacts),
|
|
372
533
|
'',
|
|
373
534
|
'Project state',
|
|
535
|
+
codeStateLine(context.overview),
|
|
374
536
|
`- Code memory: ${context.overview.code.files} files, ${context.overview.code.symbols} symbols, ${context.overview.code.refs} memory links`,
|
|
375
537
|
`- Languages: ${formatLanguages(context.overview)}`,
|
|
376
538
|
`- Memories: ${context.overview.memory.active} active / ${context.overview.memory.total} total`,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import type {
|
|
5
|
+
CodeGraphProviderKind,
|
|
6
|
+
CodeStateScanCompleteness,
|
|
7
|
+
CodeStateSnapshotInput,
|
|
8
|
+
CodeStateWorktreeState,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
const GIT_TIMEOUT_MS = 3_000;
|
|
13
|
+
const GIT_MAX_BUFFER_BYTES = 256 * 1024;
|
|
14
|
+
|
|
15
|
+
interface GitResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
output: Buffer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runGit(rootPath: string, args: string[]): Promise<GitResult> {
|
|
21
|
+
try {
|
|
22
|
+
const result = await execFileAsync('git', ['-C', rootPath, ...args], {
|
|
23
|
+
encoding: 'buffer',
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
timeout: GIT_TIMEOUT_MS,
|
|
26
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
27
|
+
});
|
|
28
|
+
const output = Buffer.isBuffer(result.stdout)
|
|
29
|
+
? result.stdout
|
|
30
|
+
: Buffer.from(result.stdout ?? '');
|
|
31
|
+
return { ok: true, output };
|
|
32
|
+
} catch {
|
|
33
|
+
return { ok: false, output: Buffer.alloc(0) };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function changedPathCount(status: Buffer): number {
|
|
38
|
+
const entries = status.toString('utf8').split('\0');
|
|
39
|
+
let count = 0;
|
|
40
|
+
for (let index = 0; index < entries.length; index++) {
|
|
41
|
+
const entry = entries[index];
|
|
42
|
+
if (!entry) continue;
|
|
43
|
+
count++;
|
|
44
|
+
const code = entry.slice(0, 2);
|
|
45
|
+
if (code.includes('R') || code.includes('C')) index++;
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function fingerprint(baseRevision: string | undefined, status: Buffer): string {
|
|
51
|
+
return createHash('sha256')
|
|
52
|
+
.update(baseRevision ?? '')
|
|
53
|
+
.update('\0')
|
|
54
|
+
.update(status)
|
|
55
|
+
.digest('hex');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CollectCodeStateInput {
|
|
59
|
+
projectId: string;
|
|
60
|
+
projectRoot: string;
|
|
61
|
+
provider: CodeGraphProviderKind;
|
|
62
|
+
indexedAt: string;
|
|
63
|
+
completeness: CodeStateScanCompleteness;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Capture bounded Git metadata for a completed scan. No source file contents
|
|
68
|
+
* are copied into the snapshot, and an unavailable Git executable is explicit
|
|
69
|
+
* instead of being treated as a clean worktree.
|
|
70
|
+
*/
|
|
71
|
+
export async function collectCodeStateSnapshot(
|
|
72
|
+
input: CollectCodeStateInput,
|
|
73
|
+
): Promise<CodeStateSnapshotInput> {
|
|
74
|
+
const [revision, status] = await Promise.all([
|
|
75
|
+
runGit(input.projectRoot, ['rev-parse', 'HEAD']),
|
|
76
|
+
runGit(input.projectRoot, ['status', '--porcelain=v1', '-z', '--untracked-files=normal']),
|
|
77
|
+
]);
|
|
78
|
+
const baseRevision = revision.ok
|
|
79
|
+
? revision.output.toString('utf8').trim() || undefined
|
|
80
|
+
: undefined;
|
|
81
|
+
const worktreeState: CodeStateWorktreeState = status.ok
|
|
82
|
+
? (status.output.length > 0 ? 'dirty' : 'clean')
|
|
83
|
+
: 'unavailable';
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
projectId: input.projectId,
|
|
87
|
+
provider: input.provider,
|
|
88
|
+
...(baseRevision ? { baseRevision } : {}),
|
|
89
|
+
worktreeFingerprint: fingerprint(baseRevision, status.ok ? status.output : Buffer.alloc(0)),
|
|
90
|
+
worktreeState,
|
|
91
|
+
changedPathCount: status.ok ? changedPathCount(status.output) : 0,
|
|
92
|
+
indexedAt: input.indexedAt,
|
|
93
|
+
completeness: input.completeness,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { evaluateCodeRefFreshness } from './freshness.js';
|
|
2
|
-
import
|
|
2
|
+
import { buildTaskWorkset, type TaskWorkset, type WorksetCaution } from '../knowledge/workset.js';
|
|
3
|
+
import type {
|
|
4
|
+
CodeFile,
|
|
5
|
+
CodeGraphProviderQuality,
|
|
6
|
+
CodeRefStatus,
|
|
7
|
+
CodeSymbol,
|
|
8
|
+
ExternalCodeGraphOutline,
|
|
9
|
+
ObservationCodeRef,
|
|
10
|
+
} from './types.js';
|
|
3
11
|
import type { CodeGraphStore } from './store.js';
|
|
4
12
|
import { isCodeGraphExcludedPath } from './exclude.js';
|
|
5
13
|
|
|
@@ -9,6 +17,8 @@ export interface ContextPackMemory {
|
|
|
9
17
|
type: string;
|
|
10
18
|
status: CodeRefStatus | 'unbound';
|
|
11
19
|
reason: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
symbol?: string;
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
export interface ContextPackCodeFact {
|
|
@@ -32,6 +42,8 @@ export interface ContextPack {
|
|
|
32
42
|
warnings: ContextPackWarning[];
|
|
33
43
|
suggestedReads: string[];
|
|
34
44
|
suggestedVerification: string[];
|
|
45
|
+
/** 1.2 bounded task selection, added without removing legacy pack fields. */
|
|
46
|
+
workset?: TaskWorkset;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
export interface ContextPackObservation {
|
|
@@ -181,6 +193,8 @@ export function assembleContextPack(input: AssembleContextPackInput): ContextPac
|
|
|
181
193
|
type: observation.type,
|
|
182
194
|
status: freshness.status,
|
|
183
195
|
reason: freshness.reason,
|
|
196
|
+
...(file ? { path: file.path } : {}),
|
|
197
|
+
...(symbol ? { symbol: symbol.name } : {}),
|
|
184
198
|
});
|
|
185
199
|
}
|
|
186
200
|
if (file) suggestedReads.push(file.path);
|
|
@@ -240,7 +254,74 @@ export function assembleContextPack(input: AssembleContextPackInput): ContextPac
|
|
|
240
254
|
};
|
|
241
255
|
}
|
|
242
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Context Pack remains compatible with its detailed legacy fields, but the
|
|
259
|
+
* agent-facing prompt now uses the same bounded Workset selector as Project
|
|
260
|
+
* Context. No repository scan or page compilation happens here.
|
|
261
|
+
*/
|
|
262
|
+
export async function attachTaskWorkset(input: {
|
|
263
|
+
pack: ContextPack;
|
|
264
|
+
projectId: string;
|
|
265
|
+
dataDir: string;
|
|
266
|
+
lens: string;
|
|
267
|
+
worktreeDirty: boolean;
|
|
268
|
+
currentFacts?: string[];
|
|
269
|
+
codeState?: string;
|
|
270
|
+
snapshot?: {
|
|
271
|
+
id?: string;
|
|
272
|
+
sourceEpoch?: number;
|
|
273
|
+
worktreeState?: 'clean' | 'dirty' | 'unavailable';
|
|
274
|
+
incomplete?: boolean;
|
|
275
|
+
};
|
|
276
|
+
semanticCode?: ExternalCodeGraphOutline;
|
|
277
|
+
providerQuality?: CodeGraphProviderQuality;
|
|
278
|
+
runtimeCautions?: WorksetCaution[];
|
|
279
|
+
}): Promise<ContextPack> {
|
|
280
|
+
const semanticStartHere = input.semanticCode
|
|
281
|
+
? [...input.semanticCode.relatedFiles, ...input.semanticCode.entryPoints.map(entry => entry.path)]
|
|
282
|
+
: [];
|
|
283
|
+
const workset = await buildTaskWorkset({
|
|
284
|
+
projectId: input.projectId,
|
|
285
|
+
dataDir: input.dataDir,
|
|
286
|
+
task: input.pack.task,
|
|
287
|
+
lens: input.lens,
|
|
288
|
+
currentFacts: input.currentFacts ?? [],
|
|
289
|
+
...(input.codeState ? { codeState: input.codeState } : {}),
|
|
290
|
+
startHere: uniq([...semanticStartHere, ...input.pack.suggestedReads]),
|
|
291
|
+
...(input.semanticCode ? { semanticCode: input.semanticCode } : {}),
|
|
292
|
+
...(input.providerQuality ? { providerQuality: input.providerQuality } : {}),
|
|
293
|
+
reliableMemory: input.pack.memories
|
|
294
|
+
.filter(memory => memory.status === 'current')
|
|
295
|
+
.map(memory => ({
|
|
296
|
+
id: memory.id,
|
|
297
|
+
title: memory.title,
|
|
298
|
+
type: memory.type,
|
|
299
|
+
status: memory.status,
|
|
300
|
+
...(memory.path ? { path: memory.path } : {}),
|
|
301
|
+
...(memory.symbol ? { symbol: memory.symbol } : {}),
|
|
302
|
+
...(memory.reason ? { reason: memory.reason } : {}),
|
|
303
|
+
})),
|
|
304
|
+
cautionMemory: input.pack.warnings.map(warning => ({
|
|
305
|
+
id: warning.id,
|
|
306
|
+
title: warning.title,
|
|
307
|
+
type: 'memory',
|
|
308
|
+
status: warning.status,
|
|
309
|
+
reason: warning.reason,
|
|
310
|
+
})),
|
|
311
|
+
verificationHints: input.pack.suggestedVerification,
|
|
312
|
+
worktreeDirty: input.worktreeDirty,
|
|
313
|
+
...(input.snapshot ? { snapshot: input.snapshot } : {}),
|
|
314
|
+
freshness: {
|
|
315
|
+
suspect: input.pack.warnings.filter(warning => warning.status === 'suspect').length,
|
|
316
|
+
stale: input.pack.warnings.filter(warning => warning.status === 'stale').length,
|
|
317
|
+
},
|
|
318
|
+
runtimeCautions: input.runtimeCautions,
|
|
319
|
+
});
|
|
320
|
+
return { ...input.pack, workset };
|
|
321
|
+
}
|
|
322
|
+
|
|
243
323
|
export function buildContextPackPrompt(pack: ContextPack): string {
|
|
324
|
+
if (pack.workset) return pack.workset.prompt;
|
|
244
325
|
const reliableMemories = pack.memories.filter(memory => memory.status === 'current');
|
|
245
326
|
const unboundMemories = pack.memories.filter(memory => memory.status === 'unbound');
|
|
246
327
|
const lines: string[] = ['## Task', pack.task, '', '## Reliable Memories'];
|