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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* is skipped and the task completes as before (backward compatible).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { spawn
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
13
|
|
|
14
14
|
// ── Types ──────────────────────────────────────────────────────────
|
|
15
15
|
|
|
@@ -63,6 +63,15 @@ export function runGate(
|
|
|
63
63
|
return new Promise<GateResult>((resolve) => {
|
|
64
64
|
const chunks: Buffer[] = [];
|
|
65
65
|
let totalBytes = 0;
|
|
66
|
+
let settled = false;
|
|
67
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
68
|
+
|
|
69
|
+
const finish = (result: GateResult) => {
|
|
70
|
+
if (settled) return;
|
|
71
|
+
settled = true;
|
|
72
|
+
if (timer) clearTimeout(timer);
|
|
73
|
+
resolve(result);
|
|
74
|
+
};
|
|
66
75
|
|
|
67
76
|
const proc = spawn(command, {
|
|
68
77
|
cwd,
|
|
@@ -86,9 +95,14 @@ export function runGate(
|
|
|
86
95
|
let killed = false;
|
|
87
96
|
const killTree = () => {
|
|
88
97
|
try {
|
|
89
|
-
if (process.platform === 'win32') {
|
|
90
|
-
//
|
|
91
|
-
|
|
98
|
+
if (process.platform === 'win32' && proc.pid) {
|
|
99
|
+
// `shell: true` gives Windows a cmd.exe parent. Kill its whole tree
|
|
100
|
+
// without making the timeout path wait for a stubborn descendant.
|
|
101
|
+
const killer = spawn('taskkill.exe', ['/F', '/T', '/PID', String(proc.pid)], {
|
|
102
|
+
stdio: 'ignore',
|
|
103
|
+
windowsHide: true,
|
|
104
|
+
});
|
|
105
|
+
killer.unref();
|
|
92
106
|
} else {
|
|
93
107
|
// Unix: kill process group
|
|
94
108
|
process.kill(-proc.pid!, 'SIGKILL');
|
|
@@ -97,20 +111,30 @@ export function runGate(
|
|
|
97
111
|
// Fallback: kill the process directly
|
|
98
112
|
try { proc.kill('SIGKILL'); } catch { /* already dead */ }
|
|
99
113
|
}
|
|
114
|
+
try { proc.stdout?.destroy(); } catch { /* already closed */ }
|
|
115
|
+
try { proc.stderr?.destroy(); } catch { /* already closed */ }
|
|
116
|
+
proc.unref();
|
|
100
117
|
};
|
|
101
118
|
|
|
102
|
-
|
|
119
|
+
timer = setTimeout(() => {
|
|
103
120
|
killed = true;
|
|
104
121
|
killTree();
|
|
122
|
+
const output = Buffer.concat(chunks).toString('utf-8');
|
|
123
|
+
finish({
|
|
124
|
+
gate,
|
|
125
|
+
passed: false,
|
|
126
|
+
output: output + `\n[TIMEOUT] Gate "${gate}" killed after ${timeoutMs}ms`,
|
|
127
|
+
durationMs: Date.now() - start,
|
|
128
|
+
command,
|
|
129
|
+
});
|
|
105
130
|
}, timeoutMs);
|
|
106
131
|
|
|
107
132
|
proc.on('close', (code) => {
|
|
108
|
-
clearTimeout(timer);
|
|
109
133
|
const output = Buffer.concat(chunks).toString('utf-8');
|
|
110
134
|
const durationMs = Date.now() - start;
|
|
111
135
|
|
|
112
136
|
if (killed) {
|
|
113
|
-
|
|
137
|
+
finish({
|
|
114
138
|
gate,
|
|
115
139
|
passed: false,
|
|
116
140
|
output: output + `\n[TIMEOUT] Gate "${gate}" killed after ${timeoutMs}ms`,
|
|
@@ -120,7 +144,7 @@ export function runGate(
|
|
|
120
144
|
return;
|
|
121
145
|
}
|
|
122
146
|
|
|
123
|
-
|
|
147
|
+
finish({
|
|
124
148
|
gate,
|
|
125
149
|
passed: code === 0,
|
|
126
150
|
output,
|
|
@@ -130,8 +154,7 @@ export function runGate(
|
|
|
130
154
|
});
|
|
131
155
|
|
|
132
156
|
proc.on('error', (err) => {
|
|
133
|
-
|
|
134
|
-
resolve({
|
|
157
|
+
finish({
|
|
135
158
|
gate,
|
|
136
159
|
passed: false,
|
|
137
160
|
output: `[ERROR] Failed to spawn gate command: ${err.message}`,
|
|
@@ -11,6 +11,11 @@ export const CONTROL_PLANE_MAINTENANCE_KINDS: readonly MaintenanceJobKind[] = [
|
|
|
11
11
|
'retention-archive',
|
|
12
12
|
'consolidation',
|
|
13
13
|
'codegraph-refresh',
|
|
14
|
+
'claim-derive',
|
|
15
|
+
'claim-requalification',
|
|
16
|
+
'knowledge-compile',
|
|
17
|
+
'knowledge-lint',
|
|
18
|
+
'workflow-index',
|
|
14
19
|
];
|
|
15
20
|
|
|
16
21
|
const TARGET_WAIT_RETRY_MS = 30_000;
|
|
@@ -12,6 +12,11 @@ export const ISOLATED_MAINTENANCE_JOB_KINDS: readonly MaintenanceJobKind[] = [
|
|
|
12
12
|
'retention-archive',
|
|
13
13
|
'consolidation',
|
|
14
14
|
'codegraph-refresh',
|
|
15
|
+
'claim-derive',
|
|
16
|
+
'claim-requalification',
|
|
17
|
+
'knowledge-compile',
|
|
18
|
+
'knowledge-lint',
|
|
19
|
+
'workflow-index',
|
|
15
20
|
];
|
|
16
21
|
const DEFAULT_TIMEOUT_MS = 5 * 60_000;
|
|
17
22
|
const MAX_CHILD_OUTPUT_BYTES = 1_000_000;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ClaimStore } from '../knowledge/claim-store.js';
|
|
2
|
+
import { KnowledgeWorkspaceStore } from '../knowledge/workspace-store.js';
|
|
3
|
+
import { WorkflowStore } from '../knowledge/workflow-store.js';
|
|
4
|
+
import { MaintenanceJobStore, type MaintenanceJobSummary } from './maintenance-jobs.js';
|
|
5
|
+
|
|
6
|
+
export interface LifecycleDiagnostics {
|
|
7
|
+
maintenance: {
|
|
8
|
+
summary: MaintenanceJobSummary;
|
|
9
|
+
failedJobs: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
attempts: number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
lastError?: string;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
claims: {
|
|
18
|
+
total: number;
|
|
19
|
+
active: number;
|
|
20
|
+
unknown: number;
|
|
21
|
+
needsReview: number;
|
|
22
|
+
conflicts: number;
|
|
23
|
+
};
|
|
24
|
+
workspaces: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
mode: 'local' | 'versioned';
|
|
27
|
+
status: string;
|
|
28
|
+
pages: number;
|
|
29
|
+
pendingProposals: number;
|
|
30
|
+
lastCompiledAt?: string;
|
|
31
|
+
lastLintedAt?: string;
|
|
32
|
+
}>;
|
|
33
|
+
workflows: {
|
|
34
|
+
total: number;
|
|
35
|
+
active: number;
|
|
36
|
+
failedRuns: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read-only operational summary for Doctor and dashboard surfaces. It exposes
|
|
42
|
+
* lifecycle state, never maintenance payloads or workspace file contents.
|
|
43
|
+
*/
|
|
44
|
+
export async function collectLifecycleDiagnostics(input: {
|
|
45
|
+
dataDir: string;
|
|
46
|
+
projectId: string;
|
|
47
|
+
}): Promise<LifecycleDiagnostics> {
|
|
48
|
+
const queue = new MaintenanceJobStore(input.dataDir);
|
|
49
|
+
const maintenance = {
|
|
50
|
+
summary: queue.summary(input.projectId),
|
|
51
|
+
failedJobs: queue.list({ projectId: input.projectId, status: 'failed', limit: 5 }).map(job => ({
|
|
52
|
+
id: job.id,
|
|
53
|
+
kind: job.kind,
|
|
54
|
+
attempts: job.attempts,
|
|
55
|
+
updatedAt: job.updatedAt,
|
|
56
|
+
...(job.lastError ? { lastError: job.lastError } : {}),
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const claims = new ClaimStore();
|
|
61
|
+
const workspaces = new KnowledgeWorkspaceStore();
|
|
62
|
+
const workflows = new WorkflowStore();
|
|
63
|
+
await Promise.all([
|
|
64
|
+
claims.init(input.dataDir),
|
|
65
|
+
workspaces.init(input.dataDir),
|
|
66
|
+
workflows.init(input.dataDir),
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const projectClaims = claims.listClaims(input.projectId);
|
|
70
|
+
const workspaceRecords = [
|
|
71
|
+
workspaces.findWorkspace(input.projectId, 'versioned'),
|
|
72
|
+
workspaces.findWorkspace(input.projectId, 'local'),
|
|
73
|
+
].filter((workspace): workspace is NonNullable<typeof workspace> => !!workspace);
|
|
74
|
+
const workspaceDiagnostics = workspaceRecords.map(workspace => ({
|
|
75
|
+
id: workspace.id,
|
|
76
|
+
mode: workspace.mode,
|
|
77
|
+
status: workspace.status,
|
|
78
|
+
pages: workspaces.listPages(workspace.id).length,
|
|
79
|
+
pendingProposals: workspaces.listProposals(workspace.id, 'pending').length,
|
|
80
|
+
...(workspace.lastCompiledAt ? { lastCompiledAt: workspace.lastCompiledAt } : {}),
|
|
81
|
+
...(workspace.lastLintedAt ? { lastLintedAt: workspace.lastLintedAt } : {}),
|
|
82
|
+
}));
|
|
83
|
+
const projectWorkflows = workspaceRecords.flatMap(workspace => workflows.listWorkflows(workspace.id));
|
|
84
|
+
const runs = workflows.listRuns(input.projectId, undefined, 100);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
maintenance,
|
|
88
|
+
claims: {
|
|
89
|
+
total: projectClaims.length,
|
|
90
|
+
active: projectClaims.filter(claim => claim.status === 'active').length,
|
|
91
|
+
unknown: projectClaims.filter(claim => claim.status === 'unknown').length,
|
|
92
|
+
needsReview: projectClaims.filter(claim => claim.reviewState === 'needs-review').length,
|
|
93
|
+
conflicts: claims.listConflicts(input.projectId).length,
|
|
94
|
+
},
|
|
95
|
+
workspaces: workspaceDiagnostics,
|
|
96
|
+
workflows: {
|
|
97
|
+
total: projectWorkflows.length,
|
|
98
|
+
active: projectWorkflows.filter(workflow => workflow.status === 'active').length,
|
|
99
|
+
failedRuns: runs.filter(run => run.outcome === 'failed' || run.verificationVerdict === 'failed').length,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { MaintenanceJobStore } from './maintenance-jobs.js';
|
|
2
|
+
|
|
3
|
+
export type MaintenanceQueue = Pick<MaintenanceJobStore, 'enqueue'>;
|
|
4
|
+
|
|
5
|
+
function queueFor(input: { dataDir: string; queue?: MaintenanceQueue }): MaintenanceQueue {
|
|
6
|
+
return input.queue ?? new MaintenanceJobStore(input.dataDir);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Coalesce every trigger for a project's next Code Memory scan. The newest
|
|
11
|
+
* event replaces the payload while one durable job remains pending.
|
|
12
|
+
*/
|
|
13
|
+
export function enqueueCodegraphRefresh(input: {
|
|
14
|
+
dataDir: string;
|
|
15
|
+
projectId: string;
|
|
16
|
+
source: string;
|
|
17
|
+
maxFiles?: number;
|
|
18
|
+
queue?: MaintenanceQueue;
|
|
19
|
+
}): void {
|
|
20
|
+
queueFor(input).enqueue({
|
|
21
|
+
projectId: input.projectId,
|
|
22
|
+
kind: 'codegraph-refresh',
|
|
23
|
+
dedupeKey: 'codegraph-refresh',
|
|
24
|
+
payload: {
|
|
25
|
+
source: input.source,
|
|
26
|
+
...(input.maxFiles ? { maxFiles: input.maxFiles } : {}),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Requalification is owned by one job per project, never by an individual
|
|
33
|
+
* snapshot. A newer refresh replaces the snapshot metadata before it runs.
|
|
34
|
+
*/
|
|
35
|
+
export function enqueueClaimRequalification(input: {
|
|
36
|
+
dataDir: string;
|
|
37
|
+
projectId: string;
|
|
38
|
+
source: string;
|
|
39
|
+
snapshotId?: string;
|
|
40
|
+
queue?: MaintenanceQueue;
|
|
41
|
+
}): void {
|
|
42
|
+
queueFor(input).enqueue({
|
|
43
|
+
projectId: input.projectId,
|
|
44
|
+
kind: 'claim-requalification',
|
|
45
|
+
dedupeKey: 'claim-requalification',
|
|
46
|
+
payload: {
|
|
47
|
+
source: input.source,
|
|
48
|
+
...(input.snapshotId ? { snapshotId: input.snapshotId } : {}),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A stored explicit/Git observation earns a recoverable claim derivation. */
|
|
54
|
+
export function enqueueClaimDerivation(input: {
|
|
55
|
+
dataDir: string;
|
|
56
|
+
projectId: string;
|
|
57
|
+
observationId: number;
|
|
58
|
+
queue?: MaintenanceQueue;
|
|
59
|
+
}): void {
|
|
60
|
+
queueFor(input).enqueue({
|
|
61
|
+
projectId: input.projectId,
|
|
62
|
+
kind: 'claim-derive',
|
|
63
|
+
dedupeKey: 'claim-derive:' + input.observationId,
|
|
64
|
+
payload: { observationId: input.observationId },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The follow-up jobs operate on the same workspace the Workset reads: a
|
|
70
|
+
* versioned workspace when present, otherwise the local workspace.
|
|
71
|
+
*/
|
|
72
|
+
export function enqueueKnowledgeFollowups(input: {
|
|
73
|
+
dataDir: string;
|
|
74
|
+
projectId: string;
|
|
75
|
+
source: string;
|
|
76
|
+
includeCompile?: boolean;
|
|
77
|
+
workspaceMode?: 'local' | 'versioned';
|
|
78
|
+
allowVersionedWrite?: boolean;
|
|
79
|
+
queue?: MaintenanceQueue;
|
|
80
|
+
}): void {
|
|
81
|
+
const queue = queueFor(input);
|
|
82
|
+
const payload = {
|
|
83
|
+
source: input.source,
|
|
84
|
+
...(input.workspaceMode ? { workspaceMode: input.workspaceMode } : {}),
|
|
85
|
+
...(input.allowVersionedWrite ? { allowVersionedWrite: true } : {}),
|
|
86
|
+
};
|
|
87
|
+
if (input.includeCompile) {
|
|
88
|
+
queue.enqueue({
|
|
89
|
+
projectId: input.projectId,
|
|
90
|
+
kind: 'knowledge-compile',
|
|
91
|
+
dedupeKey: 'knowledge-compile',
|
|
92
|
+
payload,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
queue.enqueue({
|
|
96
|
+
projectId: input.projectId,
|
|
97
|
+
kind: 'knowledge-lint',
|
|
98
|
+
dedupeKey: 'knowledge-lint',
|
|
99
|
+
payload,
|
|
100
|
+
});
|
|
101
|
+
queue.enqueue({
|
|
102
|
+
projectId: input.projectId,
|
|
103
|
+
kind: 'workflow-index',
|
|
104
|
+
dedupeKey: 'workflow-index',
|
|
105
|
+
payload,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -8,6 +8,11 @@ export const MAINTENANCE_JOB_KINDS = [
|
|
|
8
8
|
'retention-archive',
|
|
9
9
|
'consolidation',
|
|
10
10
|
'codegraph-refresh',
|
|
11
|
+
'claim-derive',
|
|
12
|
+
'claim-requalification',
|
|
13
|
+
'knowledge-compile',
|
|
14
|
+
'knowledge-lint',
|
|
15
|
+
'workflow-index',
|
|
11
16
|
] as const;
|
|
12
17
|
|
|
13
18
|
export type MaintenanceJobKind = typeof MAINTENANCE_JOB_KINDS[number];
|
|
@@ -3,11 +3,17 @@ import type {
|
|
|
3
3
|
MaintenanceJobRunResult,
|
|
4
4
|
} from './maintenance-jobs.js';
|
|
5
5
|
import { runMaintenanceInChildProcess } from './isolated-maintenance.js';
|
|
6
|
+
import {
|
|
7
|
+
enqueueClaimRequalification,
|
|
8
|
+
enqueueKnowledgeFollowups,
|
|
9
|
+
type MaintenanceQueue,
|
|
10
|
+
} from './lifecycle.js';
|
|
6
11
|
|
|
7
12
|
const DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
8
13
|
const DEFAULT_RETENTION_BATCH_SIZE = 100;
|
|
9
14
|
const DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
|
|
10
15
|
const DEFAULT_CODEGRAPH_MAX_FILES = 5_000;
|
|
16
|
+
const DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
|
|
11
17
|
const VECTOR_RETRY_DELAY_MS = 5_000;
|
|
12
18
|
const DEDUP_PER_PAIR_TIMEOUT_MS = 5_000;
|
|
13
19
|
|
|
@@ -49,6 +55,46 @@ function codeGraphMaxFiles(payload: Record<string, unknown>): number {
|
|
|
49
55
|
return Math.min(20_000, Math.max(1, Math.floor(value)));
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
function claimDerivationBatchSize(payload: Record<string, unknown>): number {
|
|
59
|
+
const value = payload.limit;
|
|
60
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return DEFAULT_CLAIM_DERIVATION_BATCH_SIZE;
|
|
61
|
+
return Math.min(500, Math.max(1, Math.floor(value)));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function claimDerivationCursor(payload: Record<string, unknown>): number {
|
|
65
|
+
const value = payload.cursor;
|
|
66
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
67
|
+
? Math.max(0, Math.floor(value))
|
|
68
|
+
: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function observationId(payload: Record<string, unknown>): number | undefined {
|
|
72
|
+
const value = payload.observationId;
|
|
73
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
|
|
74
|
+
? value
|
|
75
|
+
: undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function workspaceMode(payload: Record<string, unknown>): 'local' | 'versioned' | undefined {
|
|
79
|
+
return payload.workspaceMode === 'local' || payload.workspaceMode === 'versioned'
|
|
80
|
+
? payload.workspaceMode
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadWorkspaceForMaintenance(
|
|
85
|
+
projectId: string,
|
|
86
|
+
projectDir: string,
|
|
87
|
+
mode?: 'local' | 'versioned',
|
|
88
|
+
) {
|
|
89
|
+
const { loadKnowledgeWorkspace } = await import('../knowledge/workspace.js');
|
|
90
|
+
if (mode) return loadKnowledgeWorkspace({ projectId, dataDir: projectDir, mode });
|
|
91
|
+
const [versioned, local] = await Promise.all([
|
|
92
|
+
loadKnowledgeWorkspace({ projectId, dataDir: projectDir, mode: 'versioned' }),
|
|
93
|
+
loadKnowledgeWorkspace({ projectId, dataDir: projectDir, mode: 'local' }),
|
|
94
|
+
]);
|
|
95
|
+
return versioned ?? local;
|
|
96
|
+
}
|
|
97
|
+
|
|
52
98
|
/**
|
|
53
99
|
* Creates handlers for one initialized project runtime. The worker itself is
|
|
54
100
|
* project-scoped so it never claims a different project's queued work.
|
|
@@ -149,6 +195,7 @@ export function createProjectMaintenanceHandler(
|
|
|
149
195
|
projectId: string,
|
|
150
196
|
projectDir: string,
|
|
151
197
|
projectRoot?: string,
|
|
198
|
+
options: { maintenanceQueue?: MaintenanceQueue } = {},
|
|
152
199
|
): MaintenanceJobHandler {
|
|
153
200
|
return async (job): Promise<MaintenanceJobRunResult> => {
|
|
154
201
|
if (job.projectId !== projectId) {
|
|
@@ -220,6 +267,149 @@ export function createProjectMaintenanceHandler(
|
|
|
220
267
|
});
|
|
221
268
|
const activeObservations = await getObservationStore().loadByProject(projectId, { status: 'active' });
|
|
222
269
|
await backfillMissingObservationCodeRefs(store, activeObservations);
|
|
270
|
+
const snapshot = store.latestSnapshot(projectId);
|
|
271
|
+
enqueueClaimRequalification({
|
|
272
|
+
projectId,
|
|
273
|
+
dataDir: projectDir,
|
|
274
|
+
source: 'codegraph-refresh',
|
|
275
|
+
...(snapshot?.id ? { snapshotId: snapshot.id } : {}),
|
|
276
|
+
...(options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}),
|
|
277
|
+
});
|
|
278
|
+
return { action: 'complete' };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (job.kind === 'claim-derive') {
|
|
282
|
+
const id = observationId(job.payload);
|
|
283
|
+
if (!id) throw new Error('Claim derivation requires a positive observationId');
|
|
284
|
+
const [
|
|
285
|
+
{ CodeGraphStore },
|
|
286
|
+
{ ClaimStore },
|
|
287
|
+
{ bindObservationToCode },
|
|
288
|
+
{ deriveLowRiskClaimsFromObservation },
|
|
289
|
+
{ getObservationStore },
|
|
290
|
+
] = await Promise.all([
|
|
291
|
+
import('../codegraph/store.js'),
|
|
292
|
+
import('../knowledge/claim-store.js'),
|
|
293
|
+
import('../codegraph/binder.js'),
|
|
294
|
+
import('../knowledge/claims.js'),
|
|
295
|
+
import('../store/obs-store.js'),
|
|
296
|
+
]);
|
|
297
|
+
const observation = await getObservationStore().getById(id);
|
|
298
|
+
if (!observation || observation.projectId !== projectId) return { action: 'complete' };
|
|
299
|
+
const codeStore = new CodeGraphStore();
|
|
300
|
+
const claimStore = new ClaimStore();
|
|
301
|
+
await Promise.all([codeStore.init(projectDir), claimStore.init(projectDir)]);
|
|
302
|
+
await bindObservationToCode(codeStore, observation);
|
|
303
|
+
const derived = deriveLowRiskClaimsFromObservation(claimStore, observation, codeStore);
|
|
304
|
+
if (derived.length > 0) {
|
|
305
|
+
enqueueKnowledgeFollowups({
|
|
306
|
+
projectId,
|
|
307
|
+
dataDir: projectDir,
|
|
308
|
+
source: 'claim-derive:' + id,
|
|
309
|
+
includeCompile: true,
|
|
310
|
+
...(options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return { action: 'complete' };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (job.kind === 'claim-requalification') {
|
|
317
|
+
const [
|
|
318
|
+
{ CodeGraphStore },
|
|
319
|
+
{ ClaimStore },
|
|
320
|
+
{ bindObservationToCode },
|
|
321
|
+
{ deriveLowRiskClaimsFromObservation, requalifyClaimsForCodeState },
|
|
322
|
+
{ getObservationStore },
|
|
323
|
+
] = await Promise.all([
|
|
324
|
+
import('../codegraph/store.js'),
|
|
325
|
+
import('../knowledge/claim-store.js'),
|
|
326
|
+
import('../codegraph/binder.js'),
|
|
327
|
+
import('../knowledge/claims.js'),
|
|
328
|
+
import('../store/obs-store.js'),
|
|
329
|
+
]);
|
|
330
|
+
const codeStore = new CodeGraphStore();
|
|
331
|
+
const claimStore = new ClaimStore();
|
|
332
|
+
await Promise.all([codeStore.init(projectDir), claimStore.init(projectDir)]);
|
|
333
|
+
const limit = claimDerivationBatchSize(job.payload);
|
|
334
|
+
const observations = await getObservationStore().loadByProject(projectId, {
|
|
335
|
+
status: 'active',
|
|
336
|
+
afterId: claimDerivationCursor(job.payload),
|
|
337
|
+
limit,
|
|
338
|
+
});
|
|
339
|
+
let derivedCount = 0;
|
|
340
|
+
for (const observation of observations) {
|
|
341
|
+
await bindObservationToCode(codeStore, observation);
|
|
342
|
+
derivedCount += deriveLowRiskClaimsFromObservation(claimStore, observation, codeStore).length;
|
|
343
|
+
}
|
|
344
|
+
requalifyClaimsForCodeState(claimStore, codeStore, projectId);
|
|
345
|
+
if (observations.length === limit) {
|
|
346
|
+
return {
|
|
347
|
+
action: 'reschedule',
|
|
348
|
+
delayMs: 0,
|
|
349
|
+
resetAttempts: true,
|
|
350
|
+
payload: {
|
|
351
|
+
...job.payload,
|
|
352
|
+
cursor: observations[observations.length - 1].id,
|
|
353
|
+
limit,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
enqueueKnowledgeFollowups({
|
|
358
|
+
projectId,
|
|
359
|
+
dataDir: projectDir,
|
|
360
|
+
source: 'claim-requalification',
|
|
361
|
+
includeCompile: derivedCount > 0,
|
|
362
|
+
...(options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}),
|
|
363
|
+
});
|
|
364
|
+
return { action: 'complete' };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (job.kind === 'knowledge-compile') {
|
|
368
|
+
const workspace = await loadWorkspaceForMaintenance(projectId, projectDir, workspaceMode(job.payload));
|
|
369
|
+
if (!workspace) return { action: 'complete' };
|
|
370
|
+
if (workspace.mode === 'versioned' && job.payload.allowVersionedWrite !== true) {
|
|
371
|
+
return { action: 'complete' };
|
|
372
|
+
}
|
|
373
|
+
const [{ ClaimStore }, { compileKnowledgeWorkspace }] = await Promise.all([
|
|
374
|
+
import('../knowledge/claim-store.js'),
|
|
375
|
+
import('../knowledge/wiki.js'),
|
|
376
|
+
]);
|
|
377
|
+
const claims = new ClaimStore();
|
|
378
|
+
await claims.init(projectDir);
|
|
379
|
+
await compileKnowledgeWorkspace({ workspace, claims });
|
|
380
|
+
enqueueKnowledgeFollowups({
|
|
381
|
+
projectId,
|
|
382
|
+
dataDir: projectDir,
|
|
383
|
+
source: 'knowledge-compile',
|
|
384
|
+
...(options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}),
|
|
385
|
+
});
|
|
386
|
+
return { action: 'complete' };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (job.kind === 'workflow-index') {
|
|
390
|
+
const workspace = await loadWorkspaceForMaintenance(projectId, projectDir, workspaceMode(job.payload));
|
|
391
|
+
if (!workspace) return { action: 'complete' };
|
|
392
|
+
const { syncCanonicalWorkflows } = await import('../knowledge/workflows.js');
|
|
393
|
+
await syncCanonicalWorkflows(workspace);
|
|
394
|
+
return { action: 'complete' };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (job.kind === 'knowledge-lint') {
|
|
398
|
+
const workspace = await loadWorkspaceForMaintenance(projectId, projectDir, workspaceMode(job.payload));
|
|
399
|
+
if (!workspace) return { action: 'complete' };
|
|
400
|
+
const [
|
|
401
|
+
{ ClaimStore },
|
|
402
|
+
{ CodeGraphStore },
|
|
403
|
+
{ lintKnowledgeWorkspace },
|
|
404
|
+
] = await Promise.all([
|
|
405
|
+
import('../knowledge/claim-store.js'),
|
|
406
|
+
import('../codegraph/store.js'),
|
|
407
|
+
import('../knowledge/wiki.js'),
|
|
408
|
+
]);
|
|
409
|
+
const claims = new ClaimStore();
|
|
410
|
+
const codeStore = new CodeGraphStore();
|
|
411
|
+
await Promise.all([claims.init(projectDir), codeStore.init(projectDir)]);
|
|
412
|
+
await lintKnowledgeWorkspace({ workspace, claims, codeStore });
|
|
223
413
|
return { action: 'complete' };
|
|
224
414
|
}
|
|
225
415
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Suitable for normal MCP clients where every tool schema costs context tokens.
|
|
11
11
|
* - "lite": Core memory CRUD, sessions, reasoning, retention, backup — 17 tools.
|
|
12
12
|
* Suitable for solo users who want the full memory surface without team tools.
|
|
13
|
-
* - "team" (HTTP default): lite + orchestration coordination tools
|
|
13
|
+
* - "team" (HTTP default): lite + orchestration coordination tools, dashboard, and Knowledge Workspace — 25 tools.
|
|
14
14
|
* Suitable when an operator explicitly wants task/message/lock surfaces.
|
|
15
15
|
* - "full": Everything, including niche / advanced tools (consolidate, dedup,
|
|
16
16
|
* formation metrics, skills, rules/workspace sync, KG-official, image ingest).
|
|
@@ -50,6 +50,7 @@ export const TOOL_PROFILES: Record<string, ReadonlyArray<ToolProfile>> = Object.
|
|
|
50
50
|
|
|
51
51
|
// ── team: orchestration coordination surfaces — HTTP default ──────
|
|
52
52
|
memorix_dashboard: ['team', 'full'],
|
|
53
|
+
memorix_knowledge: ['team', 'full'],
|
|
53
54
|
memorix_handoff: ['team', 'full'],
|
|
54
55
|
memorix_poll: ['team', 'full'],
|
|
55
56
|
team_manage: ['team', 'full'],
|
|
@@ -135,7 +136,7 @@ export function describeProfile(profile: ToolProfile): string {
|
|
|
135
136
|
switch (profile) {
|
|
136
137
|
case 'micro': return 'micro (agent-ready context + basic memory, ~7 tools)';
|
|
137
138
|
case 'lite': return 'lite (core memory + sessions, ~17 tools)';
|
|
138
|
-
case 'team': return 'team (lite + coordination tools + dashboard, ~
|
|
139
|
+
case 'team': return 'team (lite + coordination tools + dashboard + knowledge workspace, ~25 tools)';
|
|
139
140
|
case 'full': return 'full (all tools including advanced / KG-compat)';
|
|
140
141
|
}
|
|
141
142
|
}
|