memorix 1.1.12 → 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.
Files changed (94) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +5 -2
  3. package/README.zh-CN.md +5 -2
  4. package/dist/cli/index.js +36575 -31127
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/dashboard/static/app.js +50 -30
  7. package/dist/index.js +5348 -674
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +3661 -293
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +28 -0
  13. package/dist/memcode-runtime/package.json +1 -0
  14. package/dist/sdk.d.ts +1 -1
  15. package/dist/sdk.js +5346 -672
  16. package/dist/sdk.js.map +1 -1
  17. package/docs/1.2.0-CLAIM-LEDGER.md +72 -0
  18. package/docs/1.2.0-CODE-STATE.md +61 -0
  19. package/docs/1.2.0-DEVELOPMENT-CHARTER.md +255 -0
  20. package/docs/1.2.0-DYNAMIC-LIFECYCLE.md +71 -0
  21. package/docs/1.2.0-EVALUATION-HARNESS.md +51 -0
  22. package/docs/1.2.0-IMPLEMENTATION-PLAN.md +554 -0
  23. package/docs/1.2.0-KNOWLEDGE-WORKFLOW-RESEARCH.md +205 -0
  24. package/docs/1.2.0-KNOWLEDGE-WORKSPACE.md +68 -0
  25. package/docs/1.2.0-PRODUCT-STORY.md +234 -0
  26. package/docs/1.2.0-PROVIDER-QUALITY.md +189 -0
  27. package/docs/1.2.0-WORKFLOW-INHERITANCE.md +101 -0
  28. package/docs/1.2.0-WORKSET-RETRIEVAL.md +80 -0
  29. package/docs/AGENT_OPERATOR_PLAYBOOK.md +7 -4
  30. package/docs/API_REFERENCE.md +25 -6
  31. package/docs/CONFIGURATION.md +21 -3
  32. package/docs/DEVELOPMENT.md +3 -10
  33. package/docs/INTEGRATIONS.md +1 -1
  34. package/docs/README.md +17 -2
  35. package/docs/SETUP.md +3 -1
  36. package/docs/dev-log/progress.txt +115 -63
  37. package/llms-full.txt +16 -2
  38. package/llms.txt +9 -4
  39. package/package.json +3 -2
  40. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  41. package/src/cli/capability-map.ts +1 -0
  42. package/src/cli/commands/agent-integrations.ts +384 -2
  43. package/src/cli/commands/codegraph.ts +112 -9
  44. package/src/cli/commands/context.ts +2 -0
  45. package/src/cli/commands/doctor.ts +73 -4
  46. package/src/cli/commands/knowledge.ts +282 -0
  47. package/src/cli/commands/serve-http.ts +12 -1
  48. package/src/cli/commands/setup.ts +13 -1
  49. package/src/cli/index.ts +3 -1
  50. package/src/cli/tui/App.tsx +1 -1
  51. package/src/cli/tui/Panels.tsx +8 -8
  52. package/src/cli/tui/theme.ts +1 -1
  53. package/src/cli/tui/views/GraphView.tsx +8 -7
  54. package/src/cli/tui/views/KnowledgeView.tsx +9 -9
  55. package/src/codegraph/auto-context.ts +171 -9
  56. package/src/codegraph/code-state.ts +95 -0
  57. package/src/codegraph/context-pack.ts +82 -1
  58. package/src/codegraph/external-provider.ts +581 -0
  59. package/src/codegraph/lite-provider.ts +64 -19
  60. package/src/codegraph/project-context.ts +9 -1
  61. package/src/codegraph/store.ts +154 -6
  62. package/src/codegraph/types.ts +117 -0
  63. package/src/config/resolved-config.ts +28 -0
  64. package/src/config/toml-loader.ts +3 -0
  65. package/src/config/yaml-loader.ts +6 -0
  66. package/src/dashboard/server.ts +15 -1
  67. package/src/evaluation/workset-evaluation.ts +120 -0
  68. package/src/hooks/handler.ts +48 -6
  69. package/src/knowledge/claim-store.ts +267 -0
  70. package/src/knowledge/claims.ts +537 -0
  71. package/src/knowledge/markdown.ts +129 -0
  72. package/src/knowledge/types.ts +157 -0
  73. package/src/knowledge/wiki.ts +524 -0
  74. package/src/knowledge/workflow-store.ts +168 -0
  75. package/src/knowledge/workflow-types.ts +95 -0
  76. package/src/knowledge/workflows.ts +743 -0
  77. package/src/knowledge/workset.ts +515 -0
  78. package/src/knowledge/workspace-store.ts +220 -0
  79. package/src/knowledge/workspace-types.ts +106 -0
  80. package/src/knowledge/workspace.ts +220 -0
  81. package/src/memory/observations.ts +19 -0
  82. package/src/runtime/control-plane-maintenance.ts +5 -0
  83. package/src/runtime/isolated-maintenance.ts +5 -0
  84. package/src/runtime/lifecycle-status.ts +102 -0
  85. package/src/runtime/lifecycle.ts +107 -0
  86. package/src/runtime/maintenance-jobs.ts +5 -0
  87. package/src/runtime/project-maintenance.ts +190 -0
  88. package/src/server/tool-profile.ts +3 -2
  89. package/src/server.ts +354 -14
  90. package/src/store/file-lock.ts +24 -4
  91. package/src/store/sqlite-db.ts +307 -0
  92. package/src/wiki/generator.ts +4 -2
  93. package/src/wiki/knowledge-graph.ts +7 -4
  94. package/src/wiki/types.ts +16 -4
@@ -319,9 +319,11 @@ export default defineCommand({
319
319
  const { CodeGraphStore } = await import('../../codegraph/store.js');
320
320
  const { buildProjectContextOverview } = await import('../../codegraph/project-context.js');
321
321
  const { getResolvedConfig } = await import('../../config/resolved-config.js');
322
+ const { inspectExternalCodeGraph } = await import('../../codegraph/external-provider.js');
322
323
  const codeStore = new CodeGraphStore();
323
324
  await codeStore.init(dataDir);
324
- const exclude = getResolvedConfig({ projectRoot: projectRoot || process.cwd() }).codegraph.excludePatterns;
325
+ const codegraphConfig = getResolvedConfig({ projectRoot: projectRoot || process.cwd() }).codegraph;
326
+ const exclude = codegraphConfig.excludePatterns;
325
327
  const overview = buildProjectContextOverview({
326
328
  project: {
327
329
  id: projectId,
@@ -335,6 +337,12 @@ export default defineCommand({
335
337
  const languageText = overview.code.languages.length > 0
336
338
  ? overview.code.languages.map((item: any) => `${item.language} ${item.files}`).join(', ')
337
339
  : 'none indexed yet';
340
+ const providerQuality = await inspectExternalCodeGraph({
341
+ projectRoot: projectRoot || process.cwd(),
342
+ mode: codegraphConfig.externalContext,
343
+ command: codegraphConfig.externalCommand,
344
+ timeoutMs: codegraphConfig.externalTimeoutMs,
345
+ });
338
346
 
339
347
  if (overview.code.files > 0) {
340
348
  lines.push(ok(`Code memory: ${overview.code.files} files, ${overview.code.symbols} symbols, ${overview.code.refs} memory links`));
@@ -344,6 +352,15 @@ export default defineCommand({
344
352
  tips.push('Run `memorix codegraph refresh` once to seed project context.');
345
353
  }
346
354
 
355
+ lines.push(info(
356
+ `Code provider: Lite durable index; external semantic graph ${providerQuality.health.state}`,
357
+ ));
358
+ if (providerQuality.health.state !== 'ready'
359
+ && providerQuality.health.state !== 'not-detected'
360
+ && providerQuality.health.state !== 'disabled') {
361
+ lines.push(warn(`External CodeGraph: ${providerQuality.health.reason ?? providerQuality.health.state}`));
362
+ }
363
+
347
364
  if (overview.freshness.stale > 0 || overview.freshness.suspect > 0) {
348
365
  lines.push(warn(`Freshness: ${overview.freshness.current} current, ${overview.freshness.suspect} suspect, ${overview.freshness.stale} stale`));
349
366
  } else {
@@ -362,6 +379,7 @@ export default defineCommand({
362
379
  refs: overview.code.refs,
363
380
  languages: overview.code.languages,
364
381
  freshness: overview.freshness,
382
+ providerQuality: providerQuality.quality,
365
383
  ...(overview.code.indexedAt ? { indexedAt: overview.code.indexedAt } : {}),
366
384
  };
367
385
  } catch (e) {
@@ -373,7 +391,58 @@ export default defineCommand({
373
391
  lines.push(info('Project context unavailable without a detected project.'));
374
392
  }
375
393
 
376
- // ── 6. Conflict Check ────────────────────────────────────────
394
+ // ── 6. Knowledge Lifecycle ──────────────────────────────────
395
+ lines.push('');
396
+ lines.push('┌─ Knowledge Lifecycle ─────────────────────────────');
397
+
398
+ if (projectId && dataDir) {
399
+ try {
400
+ const { collectLifecycleDiagnostics } = await import('../../runtime/lifecycle-status.js');
401
+ const lifecycle = await collectLifecycleDiagnostics({ dataDir, projectId });
402
+ report.lifecycle = lifecycle;
403
+
404
+ const queued = lifecycle.maintenance.summary.pending + lifecycle.maintenance.summary.retrying;
405
+ const running = lifecycle.maintenance.summary.running;
406
+ if (lifecycle.maintenance.summary.failed > 0) {
407
+ lines.push(warn(`Maintenance: ${lifecycle.maintenance.summary.failed} failed job(s)`));
408
+ for (const job of lifecycle.maintenance.failedJobs) {
409
+ lines.push(info(`Failed: ${job.kind} after ${job.attempts} attempt(s)`));
410
+ }
411
+ issues.push('Knowledge maintenance has failed work. Run `memorix doctor --json` to inspect the affected job kind.');
412
+ } else if (queued > 0 || running > 0) {
413
+ lines.push(info(`Maintenance: ${queued} queued, ${running} running`));
414
+ } else {
415
+ lines.push(ok('Maintenance: idle'));
416
+ }
417
+
418
+ lines.push(info(
419
+ `Claims: ${lifecycle.claims.active} active, ${lifecycle.claims.needsReview} need review, ${lifecycle.claims.unknown} unknown`,
420
+ ));
421
+ if (lifecycle.claims.conflicts > 0) {
422
+ lines.push(warn(`Claim conflicts: ${lifecycle.claims.conflicts}`));
423
+ issues.push('Source-backed claims conflict and need review before they guide work.');
424
+ }
425
+
426
+ if (lifecycle.workspaces.length === 0) {
427
+ lines.push(info('Knowledge workspace: not initialized'));
428
+ } else {
429
+ for (const workspace of lifecycle.workspaces) {
430
+ lines.push(info(
431
+ `${workspace.mode} workspace: ${workspace.pages} pages, ${workspace.pendingProposals} pending proposal(s), ${workspace.status}`,
432
+ ));
433
+ }
434
+ }
435
+ lines.push(info(`Workflows: ${lifecycle.workflows.active}/${lifecycle.workflows.total} active, ${lifecycle.workflows.failedRuns} failed run(s)`));
436
+ } catch (e) {
437
+ const message = e instanceof Error ? e.message : String(e);
438
+ lines.push(warn(`Knowledge lifecycle unavailable: ${message}`));
439
+ report.lifecycle = { error: message };
440
+ }
441
+ } else {
442
+ lines.push(info('Knowledge lifecycle unavailable without a detected project.'));
443
+ }
444
+
445
+ // ── 7. Conflict Check ────────────────────────────────────────
377
446
  lines.push('');
378
447
  lines.push('┌─ Conflict Check ──────────────────────────────────');
379
448
 
@@ -403,7 +472,7 @@ export default defineCommand({
403
472
  }
404
473
  report.conflicts = { found: conflictsFound };
405
474
 
406
- // ── 7. LLM Status ────────────────────────────────────────────
475
+ // ── 8. LLM Status ────────────────────────────────────────────
407
476
  lines.push('');
408
477
  lines.push('┌─ LLM Enhanced Mode ───────────────────────────────');
409
478
 
@@ -427,7 +496,7 @@ export default defineCommand({
427
496
  report.llm = { enabled: false };
428
497
  }
429
498
 
430
- // ── 8. Auto-Update Status ──────────────────────────────────
499
+ // ── 9. Auto-Update Status ──────────────────────────────────
431
500
  lines.push('');
432
501
  lines.push('┌─ Auto-Update ─────────────────────────────────────');
433
502
 
@@ -0,0 +1,282 @@
1
+ import { defineCommand } from 'citty';
2
+ import { CodeGraphStore } from '../../codegraph/store.js';
3
+ import { ClaimStore } from '../../knowledge/claim-store.js';
4
+ import { applyKnowledgeProposal, compileKnowledgeWorkspace, lintKnowledgeWorkspace } from '../../knowledge/wiki.js';
5
+ import { initializeKnowledgeWorkspace, loadKnowledgeWorkspace } from '../../knowledge/workspace.js';
6
+ import { KnowledgeWorkspaceStore } from '../../knowledge/workspace-store.js';
7
+ import { WorkflowStore } from '../../knowledge/workflow-store.js';
8
+ import {
9
+ applyWorkflowAdapter,
10
+ importWindsurfWorkflows,
11
+ previewWorkflowAdapter,
12
+ recordWorkflowRun,
13
+ selectWorkspaceWorkflows,
14
+ syncCanonicalWorkflows,
15
+ } from '../../knowledge/workflows.js';
16
+ import { emitError, emitResult, getCliProjectContext } from './operator-shared.js';
17
+
18
+ function modeFrom(value: unknown): 'local' | 'versioned' {
19
+ if (!value || value === 'local') return 'local';
20
+ if (value === 'versioned') return 'versioned';
21
+ throw new Error('knowledge mode must be local or versioned');
22
+ }
23
+
24
+ function usage(): string {
25
+ return [
26
+ 'Memorix Knowledge Commands',
27
+ '',
28
+ ' memorix knowledge init [--mode local]',
29
+ ' memorix knowledge init --mode versioned --path C:\\project\\docs\\knowledge',
30
+ ' memorix knowledge status',
31
+ ' memorix knowledge compile',
32
+ ' memorix knowledge lint',
33
+ ' memorix knowledge apply --proposal <id> [--force]',
34
+ ' memorix knowledge workflow import',
35
+ ' memorix knowledge workflow list',
36
+ ' memorix knowledge workflow select --task "prepare a release"',
37
+ ' memorix knowledge workflow preview --id <workflow-id> --agent codex',
38
+ ' memorix knowledge workflow apply --id <workflow-id> --agent codex',
39
+ ' memorix knowledge workflow run --id <workflow-id> --task "..." --outcome passed',
40
+ ].join('\n');
41
+ }
42
+
43
+ function requiredText(value: unknown, field: string): string {
44
+ const text = typeof value === 'string' ? value.trim() : '';
45
+ if (!text) throw new Error(field + ' is required');
46
+ return text;
47
+ }
48
+
49
+ function workflowOutcome(value: unknown): 'passed' | 'failed' | 'cancelled' | 'in-progress' {
50
+ if (value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'in-progress') return value;
51
+ throw new Error('workflow outcome must be passed, failed, cancelled, or in-progress');
52
+ }
53
+
54
+ function workflowVerdict(value: unknown): 'passed' | 'failed' | 'not-run' | undefined {
55
+ if (value === undefined || value === null || value === '') return undefined;
56
+ if (value === 'passed' || value === 'failed' || value === 'not-run') return value;
57
+ throw new Error('workflow verdict must be passed, failed, or not-run');
58
+ }
59
+
60
+ function evidenceList(value: unknown): string[] {
61
+ if (typeof value !== 'string' || !value.trim()) return [];
62
+ return [...new Set(value.split(',').map(item => item.trim()).filter(Boolean))];
63
+ }
64
+
65
+ export default defineCommand({
66
+ meta: {
67
+ name: 'knowledge',
68
+ description: 'Initialize, review, and lint the Memorix Knowledge Workspace',
69
+ },
70
+ args: {
71
+ action: { type: 'string', description: 'Action: init, status, compile, lint, or apply' },
72
+ mode: { type: 'string', description: 'Workspace mode: local or versioned' },
73
+ path: { type: 'string', description: 'Explicit versioned workspace path for init' },
74
+ proposal: { type: 'string', description: 'Proposal id for apply' },
75
+ force: { type: 'boolean', description: 'Explicitly allow an approved proposal to replace manual page edits' },
76
+ id: { type: 'string', description: 'Canonical workflow id' },
77
+ agent: { type: 'string', description: 'Target agent for a workflow adapter' },
78
+ task: { type: 'string', description: 'Task wording for workflow selection or a workflow run' },
79
+ outcome: { type: 'string', description: 'Workflow run outcome: passed, failed, cancelled, or in-progress' },
80
+ verdict: { type: 'string', description: 'Workflow verification verdict: passed, failed, or not-run' },
81
+ failure: { type: 'string', description: 'Sanitized failure reason for a workflow run' },
82
+ snapshot: { type: 'string', description: 'Starting code snapshot id for a workflow run' },
83
+ evidence: { type: 'string', description: 'Comma-separated evidence ids selected for a workflow run' },
84
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
85
+ },
86
+ run: async ({ args }) => {
87
+ const positional = (args._ as string[]) ?? [];
88
+ const action = (positional[0] || (args.action as string | undefined) || 'status').toLowerCase();
89
+ const asJson = !!args.json;
90
+ try {
91
+ const { project, dataDir } = await getCliProjectContext();
92
+ const mode = modeFrom(args.mode);
93
+ if (action === 'init') {
94
+ const workspace = await initializeKnowledgeWorkspace({
95
+ projectId: project.id,
96
+ dataDir,
97
+ mode,
98
+ ...(mode === 'versioned'
99
+ ? {
100
+ projectRoot: project.rootPath,
101
+ rootPath: (args.path as string | undefined) ?? '',
102
+ }
103
+ : {}),
104
+ });
105
+ emitResult(
106
+ { project, workspace },
107
+ 'Knowledge workspace initialized at ' + workspace.rootPath,
108
+ asJson,
109
+ );
110
+ return;
111
+ }
112
+
113
+ const workspace = await loadKnowledgeWorkspace({ projectId: project.id, dataDir, mode });
114
+ if (!workspace) {
115
+ emitError('Knowledge workspace is not initialized. Run "memorix knowledge init" first.', asJson);
116
+ return;
117
+ }
118
+ const claimStore = new ClaimStore();
119
+ await claimStore.init(dataDir);
120
+ const workspaceStore = new KnowledgeWorkspaceStore();
121
+ await workspaceStore.init(dataDir);
122
+
123
+ if (action === 'workflow') {
124
+ const workflowAction = (positional[1] || 'list').toLowerCase();
125
+ const workflowStore = new WorkflowStore();
126
+ await workflowStore.init(dataDir);
127
+ const projectRoot = workspace.projectRoot ?? project.rootPath;
128
+
129
+ if (workflowAction === 'import') {
130
+ const result = await importWindsurfWorkflows({ workspace, projectRoot });
131
+ emitResult(
132
+ { project, workspace, result },
133
+ 'Imported ' + result.imported.length + ' Windsurf workflow(s); preserved ' + result.skipped.length + ' existing source or canonical file(s).',
134
+ asJson,
135
+ );
136
+ return;
137
+ }
138
+
139
+ const synced = await syncCanonicalWorkflows(workspace);
140
+ if (workflowAction === 'list') {
141
+ emitResult(
142
+ {
143
+ project,
144
+ workspace,
145
+ workflows: workflowStore.listWorkflows(workspace.id),
146
+ parseErrors: synced.errors,
147
+ },
148
+ [
149
+ 'Canonical workflows: ' + workflowStore.listWorkflows(workspace.id).length,
150
+ 'Parse errors: ' + synced.errors.length,
151
+ ].join('\n'),
152
+ asJson,
153
+ );
154
+ return;
155
+ }
156
+
157
+ if (workflowAction === 'select') {
158
+ const task = requiredText(args.task, 'task');
159
+ const result = await selectWorkspaceWorkflows({ workspace, task });
160
+ emitResult(
161
+ { project, workspace, task, ...result },
162
+ result.selections.length
163
+ ? 'Selected ' + result.selections.length + ' workflow(s) for this task.'
164
+ : 'No project workflow matches this task.',
165
+ asJson,
166
+ );
167
+ return;
168
+ }
169
+
170
+ const workflowId = requiredText(args.id, 'workflow id');
171
+ const workflow = workflowStore.getWorkflow(workflowId);
172
+ if (!workflow || workflow.workspaceId !== workspace.id) {
173
+ emitError('Workflow was not found for this knowledge workspace.', asJson);
174
+ return;
175
+ }
176
+
177
+ if (workflowAction === 'preview' || workflowAction === 'apply') {
178
+ const agent = requiredText(args.agent, 'agent');
179
+ const result = workflowAction === 'preview'
180
+ ? await previewWorkflowAdapter({ workflow, projectRoot, agent: agent as any })
181
+ : await applyWorkflowAdapter({ workflow, projectRoot, agent: agent as any });
182
+ emitResult(
183
+ { project, workspace, result },
184
+ 'Workflow adapter ' + result.status + ': ' + result.reason,
185
+ asJson,
186
+ );
187
+ return;
188
+ }
189
+
190
+ if (workflowAction === 'run') {
191
+ const task = requiredText(args.task, 'task');
192
+ const outcome = workflowOutcome(args.outcome);
193
+ const result = await recordWorkflowRun({
194
+ workspace,
195
+ run: {
196
+ workflowId,
197
+ projectId: project.id,
198
+ task,
199
+ outcome,
200
+ ...(workflowVerdict(args.verdict) ? { verificationVerdict: workflowVerdict(args.verdict) } : {}),
201
+ ...(typeof args.failure === 'string' && args.failure.trim() ? { failureReason: args.failure.trim() } : {}),
202
+ ...(typeof args.snapshot === 'string' && args.snapshot.trim() ? { startingSnapshotId: args.snapshot.trim() } : {}),
203
+ selectedEvidence: evidenceList(args.evidence),
204
+ },
205
+ });
206
+ emitResult(
207
+ { project, workspace, result },
208
+ 'Recorded workflow run ' + result.id + ' (' + result.outcome + ').',
209
+ asJson,
210
+ );
211
+ return;
212
+ }
213
+
214
+ emitResult({ usage: usage() }, usage(), asJson);
215
+ return;
216
+ }
217
+
218
+ switch (action) {
219
+ case 'status': {
220
+ const pages = workspaceStore.listPages(workspace.id);
221
+ const pending = workspaceStore.listProposals(workspace.id, 'pending');
222
+ emitResult(
223
+ { project, workspace, pages, pendingProposals: pending },
224
+ [
225
+ 'Knowledge workspace: ' + workspace.rootPath,
226
+ '- Mode: ' + workspace.mode,
227
+ '- Published pages: ' + pages.filter(page => page.status === 'active').length,
228
+ '- Pending proposals: ' + pending.length,
229
+ '- Status: ' + workspace.status,
230
+ ].join('\n'),
231
+ asJson,
232
+ );
233
+ return;
234
+ }
235
+ case 'compile': {
236
+ const result = await compileKnowledgeWorkspace({ workspace, claims: claimStore });
237
+ emitResult(
238
+ { project, workspace, result },
239
+ 'Knowledge compilation completed: ' + result.proposals.length + ' proposal(s), ' + result.published.length + ' unchanged published page(s).',
240
+ asJson,
241
+ );
242
+ return;
243
+ }
244
+ case 'lint': {
245
+ const codeStore = new CodeGraphStore();
246
+ await codeStore.init(dataDir);
247
+ const result = await lintKnowledgeWorkspace({ workspace, claims: claimStore, codeStore });
248
+ emitResult(
249
+ { project, workspace, result },
250
+ result.valid
251
+ ? 'Knowledge workspace lint passed.'
252
+ : 'Knowledge workspace lint found ' + result.issues.length + ' issue(s).',
253
+ asJson,
254
+ );
255
+ return;
256
+ }
257
+ case 'apply': {
258
+ const proposalId = (args.proposal as string | undefined)?.trim();
259
+ if (!proposalId) {
260
+ emitError('proposal is required for "memorix knowledge apply"', asJson);
261
+ return;
262
+ }
263
+ const result = await applyKnowledgeProposal({
264
+ workspace,
265
+ proposalId,
266
+ allowManualOverwrite: !!args.force,
267
+ });
268
+ emitResult(
269
+ { project, workspace, result },
270
+ 'Knowledge proposal applied to ' + result.targetPath,
271
+ asJson,
272
+ );
273
+ return;
274
+ }
275
+ default:
276
+ emitResult({ usage: usage() }, usage(), asJson);
277
+ }
278
+ } catch (error) {
279
+ emitError(error instanceof Error ? error.message : String(error), asJson);
280
+ }
281
+ },
282
+ });
@@ -868,9 +868,12 @@ export default defineCommand({
868
868
  const projectRelations = graph.relations.filter((r: any) => projectEntitySet.has(r.from) && projectEntitySet.has(r.to));
869
869
 
870
870
  let maintenance = { total: 0, pending: 0, running: 0, retrying: 0, completed: 0, failed: 0 };
871
+ let lifecycle: unknown;
871
872
  try {
872
873
  const { MaintenanceJobStore } = await import('../../runtime/maintenance-jobs.js');
873
874
  maintenance = new MaintenanceJobStore(statsDataDir).summary(statsProjectId);
875
+ const { collectLifecycleDiagnostics } = await import('../../runtime/lifecycle-status.js');
876
+ lifecycle = await collectLifecycleDiagnostics({ dataDir: statsDataDir, projectId: statsProjectId });
874
877
  } catch { /* optional maintenance diagnostics */ }
875
878
 
876
879
  sendJson({
@@ -892,17 +895,25 @@ export default defineCommand({
892
895
  },
893
896
  retentionSummary,
894
897
  maintenance,
898
+ ...(lifecycle ? { lifecycle } : {}),
895
899
  });
896
900
  return;
897
901
  }
898
902
 
899
903
  if (apiPath === '/maintenance') {
900
904
  const { projectId: maintenanceProjectId, dataDir: maintenanceDataDir } = await resolveRequestProject(url);
901
- const { MaintenanceJobStore } = await import('../../runtime/maintenance-jobs.js');
905
+ const [{ MaintenanceJobStore }, { collectLifecycleDiagnostics }] = await Promise.all([
906
+ import('../../runtime/maintenance-jobs.js'),
907
+ import('../../runtime/lifecycle-status.js'),
908
+ ]);
902
909
  const queue = new MaintenanceJobStore(maintenanceDataDir);
903
910
  sendJson({
904
911
  summary: queue.summary(maintenanceProjectId),
905
912
  jobs: queue.list({ projectId: maintenanceProjectId, limit: 50 }),
913
+ lifecycle: await collectLifecycleDiagnostics({
914
+ dataDir: maintenanceDataDir,
915
+ projectId: maintenanceProjectId,
916
+ }),
906
917
  });
907
918
  return;
908
919
  }
@@ -300,6 +300,17 @@ async function stripHookCaptureFromPlugin(pluginPath: string, agent: PluginInsta
300
300
  }
301
301
  }
302
302
 
303
+ async function stampCodexPluginVersion(pluginPath: string): Promise<void> {
304
+ const manifestPath = path.join(pluginPath, '.codex-plugin', 'plugin.json');
305
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf-8')) as Record<string, unknown>;
306
+ if (manifest.name !== 'memorix') {
307
+ throw new Error(`Unexpected Codex plugin manifest at ${manifestPath}`);
308
+ }
309
+
310
+ manifest.version = getCliVersion();
311
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
312
+ }
313
+
303
314
  async function stripHookCaptureFromOpenClawBundle(bundlePath: string): Promise<void> {
304
315
  await rm(path.join(bundlePath, 'hooks'), { recursive: true, force: true });
305
316
 
@@ -636,6 +647,7 @@ export async function installPluginPackage(options: PluginInstallOptions): Promi
636
647
  const pluginPath = path.join(home, '.codex', 'plugins', 'memorix');
637
648
  const marketplacePath = path.join(home, '.agents', 'plugins', 'marketplace.json');
638
649
  await copyDir(source, pluginPath);
650
+ await stampCodexPluginVersion(pluginPath);
639
651
  if (!includeHooks) await stripHookCaptureFromPlugin(pluginPath, 'codex');
640
652
  await writeOfficialSkills(path.join(pluginPath, 'skills'));
641
653
  await upsertCodexMarketplace(marketplacePath, pluginPath);
@@ -862,7 +874,7 @@ function tryInstallClaudePlugin(marketplaceRoot: string): { ok: boolean; message
862
874
  };
863
875
  }
864
876
 
865
- function tryInstallCodexPlugin(): { ok: boolean; message: string } {
877
+ export function tryInstallCodexPlugin(): { ok: boolean; message: string } {
866
878
  const result = spawnSync('codex', ['plugin', 'add', 'memorix@personal', '--json'], {
867
879
  encoding: 'utf-8',
868
880
  stdio: 'pipe',
package/src/cli/index.ts CHANGED
@@ -989,6 +989,7 @@ const main = defineCommand({
989
989
  context: () => import('./commands/context.js').then(m => m.default),
990
990
  explain: () => import('./commands/explain.js').then(m => m.default),
991
991
  codegraph: () => import('./commands/codegraph.js').then(m => m.default),
992
+ knowledge: () => import('./commands/knowledge.js').then(m => m.default),
992
993
  reasoning: () => import('./commands/reasoning.js').then(m => m.default),
993
994
  retention: () => import('./commands/retention.js').then(m => m.default),
994
995
  formation: () => import('./commands/formation.js').then(m => m.default),
@@ -1048,7 +1049,7 @@ const main = defineCommand({
1048
1049
  // Detect by checking if the first CLI arg matches a registered subcommand name.
1049
1050
  const firstArg = process.argv[2];
1050
1051
  const knownSubs = ['ask', 'search', 'remember', 'recent', 'memcode', 'config',
1051
- 'init', 'setup', 'integrate', 'memory', 'context', 'explain', 'codegraph', 'reasoning', 'retention', 'formation', 'audit', 'transfer', 'skills',
1052
+ 'init', 'setup', 'integrate', 'memory', 'context', 'explain', 'codegraph', 'knowledge', 'reasoning', 'retention', 'formation', 'audit', 'transfer', 'skills',
1052
1053
  'session', 'team', 'task', 'message', 'lock', 'handoff', 'poll',
1053
1054
  'receipt',
1054
1055
  'serve', 'serve-http', 'status', 'sync',
@@ -1088,6 +1089,7 @@ const main = defineCommand({
1088
1089
  console.error(' context Show the Memory Autopilot brief for this project');
1089
1090
  console.error(' explain Explain where Memorix project context comes from');
1090
1091
  console.error(' codegraph Refresh/status/context-pack for CodeGraph Memory');
1092
+ console.error(' knowledge Review source-backed knowledge pages and project workflows');
1091
1093
  console.error(' reasoning Store/search decision rationale');
1092
1094
  console.error(' retention Inspect stale/archive status');
1093
1095
  console.error(' formation Inspect Memory Formation metrics');
@@ -598,7 +598,7 @@ export function WorkbenchApp({ version, onExitForInteractive }: AppProps): React
598
598
  setKnowledgeSelectedIdx(0);
599
599
  setLoading(false);
600
600
  navigateTo('knowledge');
601
- setStatusMsg({ text: kb ? `Knowledge Base: ${kb.stats.observationsUsed} obs, ${kb.stats.miniSkillsUsed} skills` : 'No knowledge available', type: 'info' });
601
+ setStatusMsg({ text: kb ? `Memory Overview: ${kb.stats.observationsUsed} obs, ${kb.stats.miniSkillsUsed} skills` : 'No memory overview available', type: 'info' });
602
602
  break;
603
603
  }
604
604
  case 'memory': {
@@ -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>Knowledge Base</Text>
557
- <Text color={COLORS.muted}> LLM Wiki</Text>
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>Knowledge Base</Text>
568
- <Text color={COLORS.muted}> LLM Wiki</Text>
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 knowledge available for this project.</Text>
571
- <Text color={COLORS.textDim}>Store memories with /remember or /chat to build your Knowledge Base.</Text>
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>Knowledge Base</Text>
579
- <Text color={COLORS.muted}> LLM Wiki</Text>
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>
@@ -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: 'Knowledge Base', alias: '/knowledge' },
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 — Knowledge graph text browser.
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>Graph Node Detail</Text>
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>Edges</Text>
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 {'>'} Knowledge</Text>
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>Knowledge Graph</Text>
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 graph data available. Store observations to build the knowledge graph.</Text>
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 {'>'} Knowledge
294
+ f filter | up/down navigate | enter detail | k {'>'} Memory Overview
294
295
  </Text>
295
296
  </Box>
296
297
  </Box>