memorix 1.1.13 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/dist/cli/index.js +36313 -31189
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +50 -30
- package/dist/index.js +5348 -674
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +3661 -293
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +19 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +5346 -672
- package/dist/sdk.js.map +1 -1
- package/docs/1.2.0-CLAIM-LEDGER.md +72 -0
- package/docs/1.2.0-CODE-STATE.md +61 -0
- package/docs/1.2.0-DEVELOPMENT-CHARTER.md +255 -0
- package/docs/1.2.0-DYNAMIC-LIFECYCLE.md +71 -0
- package/docs/1.2.0-EVALUATION-HARNESS.md +51 -0
- package/docs/1.2.0-IMPLEMENTATION-PLAN.md +554 -0
- package/docs/1.2.0-KNOWLEDGE-WORKFLOW-RESEARCH.md +205 -0
- package/docs/1.2.0-KNOWLEDGE-WORKSPACE.md +68 -0
- package/docs/1.2.0-PRODUCT-STORY.md +234 -0
- package/docs/1.2.0-PROVIDER-QUALITY.md +189 -0
- package/docs/1.2.0-WORKFLOW-INHERITANCE.md +101 -0
- package/docs/1.2.0-WORKSET-RETRIEVAL.md +80 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +6 -3
- package/docs/API_REFERENCE.md +25 -6
- package/docs/CONFIGURATION.md +21 -3
- package/docs/README.md +17 -2
- package/docs/dev-log/progress.txt +120 -40
- package/llms-full.txt +16 -2
- package/llms.txt +9 -4
- package/package.json +3 -2
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/capability-map.ts +1 -0
- package/src/cli/commands/codegraph.ts +112 -9
- package/src/cli/commands/context.ts +2 -0
- package/src/cli/commands/doctor.ts +73 -4
- package/src/cli/commands/knowledge.ts +282 -0
- package/src/cli/commands/serve-http.ts +12 -1
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/App.tsx +1 -1
- package/src/cli/tui/Panels.tsx +8 -8
- package/src/cli/tui/theme.ts +1 -1
- package/src/cli/tui/views/GraphView.tsx +8 -7
- package/src/cli/tui/views/KnowledgeView.tsx +9 -9
- package/src/codegraph/auto-context.ts +171 -9
- package/src/codegraph/code-state.ts +95 -0
- package/src/codegraph/context-pack.ts +82 -1
- package/src/codegraph/external-provider.ts +581 -0
- package/src/codegraph/lite-provider.ts +64 -19
- package/src/codegraph/project-context.ts +9 -1
- package/src/codegraph/store.ts +154 -6
- package/src/codegraph/types.ts +117 -0
- package/src/config/resolved-config.ts +28 -0
- package/src/config/toml-loader.ts +3 -0
- package/src/config/yaml-loader.ts +6 -0
- package/src/dashboard/server.ts +15 -1
- package/src/evaluation/workset-evaluation.ts +120 -0
- package/src/hooks/handler.ts +48 -6
- package/src/knowledge/claim-store.ts +267 -0
- package/src/knowledge/claims.ts +537 -0
- package/src/knowledge/markdown.ts +129 -0
- package/src/knowledge/types.ts +157 -0
- package/src/knowledge/wiki.ts +524 -0
- package/src/knowledge/workflow-store.ts +168 -0
- package/src/knowledge/workflow-types.ts +95 -0
- package/src/knowledge/workflows.ts +743 -0
- package/src/knowledge/workset.ts +515 -0
- package/src/knowledge/workspace-store.ts +220 -0
- package/src/knowledge/workspace-types.ts +106 -0
- package/src/knowledge/workspace.ts +220 -0
- package/src/memory/observations.ts +19 -0
- package/src/runtime/control-plane-maintenance.ts +5 -0
- package/src/runtime/isolated-maintenance.ts +5 -0
- package/src/runtime/lifecycle-status.ts +102 -0
- package/src/runtime/lifecycle.ts +107 -0
- package/src/runtime/maintenance-jobs.ts +5 -0
- package/src/runtime/project-maintenance.ts +190 -0
- package/src/server/tool-profile.ts +3 -2
- package/src/server.ts +354 -14
- package/src/store/file-lock.ts +24 -4
- package/src/store/sqlite-db.ts +307 -0
- package/src/wiki/generator.ts +4 -2
- package/src/wiki/knowledge-graph.ts +7 -4
- package/src/wiki/types.ts +16 -4
package/src/server.ts
CHANGED
|
@@ -469,10 +469,11 @@ export async function createMemorixServer(
|
|
|
469
469
|
let maintenanceWorker: { stop(): void } | null = null;
|
|
470
470
|
const startProjectMaintenanceWorker = async (autoCleanup = false): Promise<void> => {
|
|
471
471
|
maintenanceWorker?.stop();
|
|
472
|
-
const [{ MaintenanceJobStore, MaintenanceJobWorker }, maintenance, observations] = await Promise.all([
|
|
472
|
+
const [{ MaintenanceJobStore, MaintenanceJobWorker }, maintenance, observations, lifecycle] = await Promise.all([
|
|
473
473
|
import('./runtime/maintenance-jobs.js'),
|
|
474
474
|
import('./runtime/project-maintenance.js'),
|
|
475
475
|
import('./memory/observations.js'),
|
|
476
|
+
import('./runtime/lifecycle.js'),
|
|
476
477
|
]);
|
|
477
478
|
const queue = new MaintenanceJobStore(projectDir);
|
|
478
479
|
const vectorStatus = observations.getVectorStatus(project.id);
|
|
@@ -509,11 +510,12 @@ export async function createMemorixServer(
|
|
|
509
510
|
|| !Number.isFinite(indexedAt)
|
|
510
511
|
|| Date.now() - indexedAt > 10 * 60_000;
|
|
511
512
|
if (needsRefresh) {
|
|
512
|
-
|
|
513
|
+
lifecycle.enqueueCodegraphRefresh({
|
|
514
|
+
dataDir: projectDir,
|
|
513
515
|
projectId: project.id,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
516
|
+
source: 'startup',
|
|
517
|
+
maxFiles: 5_000,
|
|
518
|
+
queue,
|
|
517
519
|
});
|
|
518
520
|
}
|
|
519
521
|
} catch {
|
|
@@ -1329,10 +1331,12 @@ export async function createMemorixServer(
|
|
|
1329
1331
|
},
|
|
1330
1332
|
{ getObservationStore },
|
|
1331
1333
|
{ MaintenanceJobStore },
|
|
1334
|
+
{ enqueueCodegraphRefresh },
|
|
1332
1335
|
] = await Promise.all([
|
|
1333
1336
|
import('./codegraph/auto-context.js'),
|
|
1334
1337
|
import('./store/obs-store.js'),
|
|
1335
1338
|
import('./runtime/maintenance-jobs.js'),
|
|
1339
|
+
import('./runtime/lifecycle.js'),
|
|
1336
1340
|
]);
|
|
1337
1341
|
const observations = await getObservationStore().loadByProject(project.id, { status: 'active' });
|
|
1338
1342
|
const context = await buildAutoProjectContext({
|
|
@@ -1342,11 +1346,12 @@ export async function createMemorixServer(
|
|
|
1342
1346
|
task,
|
|
1343
1347
|
refresh: refresh ?? 'auto',
|
|
1344
1348
|
enqueueRefresh: () => {
|
|
1345
|
-
|
|
1349
|
+
enqueueCodegraphRefresh({
|
|
1350
|
+
dataDir: projectDir,
|
|
1346
1351
|
projectId: project.id,
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1352
|
+
source: 'project-context',
|
|
1353
|
+
maxFiles: 5_000,
|
|
1354
|
+
queue: new MaintenanceJobStore(projectDir),
|
|
1350
1355
|
});
|
|
1351
1356
|
},
|
|
1352
1357
|
});
|
|
@@ -1373,13 +1378,24 @@ export async function createMemorixServer(
|
|
|
1373
1378
|
const unresolved = requireResolvedProject('show CodeGraph Memory status for the current project');
|
|
1374
1379
|
if (unresolved) return unresolved;
|
|
1375
1380
|
|
|
1376
|
-
const { CodeGraphStore } = await
|
|
1381
|
+
const [{ CodeGraphStore }, { getResolvedConfig }, { inspectExternalCodeGraph }] = await Promise.all([
|
|
1382
|
+
import('./codegraph/store.js'),
|
|
1383
|
+
import('./config/resolved-config.js'),
|
|
1384
|
+
import('./codegraph/external-provider.js'),
|
|
1385
|
+
]);
|
|
1377
1386
|
const store = new CodeGraphStore();
|
|
1378
1387
|
await store.init(projectDir);
|
|
1379
1388
|
const status = store.status(project.id);
|
|
1389
|
+
const codegraphConfig = getResolvedConfig({ projectRoot: project.rootPath }).codegraph;
|
|
1390
|
+
const providerQuality = await inspectExternalCodeGraph({
|
|
1391
|
+
projectRoot: project.rootPath,
|
|
1392
|
+
mode: codegraphConfig.externalContext,
|
|
1393
|
+
command: codegraphConfig.externalCommand,
|
|
1394
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
1395
|
+
});
|
|
1380
1396
|
|
|
1381
1397
|
return {
|
|
1382
|
-
content: [{ type: 'text' as const, text: JSON.stringify(status, null, 2) }],
|
|
1398
|
+
content: [{ type: 'text' as const, text: JSON.stringify({ ...status, providerQuality: providerQuality.quality }, null, 2) }],
|
|
1383
1399
|
};
|
|
1384
1400
|
},
|
|
1385
1401
|
);
|
|
@@ -1405,21 +1421,28 @@ export async function createMemorixServer(
|
|
|
1405
1421
|
|
|
1406
1422
|
const [
|
|
1407
1423
|
{ CodeGraphStore },
|
|
1408
|
-
{ assembleContextPackForTask, buildContextPackPrompt },
|
|
1424
|
+
{ assembleContextPackForTask, attachTaskWorkset, buildContextPackPrompt },
|
|
1409
1425
|
{ getResolvedConfig },
|
|
1426
|
+
{ getExternalCodeGraphContext },
|
|
1410
1427
|
{ getObservationStore },
|
|
1428
|
+
{ collectCurrentProjectFacts },
|
|
1429
|
+
{ resolveTaskLens },
|
|
1411
1430
|
] = await Promise.all([
|
|
1412
1431
|
import('./codegraph/store.js'),
|
|
1413
1432
|
import('./codegraph/context-pack.js'),
|
|
1414
1433
|
import('./config/resolved-config.js'),
|
|
1434
|
+
import('./codegraph/external-provider.js'),
|
|
1415
1435
|
import('./store/obs-store.js'),
|
|
1436
|
+
import('./codegraph/current-facts.js'),
|
|
1437
|
+
import('./codegraph/task-lens.js'),
|
|
1416
1438
|
]);
|
|
1417
1439
|
const store = new CodeGraphStore();
|
|
1418
1440
|
await store.init(projectDir);
|
|
1419
|
-
const
|
|
1441
|
+
const codegraphConfig = getResolvedConfig({ projectRoot: project.rootPath }).codegraph;
|
|
1442
|
+
const exclude = codegraphConfig.excludePatterns;
|
|
1420
1443
|
const observations = await getObservationStore().loadByProject(project.id, { status: 'active' });
|
|
1421
1444
|
observations.reverse();
|
|
1422
|
-
const
|
|
1445
|
+
const basePack = assembleContextPackForTask({
|
|
1423
1446
|
store,
|
|
1424
1447
|
projectId: project.id,
|
|
1425
1448
|
task,
|
|
@@ -1427,6 +1450,56 @@ export async function createMemorixServer(
|
|
|
1427
1450
|
limit: typeof limit === 'number' ? limit : 20,
|
|
1428
1451
|
exclude,
|
|
1429
1452
|
});
|
|
1453
|
+
const status = store.status(project.id);
|
|
1454
|
+
const currentFacts = collectCurrentProjectFacts({ project, now: new Date() });
|
|
1455
|
+
const snapshot = status.latestSnapshot;
|
|
1456
|
+
const external = await getExternalCodeGraphContext({
|
|
1457
|
+
projectRoot: project.rootPath,
|
|
1458
|
+
task,
|
|
1459
|
+
exclude,
|
|
1460
|
+
mode: codegraphConfig.externalContext,
|
|
1461
|
+
command: codegraphConfig.externalCommand,
|
|
1462
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
1463
|
+
});
|
|
1464
|
+
const worksetFacts: string[] = [];
|
|
1465
|
+
if (currentFacts.packageVersion) worksetFacts.push('Package version: ' + currentFacts.packageVersion);
|
|
1466
|
+
if (currentFacts.latestChangelog) {
|
|
1467
|
+
worksetFacts.push('Latest changelog: ' + currentFacts.latestChangelog.version
|
|
1468
|
+
+ (currentFacts.latestChangelog.date ? ' (' + currentFacts.latestChangelog.date + ')' : ''));
|
|
1469
|
+
}
|
|
1470
|
+
worksetFacts.push('Git: ' + (currentFacts.git.branch ? 'branch ' + currentFacts.git.branch + ', ' : '')
|
|
1471
|
+
+ (currentFacts.git.dirty ? 'dirty worktree' : 'clean worktree'));
|
|
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,273 @@ 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, inspect status, compile source-backed 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
|
+
'compile',
|
|
1522
|
+
'lint',
|
|
1523
|
+
'proposal_apply',
|
|
1524
|
+
'workflow_import',
|
|
1525
|
+
'workflow_list',
|
|
1526
|
+
'workflow_select',
|
|
1527
|
+
'workflow_preview',
|
|
1528
|
+
'workflow_apply',
|
|
1529
|
+
'workflow_run',
|
|
1530
|
+
]).describe('Knowledge operation to perform'),
|
|
1531
|
+
mode: z.enum(['local', 'versioned']).optional().default('local').describe('Workspace mode; versioned writes require an explicit project path during workspace_init'),
|
|
1532
|
+
path: z.string().optional().describe('Explicit versioned workspace path, used only by workspace_init'),
|
|
1533
|
+
proposalId: z.string().optional().describe('Pending proposal id for proposal_apply'),
|
|
1534
|
+
allowManualOverwrite: z.boolean().optional().default(false).describe('Explicitly allow proposal_apply to replace a manually edited page'),
|
|
1535
|
+
workflowId: z.string().optional().describe('Canonical workflow id for workflow preview, apply, or run'),
|
|
1536
|
+
agent: z.string().optional().describe('Target agent for a workflow adapter'),
|
|
1537
|
+
task: z.string().optional().describe('Task text for workflow selection or a workflow run'),
|
|
1538
|
+
outcome: z.enum(['passed', 'failed', 'cancelled', 'in-progress']).optional().describe('Workflow run outcome'),
|
|
1539
|
+
verificationVerdict: z.enum(['passed', 'failed', 'not-run']).optional().describe('Workflow run verification verdict'),
|
|
1540
|
+
failureReason: z.string().optional().describe('Sanitized workflow failure reason'),
|
|
1541
|
+
startingSnapshotId: z.string().optional().describe('Code snapshot id present when a workflow run began'),
|
|
1542
|
+
evidenceIds: z.array(z.string()).max(24).optional().describe('Selected evidence ids for a workflow run'),
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1545
|
+
async ({
|
|
1546
|
+
action,
|
|
1547
|
+
mode,
|
|
1548
|
+
path: workspacePath,
|
|
1549
|
+
proposalId,
|
|
1550
|
+
allowManualOverwrite,
|
|
1551
|
+
workflowId,
|
|
1552
|
+
agent,
|
|
1553
|
+
task,
|
|
1554
|
+
outcome,
|
|
1555
|
+
verificationVerdict,
|
|
1556
|
+
failureReason,
|
|
1557
|
+
startingSnapshotId,
|
|
1558
|
+
evidenceIds,
|
|
1559
|
+
}) => {
|
|
1560
|
+
const unresolved = requireResolvedProject('manage the Knowledge Workspace for the current project');
|
|
1561
|
+
if (unresolved) return unresolved;
|
|
1562
|
+
|
|
1563
|
+
const text = (value: unknown, isError = false) => ({
|
|
1564
|
+
content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }],
|
|
1565
|
+
...(isError ? { isError: true as const } : {}),
|
|
1566
|
+
});
|
|
1567
|
+
const modeValue = mode ?? 'local';
|
|
1568
|
+
const requireText = (value: string | undefined, field: string): string | undefined => {
|
|
1569
|
+
const normalized = value?.trim();
|
|
1570
|
+
return normalized ? normalized : undefined;
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
const [
|
|
1574
|
+
{ ClaimStore },
|
|
1575
|
+
{ CodeGraphStore },
|
|
1576
|
+
{ initializeKnowledgeWorkspace, loadKnowledgeWorkspace },
|
|
1577
|
+
{ KnowledgeWorkspaceStore },
|
|
1578
|
+
{ applyKnowledgeProposal, compileKnowledgeWorkspace, lintKnowledgeWorkspace },
|
|
1579
|
+
{ WorkflowStore },
|
|
1580
|
+
{
|
|
1581
|
+
applyWorkflowAdapter,
|
|
1582
|
+
importWindsurfWorkflows,
|
|
1583
|
+
previewWorkflowAdapter,
|
|
1584
|
+
recordWorkflowRun,
|
|
1585
|
+
selectWorkspaceWorkflows,
|
|
1586
|
+
syncCanonicalWorkflows,
|
|
1587
|
+
},
|
|
1588
|
+
] = await Promise.all([
|
|
1589
|
+
import('./knowledge/claim-store.js'),
|
|
1590
|
+
import('./codegraph/store.js'),
|
|
1591
|
+
import('./knowledge/workspace.js'),
|
|
1592
|
+
import('./knowledge/workspace-store.js'),
|
|
1593
|
+
import('./knowledge/wiki.js'),
|
|
1594
|
+
import('./knowledge/workflow-store.js'),
|
|
1595
|
+
import('./knowledge/workflows.js'),
|
|
1596
|
+
]);
|
|
1597
|
+
|
|
1598
|
+
if (action === 'workspace_init') {
|
|
1599
|
+
if (modeValue === 'versioned' && !requireText(workspacePath, 'path')) {
|
|
1600
|
+
return text({ error: 'path is required to initialize a versioned Knowledge Workspace.' }, true);
|
|
1601
|
+
}
|
|
1602
|
+
const workspace = await initializeKnowledgeWorkspace({
|
|
1603
|
+
projectId: project.id,
|
|
1604
|
+
dataDir: projectDir,
|
|
1605
|
+
mode: modeValue,
|
|
1606
|
+
...(modeValue === 'versioned'
|
|
1607
|
+
? { projectRoot: project.rootPath, rootPath: requireText(workspacePath, 'path')! }
|
|
1608
|
+
: {}),
|
|
1609
|
+
});
|
|
1610
|
+
return text({
|
|
1611
|
+
workspace: {
|
|
1612
|
+
id: workspace.id,
|
|
1613
|
+
mode: workspace.mode,
|
|
1614
|
+
rootPath: workspace.rootPath,
|
|
1615
|
+
status: workspace.status,
|
|
1616
|
+
},
|
|
1617
|
+
next: 'Compile creates reviewable proposals; it does not silently publish pages.',
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const workspace = await loadKnowledgeWorkspace({ projectId: project.id, dataDir: projectDir, mode: modeValue });
|
|
1622
|
+
if (!workspace) {
|
|
1623
|
+
return text({ error: 'Knowledge Workspace is not initialized. Run workspace_init first.' }, true);
|
|
1624
|
+
}
|
|
1625
|
+
const claims = new ClaimStore();
|
|
1626
|
+
const workspaceStore = new KnowledgeWorkspaceStore();
|
|
1627
|
+
await Promise.all([claims.init(projectDir), workspaceStore.init(projectDir)]);
|
|
1628
|
+
|
|
1629
|
+
if (action === 'status') {
|
|
1630
|
+
const pages = workspaceStore.listPages(workspace.id);
|
|
1631
|
+
const pending = workspaceStore.listProposals(workspace.id, 'pending');
|
|
1632
|
+
return text({
|
|
1633
|
+
workspace: {
|
|
1634
|
+
id: workspace.id,
|
|
1635
|
+
mode: workspace.mode,
|
|
1636
|
+
rootPath: workspace.rootPath,
|
|
1637
|
+
status: workspace.status,
|
|
1638
|
+
publishedPages: pages.filter(page => page.status === 'active').length,
|
|
1639
|
+
pendingProposals: pending.map(proposal => ({
|
|
1640
|
+
id: proposal.id,
|
|
1641
|
+
targetPath: proposal.targetPath,
|
|
1642
|
+
reason: proposal.reason,
|
|
1643
|
+
createdAt: proposal.createdAt,
|
|
1644
|
+
})),
|
|
1645
|
+
},
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
if (action === 'compile') {
|
|
1650
|
+
const result = await compileKnowledgeWorkspace({ workspace, claims });
|
|
1651
|
+
return text({
|
|
1652
|
+
proposals: result.proposals.map(proposal => ({
|
|
1653
|
+
id: proposal.id,
|
|
1654
|
+
targetPath: proposal.targetPath,
|
|
1655
|
+
proposalPath: proposal.proposalPath,
|
|
1656
|
+
reason: proposal.reason,
|
|
1657
|
+
})),
|
|
1658
|
+
unchangedPublishedPages: result.published.map(page => page.relativePath),
|
|
1659
|
+
next: result.proposals.length ? 'Review a proposal, then use proposal_apply deliberately.' : undefined,
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if (action === 'lint') {
|
|
1664
|
+
const codeStore = new CodeGraphStore();
|
|
1665
|
+
await codeStore.init(projectDir);
|
|
1666
|
+
const result = await lintKnowledgeWorkspace({ workspace, claims, codeStore });
|
|
1667
|
+
return text(result);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (action === 'proposal_apply') {
|
|
1671
|
+
const proposal = requireText(proposalId, 'proposalId');
|
|
1672
|
+
if (!proposal) return text({ error: 'proposalId is required for proposal_apply.' }, true);
|
|
1673
|
+
const result = await applyKnowledgeProposal({
|
|
1674
|
+
workspace,
|
|
1675
|
+
proposalId: proposal,
|
|
1676
|
+
allowManualOverwrite: !!allowManualOverwrite,
|
|
1677
|
+
});
|
|
1678
|
+
return text({
|
|
1679
|
+
proposal: { id: result.proposal.id, status: result.proposal.status },
|
|
1680
|
+
targetPath: result.targetPath,
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
const workflowStore = new WorkflowStore();
|
|
1685
|
+
await workflowStore.init(projectDir);
|
|
1686
|
+
const projectRoot = workspace.projectRoot ?? project.rootPath;
|
|
1687
|
+
|
|
1688
|
+
if (action === 'workflow_import') {
|
|
1689
|
+
const result = await importWindsurfWorkflows({ workspace, projectRoot });
|
|
1690
|
+
return text({
|
|
1691
|
+
imported: result.imported.map(workflow => ({ id: workflow.id, title: workflow.title, sourcePath: workflow.sourcePath })),
|
|
1692
|
+
skipped: result.skipped,
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
const synced = await syncCanonicalWorkflows(workspace);
|
|
1697
|
+
if (action === 'workflow_list') {
|
|
1698
|
+
return text({
|
|
1699
|
+
workflows: workflowStore.listWorkflows(workspace.id).map(workflow => ({
|
|
1700
|
+
id: workflow.id,
|
|
1701
|
+
title: workflow.title,
|
|
1702
|
+
status: workflow.status,
|
|
1703
|
+
taskLenses: workflow.taskLenses,
|
|
1704
|
+
sourcePath: workflow.sourcePath,
|
|
1705
|
+
})),
|
|
1706
|
+
parseErrors: synced.errors,
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
if (action === 'workflow_select') {
|
|
1711
|
+
const selectedTask = requireText(task, 'task');
|
|
1712
|
+
if (!selectedTask) return text({ error: 'task is required for workflow_select.' }, true);
|
|
1713
|
+
const result = await selectWorkspaceWorkflows({ workspace, task: selectedTask });
|
|
1714
|
+
return text({
|
|
1715
|
+
selections: result.selections.map(selection => ({
|
|
1716
|
+
id: selection.workflow.id,
|
|
1717
|
+
title: selection.workflow.title,
|
|
1718
|
+
reasons: selection.reasons,
|
|
1719
|
+
firstPhase: selection.firstPhase.title,
|
|
1720
|
+
cautions: selection.cautions,
|
|
1721
|
+
})),
|
|
1722
|
+
parseErrors: result.errors,
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
const requestedWorkflowId = requireText(workflowId, 'workflowId');
|
|
1727
|
+
if (!requestedWorkflowId) return text({ error: 'workflowId is required for this workflow action.' }, true);
|
|
1728
|
+
const workflow = workflowStore.getWorkflow(requestedWorkflowId);
|
|
1729
|
+
if (!workflow || workflow.workspaceId !== workspace.id) {
|
|
1730
|
+
return text({ error: 'Workflow was not found for this Knowledge Workspace.' }, true);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
if (action === 'workflow_preview' || action === 'workflow_apply') {
|
|
1734
|
+
const targetAgent = requireText(agent, 'agent');
|
|
1735
|
+
if (!targetAgent) return text({ error: 'agent is required for a workflow adapter action.' }, true);
|
|
1736
|
+
const result = action === 'workflow_preview'
|
|
1737
|
+
? await previewWorkflowAdapter({ workflow, projectRoot, agent: targetAgent as any })
|
|
1738
|
+
: await applyWorkflowAdapter({ workflow, projectRoot, agent: targetAgent as any });
|
|
1739
|
+
return text({
|
|
1740
|
+
workflowId: workflow.id,
|
|
1741
|
+
agent: targetAgent,
|
|
1742
|
+
status: result.status,
|
|
1743
|
+
targetPath: result.targetPath,
|
|
1744
|
+
reason: result.reason,
|
|
1745
|
+
...(action === 'workflow_preview' && result.content ? { content: result.content } : {}),
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (action === 'workflow_run') {
|
|
1750
|
+
const runTask = requireText(task, 'task');
|
|
1751
|
+
if (!runTask) return text({ error: 'task is required for workflow_run.' }, true);
|
|
1752
|
+
if (!outcome) return text({ error: 'outcome is required for workflow_run.' }, true);
|
|
1753
|
+
const result = await recordWorkflowRun({
|
|
1754
|
+
workspace,
|
|
1755
|
+
run: {
|
|
1756
|
+
workflowId: workflow.id,
|
|
1757
|
+
projectId: project.id,
|
|
1758
|
+
task: runTask,
|
|
1759
|
+
outcome,
|
|
1760
|
+
...(verificationVerdict ? { verificationVerdict } : {}),
|
|
1761
|
+
...(requireText(failureReason, 'failureReason') ? { failureReason: requireText(failureReason, 'failureReason') } : {}),
|
|
1762
|
+
...(requireText(startingSnapshotId, 'startingSnapshotId') ? { startingSnapshotId: requireText(startingSnapshotId, 'startingSnapshotId') } : {}),
|
|
1763
|
+
selectedEvidence: [...new Set((evidenceIds ?? []).map(item => item.trim()).filter(Boolean))],
|
|
1764
|
+
},
|
|
1765
|
+
});
|
|
1766
|
+
return text({
|
|
1767
|
+
id: result.id,
|
|
1768
|
+
workflowId: result.workflowId,
|
|
1769
|
+
outcome: result.outcome,
|
|
1770
|
+
verificationVerdict: result.verificationVerdict,
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
return text({ error: 'Unsupported knowledge action.' }, true);
|
|
1775
|
+
},
|
|
1776
|
+
);
|
|
1777
|
+
|
|
1438
1778
|
/**
|
|
1439
1779
|
* memorix_resolve — Mark memories as resolved/completed
|
|
1440
1780
|
*
|
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
|
}
|