memorix 1.1.13 → 1.2.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/CHANGELOG.md +33 -1
- package/README.md +6 -3
- package/README.zh-CN.md +6 -3
- package/dist/cli/index.js +36639 -31279
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +50 -30
- package/dist/index.js +6636 -1778
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +3775 -302
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +33 -1
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +6634 -1776
- 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 +27 -6
- package/docs/CONFIGURATION.md +21 -3
- package/docs/DEVELOPMENT.md +4 -0
- package/docs/README.md +17 -2
- package/docs/SETUP.md +7 -1
- package/docs/dev-log/progress.txt +120 -40
- package/docs/knowledge/workflows/memorix-release.md +57 -0
- 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 +111 -9
- package/src/cli/commands/context.ts +2 -0
- package/src/cli/commands/doctor.ts +73 -4
- package/src/cli/commands/knowledge.ts +322 -0
- package/src/cli/commands/serve-http.ts +26 -42
- package/src/cli/commands/setup.ts +9 -3
- 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 +169 -19
- package/src/codegraph/code-state.ts +95 -0
- package/src/codegraph/context-pack.ts +82 -1
- package/src/codegraph/current-facts.ts +19 -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/task-lens.ts +49 -5
- 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 +30 -47
- 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 +587 -0
- package/src/knowledge/markdown.ts +129 -0
- package/src/knowledge/types.ts +158 -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 +774 -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/auto-relations.ts +21 -0
- package/src/memory/graph-scope.ts +46 -0
- package/src/memory/observations.ts +19 -0
- package/src/orchestrate/verify-gate.ts +33 -10
- 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 +424 -22
- 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
package/src/cli/tui/Panels.tsx
CHANGED
|
@@ -553,8 +553,8 @@ export function WikiView({ knowledge, loading }: WikiViewProps): React.ReactElem
|
|
|
553
553
|
if (loading) {
|
|
554
554
|
return (
|
|
555
555
|
<Box flexDirection="column" paddingX={1}>
|
|
556
|
-
<Text color={COLORS.brand} bold>
|
|
557
|
-
<Text color={COLORS.muted}>
|
|
556
|
+
<Text color={COLORS.brand} bold>Memory Overview</Text>
|
|
557
|
+
<Text color={COLORS.muted}> Generated from durable memory</Text>
|
|
558
558
|
<Text color={COLORS.border}>{separator()}</Text>
|
|
559
559
|
<Text color={COLORS.muted}>Loading...</Text>
|
|
560
560
|
</Box>
|
|
@@ -564,19 +564,19 @@ export function WikiView({ knowledge, loading }: WikiViewProps): React.ReactElem
|
|
|
564
564
|
if (!knowledge) {
|
|
565
565
|
return (
|
|
566
566
|
<Box flexDirection="column" paddingX={1}>
|
|
567
|
-
<Text color={COLORS.brand} bold>
|
|
568
|
-
<Text color={COLORS.muted}>
|
|
567
|
+
<Text color={COLORS.brand} bold>Memory Overview</Text>
|
|
568
|
+
<Text color={COLORS.muted}> Generated from durable memory</Text>
|
|
569
569
|
<Text color={COLORS.border}>{separator()}</Text>
|
|
570
|
-
<Text color={COLORS.warning}>No
|
|
571
|
-
<Text color={COLORS.textDim}>Store memories with /remember or /chat to build your
|
|
570
|
+
<Text color={COLORS.warning}>No memory overview is available for this project.</Text>
|
|
571
|
+
<Text color={COLORS.textDim}>Store durable memories with /remember or /chat to build your overview.</Text>
|
|
572
572
|
</Box>
|
|
573
573
|
);
|
|
574
574
|
}
|
|
575
575
|
|
|
576
576
|
return (
|
|
577
577
|
<Box flexDirection="column" paddingX={1}>
|
|
578
|
-
<Text color={COLORS.brand} bold>
|
|
579
|
-
<Text color={COLORS.muted}>
|
|
578
|
+
<Text color={COLORS.brand} bold>Memory Overview</Text>
|
|
579
|
+
<Text color={COLORS.muted}> Generated from durable memory</Text>
|
|
580
580
|
<Box>
|
|
581
581
|
<Text color={COLORS.textDim}> Project: {knowledge.projectId}</Text>
|
|
582
582
|
<Text color={COLORS.muted}> │ {knowledge.stats.observationsUsed} obs, {knowledge.stats.miniSkillsUsed} skills</Text>
|
package/src/cli/tui/theme.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
31
31
|
{ name: '/integrate', description: 'Set up an IDE', alias: '/setup' },
|
|
32
32
|
{ name: '/cleanup', description: 'Cleanup and purge' },
|
|
33
33
|
{ name: '/ingest', description: 'Git to Memory' },
|
|
34
|
-
{ name: '/wiki', description: '
|
|
34
|
+
{ name: '/wiki', description: 'Memory overview', alias: '/knowledge' },
|
|
35
35
|
{ name: '/exit', description: 'Exit workbench', alias: '/q' },
|
|
36
36
|
];
|
|
37
37
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GraphView —
|
|
2
|
+
* GraphView — deterministic memory-map text browser.
|
|
3
3
|
*
|
|
4
4
|
* Modes: browse (clusters + nodes), detail (single node with edge summary).
|
|
5
5
|
* Keyboard: f filter, up/down navigate, enter detail, k -> Knowledge, esc back.
|
|
@@ -168,7 +168,7 @@ export function GraphView({ projectId, inputFocused, onNavigateKnowledge }: Grap
|
|
|
168
168
|
return (
|
|
169
169
|
<Box flexDirection="column" paddingX={1}>
|
|
170
170
|
<Box>
|
|
171
|
-
<Text color={COLORS.brand} bold>
|
|
171
|
+
<Text color={COLORS.brand} bold>Memory Map Item</Text>
|
|
172
172
|
</Box>
|
|
173
173
|
<Text color={COLORS.border}>{separator()}</Text>
|
|
174
174
|
<Box marginY={1} flexDirection="column">
|
|
@@ -190,7 +190,7 @@ export function GraphView({ projectId, inputFocused, onNavigateKnowledge }: Grap
|
|
|
190
190
|
)}
|
|
191
191
|
{/* Edge summary */}
|
|
192
192
|
<Box marginY={1} flexDirection="column">
|
|
193
|
-
<Text color={COLORS.brandDim} bold>
|
|
193
|
+
<Text color={COLORS.brandDim} bold>Links</Text>
|
|
194
194
|
<Text color={COLORS.muted}>Outgoing ({outgoing.length}): </Text>
|
|
195
195
|
{outgoing.slice(0, 6).map((e) => {
|
|
196
196
|
const targetNode = graph?.nodes.find((n) => n.id === e.target);
|
|
@@ -213,7 +213,7 @@ export function GraphView({ projectId, inputFocused, onNavigateKnowledge }: Grap
|
|
|
213
213
|
{incoming.length > 6 && <Text color={COLORS.muted}> ... +{incoming.length - 6} more</Text>}
|
|
214
214
|
</Box>
|
|
215
215
|
<Box marginTop={1}>
|
|
216
|
-
<Text color={COLORS.muted}>esc back | k {'>'}
|
|
216
|
+
<Text color={COLORS.muted}>esc back | k {'>'} Memory Overview</Text>
|
|
217
217
|
</Box>
|
|
218
218
|
</Box>
|
|
219
219
|
);
|
|
@@ -230,11 +230,12 @@ export function GraphView({ projectId, inputFocused, onNavigateKnowledge }: Grap
|
|
|
230
230
|
<Box flexDirection="column" paddingX={1}>
|
|
231
231
|
<Box flexDirection="column">
|
|
232
232
|
<Box>
|
|
233
|
-
<Text color={COLORS.brand} bold>
|
|
233
|
+
<Text color={COLORS.brand} bold>Memory Map</Text>
|
|
234
234
|
{!loading && graph && (
|
|
235
235
|
<Text color={COLORS.muted}> | {graph.stats.totalNodes} nodes, {graph.stats.totalEdges} edges, {graph.stats.clusterCount} clusters</Text>
|
|
236
236
|
)}
|
|
237
237
|
</Box>
|
|
238
|
+
<Text color={COLORS.textDim}>Deterministic links from shared entities, skill sources, and explicit relations</Text>
|
|
238
239
|
{/* Filter bar */}
|
|
239
240
|
{!loading && graph && (
|
|
240
241
|
<Box marginTop={0}>
|
|
@@ -248,7 +249,7 @@ export function GraphView({ projectId, inputFocused, onNavigateKnowledge }: Grap
|
|
|
248
249
|
{loading ? (
|
|
249
250
|
<Text color={COLORS.muted}>Loading...</Text>
|
|
250
251
|
) : !graph ? (
|
|
251
|
-
<Text color={COLORS.muted}>No
|
|
252
|
+
<Text color={COLORS.muted}>No memory map data is available. Store durable observations to build deterministic links.</Text>
|
|
252
253
|
) : flatItems.length === 0 ? (
|
|
253
254
|
<Text color={COLORS.muted}>
|
|
254
255
|
{filterMode !== 'all' ? 'No nodes match this filter.' : 'No nodes in graph.'}
|
|
@@ -290,7 +291,7 @@ export function GraphView({ projectId, inputFocused, onNavigateKnowledge }: Grap
|
|
|
290
291
|
|
|
291
292
|
<Box marginTop={1} flexDirection="column">
|
|
292
293
|
<Text color={COLORS.textDim}>
|
|
293
|
-
f filter | up/down navigate | enter detail | k {'>'}
|
|
294
|
+
f filter | up/down navigate | enter detail | k {'>'} Memory Overview
|
|
294
295
|
</Text>
|
|
295
296
|
</Box>
|
|
296
297
|
</Box>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* KnowledgeView —
|
|
2
|
+
* KnowledgeView — read-only memory overview browser.
|
|
3
3
|
*
|
|
4
4
|
* Displays ProjectKnowledgeOverview sections and items.
|
|
5
5
|
* Each item shows its provenance refs; pressing 'm' on an item
|
|
@@ -36,8 +36,8 @@ export function KnowledgeView({ knowledge, loading, selectedItemIdx, itemCount }
|
|
|
36
36
|
if (loading) {
|
|
37
37
|
return (
|
|
38
38
|
<Box flexDirection="column" paddingX={1}>
|
|
39
|
-
<Text color={COLORS.brand} bold>
|
|
40
|
-
<Text color={COLORS.muted}>
|
|
39
|
+
<Text color={COLORS.brand} bold>Memory Overview</Text>
|
|
40
|
+
<Text color={COLORS.muted}> Generated from durable memory</Text>
|
|
41
41
|
<Text color={COLORS.border}>{separator()}</Text>
|
|
42
42
|
<Text color={COLORS.muted}>Loading…</Text>
|
|
43
43
|
</Box>
|
|
@@ -47,11 +47,11 @@ export function KnowledgeView({ knowledge, loading, selectedItemIdx, itemCount }
|
|
|
47
47
|
if (!knowledge) {
|
|
48
48
|
return (
|
|
49
49
|
<Box flexDirection="column" paddingX={1}>
|
|
50
|
-
<Text color={COLORS.brand} bold>
|
|
51
|
-
<Text color={COLORS.muted}>
|
|
50
|
+
<Text color={COLORS.brand} bold>Memory Overview</Text>
|
|
51
|
+
<Text color={COLORS.muted}> Generated from durable memory</Text>
|
|
52
52
|
<Text color={COLORS.border}>{separator()}</Text>
|
|
53
|
-
<Text color={COLORS.warning}>No
|
|
54
|
-
<Text color={COLORS.textDim}>Store memories with /remember or use Workbench to build context.</Text>
|
|
53
|
+
<Text color={COLORS.warning}>No memory overview is available for this project.</Text>
|
|
54
|
+
<Text color={COLORS.textDim}>Store durable memories with /remember or use Workbench to build context.</Text>
|
|
55
55
|
</Box>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -68,8 +68,8 @@ export function KnowledgeView({ knowledge, loading, selectedItemIdx, itemCount }
|
|
|
68
68
|
return (
|
|
69
69
|
<Box flexDirection="column" paddingX={1}>
|
|
70
70
|
<Box>
|
|
71
|
-
<Text color={COLORS.brand} bold>
|
|
72
|
-
<Text color={COLORS.muted}>
|
|
71
|
+
<Text color={COLORS.brand} bold>Memory Overview</Text>
|
|
72
|
+
<Text color={COLORS.muted}> Generated from durable memory</Text>
|
|
73
73
|
</Box>
|
|
74
74
|
<Box>
|
|
75
75
|
<Text color={COLORS.textDim}> {knowledge.projectId}</Text>
|
|
@@ -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
|
-
import { collectCurrentProjectFacts, type CurrentProjectFacts } from './current-facts.js';
|
|
7
|
+
import { collectCurrentProjectFacts, formatGitFact, 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
|
|
|
@@ -295,15 +430,7 @@ function formatCurrentFactsLines(facts: CurrentProjectFacts): string[] {
|
|
|
295
430
|
lines.push(`- Latest changelog: ${facts.latestChangelog.version}${facts.latestChangelog.date ? ` (${facts.latestChangelog.date})` : ''}`);
|
|
296
431
|
}
|
|
297
432
|
|
|
298
|
-
|
|
299
|
-
if (facts.git.detached) {
|
|
300
|
-
gitParts.push('detached HEAD');
|
|
301
|
-
} else if (facts.git.branch) {
|
|
302
|
-
gitParts.push(`branch ${facts.git.branch}`);
|
|
303
|
-
}
|
|
304
|
-
if (facts.git.commit) gitParts.push(`commit ${facts.git.commit}`);
|
|
305
|
-
gitParts.push(facts.git.dirty ? 'dirty worktree' : 'clean worktree');
|
|
306
|
-
lines.push(`- Git: ${gitParts.join(', ')}`);
|
|
433
|
+
lines.push('- ' + formatGitFact(facts.git));
|
|
307
434
|
if (facts.git.latestCommit) lines.push(`- Latest commit: ${facts.git.latestCommit}`);
|
|
308
435
|
lines.push('- Current facts above outrank progress/dev-log files when they conflict.');
|
|
309
436
|
|
|
@@ -322,6 +449,23 @@ function formatCurrentFactsLines(facts: CurrentProjectFacts): string[] {
|
|
|
322
449
|
return lines;
|
|
323
450
|
}
|
|
324
451
|
|
|
452
|
+
function worksetFactLines(facts: CurrentProjectFacts): string[] {
|
|
453
|
+
const lines: string[] = [];
|
|
454
|
+
if (facts.packageVersion) lines.push('Package version: ' + facts.packageVersion);
|
|
455
|
+
if (facts.latestChangelog) {
|
|
456
|
+
lines.push('Latest changelog: ' + facts.latestChangelog.version
|
|
457
|
+
+ (facts.latestChangelog.date ? ' (' + facts.latestChangelog.date + ')' : ''));
|
|
458
|
+
}
|
|
459
|
+
lines.push(formatGitFact(facts.git));
|
|
460
|
+
for (const note of facts.staleNotes.slice(0, 1)) {
|
|
461
|
+
lines.push(
|
|
462
|
+
'Historical note: ' + note.path
|
|
463
|
+
+ (note.branchHint ? ' (branch hint ' + note.branchHint + '; ' + note.reason + ')' : ' (' + note.reason + ')'),
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
return lines;
|
|
467
|
+
}
|
|
468
|
+
|
|
325
469
|
export function formatAutoProjectContextSummary(context: AutoProjectContext): string {
|
|
326
470
|
const reliableSources = rankLensSources(
|
|
327
471
|
dedupeSourcesByObservation(context.explain.sources.filter(source => source.status === 'current')),
|
|
@@ -337,6 +481,7 @@ export function formatAutoProjectContextSummary(context: AutoProjectContext): st
|
|
|
337
481
|
...formatCurrentFactsLines(context.currentFacts),
|
|
338
482
|
'',
|
|
339
483
|
`- Code memory: ${context.overview.code.files} files / ${context.overview.code.symbols} symbols / ${context.overview.code.refs} memory links`,
|
|
484
|
+
`- Code provider: ${context.providerQuality.selected} (${context.providerQuality.selectedQuality})`,
|
|
340
485
|
`- Languages: ${formatLanguages(context.overview)}`,
|
|
341
486
|
`- Memories: ${context.overview.memory.active} active / ${context.overview.memory.total} total`,
|
|
342
487
|
`- Freshness: ${context.overview.freshness.current} current, ${context.overview.freshness.suspect} suspect, ${context.overview.freshness.stale} stale`,
|
|
@@ -363,6 +508,10 @@ export function formatAutoProjectContextSummary(context: AutoProjectContext): st
|
|
|
363
508
|
}
|
|
364
509
|
|
|
365
510
|
export function formatAutoProjectContextPrompt(context: AutoProjectContext): string {
|
|
511
|
+
return context.workset.prompt;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function formatLegacyAutoProjectContextPrompt(context: AutoProjectContext): string {
|
|
366
515
|
const lines = [
|
|
367
516
|
`Memorix Autopilot Brief for ${context.project.name}`,
|
|
368
517
|
context.task ? `Task: ${context.task}` : '',
|
|
@@ -371,6 +520,7 @@ export function formatAutoProjectContextPrompt(context: AutoProjectContext): str
|
|
|
371
520
|
...formatCurrentFactsLines(context.currentFacts),
|
|
372
521
|
'',
|
|
373
522
|
'Project state',
|
|
523
|
+
codeStateLine(context.overview),
|
|
374
524
|
`- Code memory: ${context.overview.code.files} files, ${context.overview.code.symbols} symbols, ${context.overview.code.refs} memory links`,
|
|
375
525
|
`- Languages: ${formatLanguages(context.overview)}`,
|
|
376
526
|
`- 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'];
|