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/server.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { initSessionStore } from './store/session-store.js';
|
|
|
28
28
|
import { checkProjectAttribution, auditProjectObservations } from './memory/attribution-guard.js';
|
|
29
29
|
import { createAutoRelations } from './memory/auto-relations.js';
|
|
30
30
|
import { extractEntities } from './memory/entity-extractor.js';
|
|
31
|
+
import { scopeKnowledgeGraphToProject } from './memory/graph-scope.js';
|
|
31
32
|
import { compactSearch, compactTimeline, compactDetail } from './compact/engine.js';
|
|
32
33
|
import { buildGraphContextPacket, formatGraphContextPrompt } from './memory/graph-context.js';
|
|
33
34
|
import { detectProject } from './project/detector.js';
|
|
@@ -469,10 +470,11 @@ export async function createMemorixServer(
|
|
|
469
470
|
let maintenanceWorker: { stop(): void } | null = null;
|
|
470
471
|
const startProjectMaintenanceWorker = async (autoCleanup = false): Promise<void> => {
|
|
471
472
|
maintenanceWorker?.stop();
|
|
472
|
-
const [{ MaintenanceJobStore, MaintenanceJobWorker }, maintenance, observations] = await Promise.all([
|
|
473
|
+
const [{ MaintenanceJobStore, MaintenanceJobWorker }, maintenance, observations, lifecycle] = await Promise.all([
|
|
473
474
|
import('./runtime/maintenance-jobs.js'),
|
|
474
475
|
import('./runtime/project-maintenance.js'),
|
|
475
476
|
import('./memory/observations.js'),
|
|
477
|
+
import('./runtime/lifecycle.js'),
|
|
476
478
|
]);
|
|
477
479
|
const queue = new MaintenanceJobStore(projectDir);
|
|
478
480
|
const vectorStatus = observations.getVectorStatus(project.id);
|
|
@@ -509,11 +511,12 @@ export async function createMemorixServer(
|
|
|
509
511
|
|| !Number.isFinite(indexedAt)
|
|
510
512
|
|| Date.now() - indexedAt > 10 * 60_000;
|
|
511
513
|
if (needsRefresh) {
|
|
512
|
-
|
|
514
|
+
lifecycle.enqueueCodegraphRefresh({
|
|
515
|
+
dataDir: projectDir,
|
|
513
516
|
projectId: project.id,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
source: 'startup',
|
|
518
|
+
maxFiles: 5_000,
|
|
519
|
+
queue,
|
|
517
520
|
});
|
|
518
521
|
}
|
|
519
522
|
} catch {
|
|
@@ -1329,10 +1332,12 @@ export async function createMemorixServer(
|
|
|
1329
1332
|
},
|
|
1330
1333
|
{ getObservationStore },
|
|
1331
1334
|
{ MaintenanceJobStore },
|
|
1335
|
+
{ enqueueCodegraphRefresh },
|
|
1332
1336
|
] = await Promise.all([
|
|
1333
1337
|
import('./codegraph/auto-context.js'),
|
|
1334
1338
|
import('./store/obs-store.js'),
|
|
1335
1339
|
import('./runtime/maintenance-jobs.js'),
|
|
1340
|
+
import('./runtime/lifecycle.js'),
|
|
1336
1341
|
]);
|
|
1337
1342
|
const observations = await getObservationStore().loadByProject(project.id, { status: 'active' });
|
|
1338
1343
|
const context = await buildAutoProjectContext({
|
|
@@ -1342,11 +1347,12 @@ export async function createMemorixServer(
|
|
|
1342
1347
|
task,
|
|
1343
1348
|
refresh: refresh ?? 'auto',
|
|
1344
1349
|
enqueueRefresh: () => {
|
|
1345
|
-
|
|
1350
|
+
enqueueCodegraphRefresh({
|
|
1351
|
+
dataDir: projectDir,
|
|
1346
1352
|
projectId: project.id,
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1353
|
+
source: 'project-context',
|
|
1354
|
+
maxFiles: 5_000,
|
|
1355
|
+
queue: new MaintenanceJobStore(projectDir),
|
|
1350
1356
|
});
|
|
1351
1357
|
},
|
|
1352
1358
|
});
|
|
@@ -1373,13 +1379,24 @@ export async function createMemorixServer(
|
|
|
1373
1379
|
const unresolved = requireResolvedProject('show CodeGraph Memory status for the current project');
|
|
1374
1380
|
if (unresolved) return unresolved;
|
|
1375
1381
|
|
|
1376
|
-
const { CodeGraphStore } = await
|
|
1382
|
+
const [{ CodeGraphStore }, { getResolvedConfig }, { inspectExternalCodeGraph }] = await Promise.all([
|
|
1383
|
+
import('./codegraph/store.js'),
|
|
1384
|
+
import('./config/resolved-config.js'),
|
|
1385
|
+
import('./codegraph/external-provider.js'),
|
|
1386
|
+
]);
|
|
1377
1387
|
const store = new CodeGraphStore();
|
|
1378
1388
|
await store.init(projectDir);
|
|
1379
1389
|
const status = store.status(project.id);
|
|
1390
|
+
const codegraphConfig = getResolvedConfig({ projectRoot: project.rootPath }).codegraph;
|
|
1391
|
+
const providerQuality = await inspectExternalCodeGraph({
|
|
1392
|
+
projectRoot: project.rootPath,
|
|
1393
|
+
mode: codegraphConfig.externalContext,
|
|
1394
|
+
command: codegraphConfig.externalCommand,
|
|
1395
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
1396
|
+
});
|
|
1380
1397
|
|
|
1381
1398
|
return {
|
|
1382
|
-
content: [{ type: 'text' as const, text: JSON.stringify(status, null, 2) }],
|
|
1399
|
+
content: [{ type: 'text' as const, text: JSON.stringify({ ...status, providerQuality: providerQuality.quality }, null, 2) }],
|
|
1383
1400
|
};
|
|
1384
1401
|
},
|
|
1385
1402
|
);
|
|
@@ -1405,21 +1422,28 @@ export async function createMemorixServer(
|
|
|
1405
1422
|
|
|
1406
1423
|
const [
|
|
1407
1424
|
{ CodeGraphStore },
|
|
1408
|
-
{ assembleContextPackForTask, buildContextPackPrompt },
|
|
1425
|
+
{ assembleContextPackForTask, attachTaskWorkset, buildContextPackPrompt },
|
|
1409
1426
|
{ getResolvedConfig },
|
|
1427
|
+
{ getExternalCodeGraphContext },
|
|
1410
1428
|
{ getObservationStore },
|
|
1429
|
+
{ collectCurrentProjectFacts, formatGitFact },
|
|
1430
|
+
{ resolveTaskLens },
|
|
1411
1431
|
] = await Promise.all([
|
|
1412
1432
|
import('./codegraph/store.js'),
|
|
1413
1433
|
import('./codegraph/context-pack.js'),
|
|
1414
1434
|
import('./config/resolved-config.js'),
|
|
1435
|
+
import('./codegraph/external-provider.js'),
|
|
1415
1436
|
import('./store/obs-store.js'),
|
|
1437
|
+
import('./codegraph/current-facts.js'),
|
|
1438
|
+
import('./codegraph/task-lens.js'),
|
|
1416
1439
|
]);
|
|
1417
1440
|
const store = new CodeGraphStore();
|
|
1418
1441
|
await store.init(projectDir);
|
|
1419
|
-
const
|
|
1442
|
+
const codegraphConfig = getResolvedConfig({ projectRoot: project.rootPath }).codegraph;
|
|
1443
|
+
const exclude = codegraphConfig.excludePatterns;
|
|
1420
1444
|
const observations = await getObservationStore().loadByProject(project.id, { status: 'active' });
|
|
1421
1445
|
observations.reverse();
|
|
1422
|
-
const
|
|
1446
|
+
const basePack = assembleContextPackForTask({
|
|
1423
1447
|
store,
|
|
1424
1448
|
projectId: project.id,
|
|
1425
1449
|
task,
|
|
@@ -1427,6 +1451,55 @@ export async function createMemorixServer(
|
|
|
1427
1451
|
limit: typeof limit === 'number' ? limit : 20,
|
|
1428
1452
|
exclude,
|
|
1429
1453
|
});
|
|
1454
|
+
const status = store.status(project.id);
|
|
1455
|
+
const currentFacts = collectCurrentProjectFacts({ project, now: new Date() });
|
|
1456
|
+
const snapshot = status.latestSnapshot;
|
|
1457
|
+
const external = await getExternalCodeGraphContext({
|
|
1458
|
+
projectRoot: project.rootPath,
|
|
1459
|
+
task,
|
|
1460
|
+
exclude,
|
|
1461
|
+
mode: codegraphConfig.externalContext,
|
|
1462
|
+
command: codegraphConfig.externalCommand,
|
|
1463
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
1464
|
+
});
|
|
1465
|
+
const worksetFacts: string[] = [];
|
|
1466
|
+
if (currentFacts.packageVersion) worksetFacts.push('Package version: ' + currentFacts.packageVersion);
|
|
1467
|
+
if (currentFacts.latestChangelog) {
|
|
1468
|
+
worksetFacts.push('Latest changelog: ' + currentFacts.latestChangelog.version
|
|
1469
|
+
+ (currentFacts.latestChangelog.date ? ' (' + currentFacts.latestChangelog.date + ')' : ''));
|
|
1470
|
+
}
|
|
1471
|
+
worksetFacts.push(formatGitFact(currentFacts.git));
|
|
1472
|
+
const codeState = snapshot
|
|
1473
|
+
? '- Code state: ' + (snapshot.baseRevision ? snapshot.baseRevision.slice(0, 12) : 'Git unavailable')
|
|
1474
|
+
+ ', ' + snapshot.worktreeState + ' worktree'
|
|
1475
|
+
+ ', epoch ' + snapshot.sourceEpoch
|
|
1476
|
+
: '- Code state: no completed snapshot yet';
|
|
1477
|
+
const pack = await attachTaskWorkset({
|
|
1478
|
+
pack: basePack,
|
|
1479
|
+
projectId: project.id,
|
|
1480
|
+
dataDir: projectDir,
|
|
1481
|
+
lens: resolveTaskLens(task).id,
|
|
1482
|
+
worktreeDirty: currentFacts.git.dirty,
|
|
1483
|
+
currentFacts: worksetFacts,
|
|
1484
|
+
codeState,
|
|
1485
|
+
...(snapshot
|
|
1486
|
+
? {
|
|
1487
|
+
snapshot: {
|
|
1488
|
+
id: snapshot.id,
|
|
1489
|
+
sourceEpoch: snapshot.sourceEpoch,
|
|
1490
|
+
worktreeState: snapshot.worktreeState,
|
|
1491
|
+
incomplete: snapshot.completeness.skippedOversizedFiles > 0
|
|
1492
|
+
|| (snapshot.completeness.unreadableFiles ?? 0) > 0
|
|
1493
|
+
|| snapshot.completeness.removalScanDeferred,
|
|
1494
|
+
},
|
|
1495
|
+
}
|
|
1496
|
+
: {}),
|
|
1497
|
+
...(external.outline ? { semanticCode: external.outline } : {}),
|
|
1498
|
+
providerQuality: external.quality,
|
|
1499
|
+
...(external.caution
|
|
1500
|
+
? { runtimeCautions: [{ kind: 'external-codegraph-fallback' as const, message: external.caution }] }
|
|
1501
|
+
: {}),
|
|
1502
|
+
});
|
|
1430
1503
|
const text = buildContextPackPrompt(pack);
|
|
1431
1504
|
|
|
1432
1505
|
return {
|
|
@@ -1435,6 +1508,339 @@ export async function createMemorixServer(
|
|
|
1435
1508
|
},
|
|
1436
1509
|
);
|
|
1437
1510
|
|
|
1511
|
+
server.registerTool(
|
|
1512
|
+
'memorix_knowledge',
|
|
1513
|
+
{
|
|
1514
|
+
title: 'Knowledge Workspace',
|
|
1515
|
+
description:
|
|
1516
|
+
'Manage the reviewable project Knowledge Workspace. Use it only for deliberate knowledge operations: initialize a local or versioned workspace, review source-backed claims, compile proposals, lint, apply a reviewed proposal, or manage canonical workflows. It is intentionally absent from the default micro/lite profiles.',
|
|
1517
|
+
inputSchema: {
|
|
1518
|
+
action: z.enum([
|
|
1519
|
+
'workspace_init',
|
|
1520
|
+
'status',
|
|
1521
|
+
'claim_list',
|
|
1522
|
+
'claim_review',
|
|
1523
|
+
'compile',
|
|
1524
|
+
'lint',
|
|
1525
|
+
'proposal_apply',
|
|
1526
|
+
'workflow_import',
|
|
1527
|
+
'workflow_list',
|
|
1528
|
+
'workflow_select',
|
|
1529
|
+
'workflow_preview',
|
|
1530
|
+
'workflow_apply',
|
|
1531
|
+
'workflow_run',
|
|
1532
|
+
]).describe('Knowledge operation to perform'),
|
|
1533
|
+
mode: z.enum(['local', 'versioned']).optional().default('local').describe('Workspace mode; versioned writes require an explicit project path during workspace_init'),
|
|
1534
|
+
path: z.string().optional().describe('Explicit versioned workspace path, used only by workspace_init'),
|
|
1535
|
+
proposalId: z.string().optional().describe('Pending proposal id for proposal_apply'),
|
|
1536
|
+
allowManualOverwrite: z.boolean().optional().default(false).describe('Explicitly allow proposal_apply to replace a manually edited page'),
|
|
1537
|
+
claimId: z.string().optional().describe('Source-backed claim id for claim_review'),
|
|
1538
|
+
claimReviewState: z.enum(['approved', 'rejected']).optional().describe('Deliberate review verdict for claim_review'),
|
|
1539
|
+
reviewDetail: z.string().max(2_000).optional().describe('Evidence check performed before approving or rejecting a claim'),
|
|
1540
|
+
workflowId: z.string().optional().describe('Canonical workflow id for workflow preview, apply, or run'),
|
|
1541
|
+
agent: z.string().optional().describe('Target agent for a workflow adapter'),
|
|
1542
|
+
task: z.string().optional().describe('Task text for workflow selection or a workflow run'),
|
|
1543
|
+
outcome: z.enum(['passed', 'failed', 'cancelled', 'in-progress']).optional().describe('Workflow run outcome'),
|
|
1544
|
+
verificationVerdict: z.enum(['passed', 'failed', 'not-run']).optional().describe('Workflow run verification verdict'),
|
|
1545
|
+
failureReason: z.string().optional().describe('Sanitized workflow failure reason'),
|
|
1546
|
+
startingSnapshotId: z.string().optional().describe('Code snapshot id present when a workflow run began'),
|
|
1547
|
+
evidenceIds: z.array(z.string()).max(24).optional().describe('Selected evidence ids for a workflow run'),
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
async ({
|
|
1551
|
+
action,
|
|
1552
|
+
mode,
|
|
1553
|
+
path: workspacePath,
|
|
1554
|
+
proposalId,
|
|
1555
|
+
allowManualOverwrite,
|
|
1556
|
+
claimId,
|
|
1557
|
+
claimReviewState,
|
|
1558
|
+
reviewDetail,
|
|
1559
|
+
workflowId,
|
|
1560
|
+
agent,
|
|
1561
|
+
task,
|
|
1562
|
+
outcome,
|
|
1563
|
+
verificationVerdict,
|
|
1564
|
+
failureReason,
|
|
1565
|
+
startingSnapshotId,
|
|
1566
|
+
evidenceIds,
|
|
1567
|
+
}) => {
|
|
1568
|
+
const unresolved = requireResolvedProject('manage the Knowledge Workspace for the current project');
|
|
1569
|
+
if (unresolved) return unresolved;
|
|
1570
|
+
|
|
1571
|
+
const text = (value: unknown, isError = false) => ({
|
|
1572
|
+
content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }],
|
|
1573
|
+
...(isError ? { isError: true as const } : {}),
|
|
1574
|
+
});
|
|
1575
|
+
const modeValue = mode ?? 'local';
|
|
1576
|
+
const requireText = (value: string | undefined, field: string): string | undefined => {
|
|
1577
|
+
const normalized = value?.trim();
|
|
1578
|
+
return normalized ? normalized : undefined;
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
const [
|
|
1582
|
+
{ ClaimStore },
|
|
1583
|
+
{ reviewClaim },
|
|
1584
|
+
{ CodeGraphStore },
|
|
1585
|
+
{ initializeKnowledgeWorkspace, loadKnowledgeWorkspace },
|
|
1586
|
+
{ KnowledgeWorkspaceStore },
|
|
1587
|
+
{ applyKnowledgeProposal, compileKnowledgeWorkspace, lintKnowledgeWorkspace },
|
|
1588
|
+
{ WorkflowStore },
|
|
1589
|
+
{
|
|
1590
|
+
applyWorkflowAdapter,
|
|
1591
|
+
importWindsurfWorkflows,
|
|
1592
|
+
previewWorkflowAdapter,
|
|
1593
|
+
recordWorkflowRun,
|
|
1594
|
+
selectWorkspaceWorkflows,
|
|
1595
|
+
syncCanonicalWorkflows,
|
|
1596
|
+
},
|
|
1597
|
+
] = await Promise.all([
|
|
1598
|
+
import('./knowledge/claim-store.js'),
|
|
1599
|
+
import('./knowledge/claims.js'),
|
|
1600
|
+
import('./codegraph/store.js'),
|
|
1601
|
+
import('./knowledge/workspace.js'),
|
|
1602
|
+
import('./knowledge/workspace-store.js'),
|
|
1603
|
+
import('./knowledge/wiki.js'),
|
|
1604
|
+
import('./knowledge/workflow-store.js'),
|
|
1605
|
+
import('./knowledge/workflows.js'),
|
|
1606
|
+
]);
|
|
1607
|
+
|
|
1608
|
+
if (action === 'workspace_init') {
|
|
1609
|
+
if (modeValue === 'versioned' && !requireText(workspacePath, 'path')) {
|
|
1610
|
+
return text({ error: 'path is required to initialize a versioned Knowledge Workspace.' }, true);
|
|
1611
|
+
}
|
|
1612
|
+
const workspace = await initializeKnowledgeWorkspace({
|
|
1613
|
+
projectId: project.id,
|
|
1614
|
+
dataDir: projectDir,
|
|
1615
|
+
mode: modeValue,
|
|
1616
|
+
...(modeValue === 'versioned'
|
|
1617
|
+
? { projectRoot: project.rootPath, rootPath: requireText(workspacePath, 'path')! }
|
|
1618
|
+
: {}),
|
|
1619
|
+
});
|
|
1620
|
+
return text({
|
|
1621
|
+
workspace: {
|
|
1622
|
+
id: workspace.id,
|
|
1623
|
+
mode: workspace.mode,
|
|
1624
|
+
rootPath: workspace.rootPath,
|
|
1625
|
+
status: workspace.status,
|
|
1626
|
+
},
|
|
1627
|
+
next: 'Compile creates reviewable proposals; it does not silently publish pages.',
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const workspace = await loadKnowledgeWorkspace({ projectId: project.id, dataDir: projectDir, mode: modeValue });
|
|
1632
|
+
if (!workspace) {
|
|
1633
|
+
return text({ error: 'Knowledge Workspace is not initialized. Run workspace_init first.' }, true);
|
|
1634
|
+
}
|
|
1635
|
+
const claims = new ClaimStore();
|
|
1636
|
+
const workspaceStore = new KnowledgeWorkspaceStore();
|
|
1637
|
+
await Promise.all([claims.init(projectDir), workspaceStore.init(projectDir)]);
|
|
1638
|
+
|
|
1639
|
+
if (action === 'status') {
|
|
1640
|
+
const pages = workspaceStore.listPages(workspace.id);
|
|
1641
|
+
const pending = workspaceStore.listProposals(workspace.id, 'pending');
|
|
1642
|
+
return text({
|
|
1643
|
+
workspace: {
|
|
1644
|
+
id: workspace.id,
|
|
1645
|
+
mode: workspace.mode,
|
|
1646
|
+
rootPath: workspace.rootPath,
|
|
1647
|
+
status: workspace.status,
|
|
1648
|
+
publishedPages: pages.filter(page => page.status === 'active').length,
|
|
1649
|
+
pendingProposals: pending.map(proposal => ({
|
|
1650
|
+
id: proposal.id,
|
|
1651
|
+
targetPath: proposal.targetPath,
|
|
1652
|
+
reason: proposal.reason,
|
|
1653
|
+
createdAt: proposal.createdAt,
|
|
1654
|
+
})),
|
|
1655
|
+
reviewableClaims: claims.listClaims(project.id, { limit: 100 })
|
|
1656
|
+
.filter(claim => claim.reviewState === 'needs-review')
|
|
1657
|
+
.map(claim => ({ id: claim.id, subject: claim.subject, predicate: claim.predicate, objectValue: claim.objectValue })),
|
|
1658
|
+
},
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
if (action === 'claim_list') {
|
|
1663
|
+
return text({
|
|
1664
|
+
claims: claims.listClaims(project.id, { limit: 100 }).map(claim => ({
|
|
1665
|
+
id: claim.id,
|
|
1666
|
+
subject: claim.subject,
|
|
1667
|
+
predicate: claim.predicate,
|
|
1668
|
+
objectValue: claim.objectValue,
|
|
1669
|
+
status: claim.status,
|
|
1670
|
+
reviewState: claim.reviewState,
|
|
1671
|
+
origin: claim.origin,
|
|
1672
|
+
confidence: claim.confidence,
|
|
1673
|
+
evidenceCount: claims.listEvidence(claim.id).length,
|
|
1674
|
+
})),
|
|
1675
|
+
next: 'Approve only after checking the linked evidence. Rejected claims stay out of retrieval and publication.',
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
if (action === 'claim_review') {
|
|
1680
|
+
const requestedClaimId = requireText(claimId, 'claimId');
|
|
1681
|
+
const detail = requireText(reviewDetail, 'reviewDetail');
|
|
1682
|
+
if (!requestedClaimId || !claimReviewState || !detail) {
|
|
1683
|
+
return text({ error: 'claimId, claimReviewState, and reviewDetail are required for claim_review.' }, true);
|
|
1684
|
+
}
|
|
1685
|
+
const existing = claims.getClaim(requestedClaimId);
|
|
1686
|
+
if (!existing || existing.projectId !== project.id) {
|
|
1687
|
+
return text({ error: 'Claim was not found for the current project.' }, true);
|
|
1688
|
+
}
|
|
1689
|
+
const claim = reviewClaim(claims, {
|
|
1690
|
+
claimId: requestedClaimId,
|
|
1691
|
+
reviewState: claimReviewState,
|
|
1692
|
+
detail,
|
|
1693
|
+
});
|
|
1694
|
+
return text({
|
|
1695
|
+
claim: {
|
|
1696
|
+
id: claim.id,
|
|
1697
|
+
status: claim.status,
|
|
1698
|
+
reviewState: claim.reviewState,
|
|
1699
|
+
updatedAt: claim.updatedAt,
|
|
1700
|
+
},
|
|
1701
|
+
next: claim.reviewState === 'approved'
|
|
1702
|
+
? 'This approved source-backed claim can now be considered by knowledge compilation.'
|
|
1703
|
+
: 'This rejected claim is excluded from retrieval and knowledge compilation.',
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
if (action === 'compile') {
|
|
1708
|
+
const result = await compileKnowledgeWorkspace({ workspace, claims });
|
|
1709
|
+
return text({
|
|
1710
|
+
proposals: result.proposals.map(proposal => ({
|
|
1711
|
+
id: proposal.id,
|
|
1712
|
+
targetPath: proposal.targetPath,
|
|
1713
|
+
proposalPath: proposal.proposalPath,
|
|
1714
|
+
reason: proposal.reason,
|
|
1715
|
+
})),
|
|
1716
|
+
unchangedPublishedPages: result.published.map(page => page.relativePath),
|
|
1717
|
+
next: result.proposals.length ? 'Review a proposal, then use proposal_apply deliberately.' : undefined,
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (action === 'lint') {
|
|
1722
|
+
const codeStore = new CodeGraphStore();
|
|
1723
|
+
await codeStore.init(projectDir);
|
|
1724
|
+
const result = await lintKnowledgeWorkspace({ workspace, claims, codeStore });
|
|
1725
|
+
return text(result);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
if (action === 'proposal_apply') {
|
|
1729
|
+
const proposal = requireText(proposalId, 'proposalId');
|
|
1730
|
+
if (!proposal) return text({ error: 'proposalId is required for proposal_apply.' }, true);
|
|
1731
|
+
const result = await applyKnowledgeProposal({
|
|
1732
|
+
workspace,
|
|
1733
|
+
proposalId: proposal,
|
|
1734
|
+
allowManualOverwrite: !!allowManualOverwrite,
|
|
1735
|
+
});
|
|
1736
|
+
return text({
|
|
1737
|
+
proposal: { id: result.proposal.id, status: result.proposal.status },
|
|
1738
|
+
targetPath: result.targetPath,
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
const workflowStore = new WorkflowStore();
|
|
1743
|
+
await workflowStore.init(projectDir);
|
|
1744
|
+
const projectRoot = workspace.projectRoot ?? project.rootPath;
|
|
1745
|
+
|
|
1746
|
+
if (action === 'workflow_import') {
|
|
1747
|
+
const result = await importWindsurfWorkflows({ workspace, projectRoot });
|
|
1748
|
+
return text({
|
|
1749
|
+
imported: result.imported.map(workflow => ({
|
|
1750
|
+
id: workflow.id,
|
|
1751
|
+
title: workflow.title,
|
|
1752
|
+
sourcePath: workflow.sourcePath,
|
|
1753
|
+
importedFrom: workflow.importedFrom,
|
|
1754
|
+
verificationGates: workflow.verificationGates,
|
|
1755
|
+
})),
|
|
1756
|
+
skipped: result.skipped,
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
const synced = await syncCanonicalWorkflows(workspace);
|
|
1761
|
+
if (action === 'workflow_list') {
|
|
1762
|
+
return text({
|
|
1763
|
+
workflows: workflowStore.listWorkflows(workspace.id).map(workflow => ({
|
|
1764
|
+
id: workflow.id,
|
|
1765
|
+
title: workflow.title,
|
|
1766
|
+
status: workflow.status,
|
|
1767
|
+
taskLenses: workflow.taskLenses,
|
|
1768
|
+
sourcePath: workflow.sourcePath,
|
|
1769
|
+
importedFrom: workflow.importedFrom,
|
|
1770
|
+
verificationGates: workflow.verificationGates,
|
|
1771
|
+
})),
|
|
1772
|
+
parseErrors: synced.errors,
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (action === 'workflow_select') {
|
|
1777
|
+
const selectedTask = requireText(task, 'task');
|
|
1778
|
+
if (!selectedTask) return text({ error: 'task is required for workflow_select.' }, true);
|
|
1779
|
+
const result = await selectWorkspaceWorkflows({ workspace, task: selectedTask });
|
|
1780
|
+
return text({
|
|
1781
|
+
selections: result.selections.map(selection => ({
|
|
1782
|
+
id: selection.workflow.id,
|
|
1783
|
+
title: selection.workflow.title,
|
|
1784
|
+
reasons: selection.reasons,
|
|
1785
|
+
firstPhase: selection.firstPhase.title,
|
|
1786
|
+
cautions: selection.cautions,
|
|
1787
|
+
})),
|
|
1788
|
+
parseErrors: result.errors,
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
const requestedWorkflowId = requireText(workflowId, 'workflowId');
|
|
1793
|
+
if (!requestedWorkflowId) return text({ error: 'workflowId is required for this workflow action.' }, true);
|
|
1794
|
+
const workflow = workflowStore.getWorkflow(requestedWorkflowId);
|
|
1795
|
+
if (!workflow || workflow.workspaceId !== workspace.id) {
|
|
1796
|
+
return text({ error: 'Workflow was not found for this Knowledge Workspace.' }, true);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (action === 'workflow_preview' || action === 'workflow_apply') {
|
|
1800
|
+
const targetAgent = requireText(agent, 'agent');
|
|
1801
|
+
if (!targetAgent) return text({ error: 'agent is required for a workflow adapter action.' }, true);
|
|
1802
|
+
const result = action === 'workflow_preview'
|
|
1803
|
+
? await previewWorkflowAdapter({ workflow, projectRoot, agent: targetAgent as any })
|
|
1804
|
+
: await applyWorkflowAdapter({ workflow, projectRoot, agent: targetAgent as any });
|
|
1805
|
+
return text({
|
|
1806
|
+
workflowId: workflow.id,
|
|
1807
|
+
agent: targetAgent,
|
|
1808
|
+
status: result.status,
|
|
1809
|
+
targetPath: result.targetPath,
|
|
1810
|
+
reason: result.reason,
|
|
1811
|
+
...(action === 'workflow_preview' && result.content ? { content: result.content } : {}),
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
if (action === 'workflow_run') {
|
|
1816
|
+
const runTask = requireText(task, 'task');
|
|
1817
|
+
if (!runTask) return text({ error: 'task is required for workflow_run.' }, true);
|
|
1818
|
+
if (!outcome) return text({ error: 'outcome is required for workflow_run.' }, true);
|
|
1819
|
+
const result = await recordWorkflowRun({
|
|
1820
|
+
workspace,
|
|
1821
|
+
run: {
|
|
1822
|
+
workflowId: workflow.id,
|
|
1823
|
+
projectId: project.id,
|
|
1824
|
+
task: runTask,
|
|
1825
|
+
outcome,
|
|
1826
|
+
...(verificationVerdict ? { verificationVerdict } : {}),
|
|
1827
|
+
...(requireText(failureReason, 'failureReason') ? { failureReason: requireText(failureReason, 'failureReason') } : {}),
|
|
1828
|
+
...(requireText(startingSnapshotId, 'startingSnapshotId') ? { startingSnapshotId: requireText(startingSnapshotId, 'startingSnapshotId') } : {}),
|
|
1829
|
+
selectedEvidence: [...new Set((evidenceIds ?? []).map(item => item.trim()).filter(Boolean))],
|
|
1830
|
+
},
|
|
1831
|
+
});
|
|
1832
|
+
return text({
|
|
1833
|
+
id: result.id,
|
|
1834
|
+
workflowId: result.workflowId,
|
|
1835
|
+
outcome: result.outcome,
|
|
1836
|
+
verificationVerdict: result.verificationVerdict,
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
return text({ error: 'Unsupported knowledge action.' }, true);
|
|
1841
|
+
},
|
|
1842
|
+
);
|
|
1843
|
+
|
|
1438
1844
|
/**
|
|
1439
1845
|
* memorix_resolve — Mark memories as resolved/completed
|
|
1440
1846
|
*
|
|
@@ -2399,15 +2805,11 @@ export async function createMemorixServer(
|
|
|
2399
2805
|
async function scopeGraphToProject(graph: { entities: any[]; relations: any[] }) {
|
|
2400
2806
|
const { getAllObservations } = await import('./memory/observations.js');
|
|
2401
2807
|
const allObs = await withFreshIndex(() => getAllObservations());
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
.map(o => o.entityName),
|
|
2808
|
+
const scoped = scopeKnowledgeGraphToProject(
|
|
2809
|
+
graph,
|
|
2810
|
+
allObs.filter(observation => observation.projectId === project.id),
|
|
2406
2811
|
);
|
|
2407
|
-
|
|
2408
|
-
const entityNameSet = new Set(entities.map((e: any) => e.name));
|
|
2409
|
-
const relations = graph.relations.filter((r: any) => entityNameSet.has(r.from) && entityNameSet.has(r.to));
|
|
2410
|
-
return { entities, relations };
|
|
2812
|
+
return { entities: scoped.entities, relations: scoped.relations };
|
|
2411
2813
|
}
|
|
2412
2814
|
|
|
2413
2815
|
/** read_graph — MCP Official compatible */
|
package/src/store/file-lock.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { promises as fs } from 'node:fs';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
12
13
|
import path from 'node:path';
|
|
13
14
|
|
|
14
15
|
/** Lock is considered stale after 10 seconds (process crash recovery) */
|
|
@@ -17,6 +18,7 @@ const LOCK_STALE_MS = 10_000;
|
|
|
17
18
|
const RETRY_INTERVAL_MS = 50;
|
|
18
19
|
/** Maximum retries before giving up (50ms × 60 = 3 seconds) */
|
|
19
20
|
const MAX_RETRIES = 60;
|
|
21
|
+
const atomicWriteTails = new Map<string, Promise<void>>();
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Acquire a lock file atomically.
|
|
@@ -93,8 +95,26 @@ export async function withFileLock<T>(projectDir: string, fn: () => Promise<T>):
|
|
|
93
95
|
* On most filesystems, rename() is atomic within the same directory,
|
|
94
96
|
* so readers always see either the old complete file or the new complete file.
|
|
95
97
|
*/
|
|
96
|
-
|
|
97
|
-
const tmpPath = filePath + `.tmp.${process.pid}`;
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
async function atomicWriteFileOnce(filePath: string, data: string): Promise<void> {
|
|
99
|
+
const tmpPath = filePath + `.tmp.${process.pid}.${randomUUID()}`;
|
|
100
|
+
try {
|
|
101
|
+
await fs.writeFile(tmpPath, data, 'utf-8');
|
|
102
|
+
await fs.rename(tmpPath, filePath);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
await fs.unlink(tmpPath).catch(() => {});
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Serialize same-process writes to one target. Windows refuses concurrent
|
|
111
|
+
* replacements of the same path even when each writer has its own temp file.
|
|
112
|
+
*/
|
|
113
|
+
export function atomicWriteFile(filePath: string, data: string): Promise<void> {
|
|
114
|
+
const previous = atomicWriteTails.get(filePath) ?? Promise.resolve();
|
|
115
|
+
const next = previous.catch(() => undefined).then(() => atomicWriteFileOnce(filePath, data));
|
|
116
|
+
atomicWriteTails.set(filePath, next);
|
|
117
|
+
return next.finally(() => {
|
|
118
|
+
if (atomicWriteTails.get(filePath) === next) atomicWriteTails.delete(filePath);
|
|
119
|
+
});
|
|
100
120
|
}
|