memorix 1.7.3 → 1.8.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 (70) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/cli/index.js +1407 -192
  5. package/dist/dashboard/static/app.js +88 -4
  6. package/dist/dashboard/static/index.html +10 -0
  7. package/dist/dashboard/static/style.css +86 -0
  8. package/dist/index.js +875 -164
  9. package/dist/maintenance-runner.js +607 -68
  10. package/dist/media-audio-runner.js +602 -63
  11. package/dist/media-video-runner.js +607 -68
  12. package/dist/memcode-runtime/CHANGELOG.md +32 -0
  13. package/dist/sdk.js +874 -163
  14. package/dist/types.d.ts +8 -0
  15. package/dist/vector-backfill-runner.js +607 -68
  16. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  17. package/docs/1.8.0-RELEASE-CANDIDATE-SPEC.md +75 -0
  18. package/docs/API_REFERENCE.md +37 -1
  19. package/docs/DASHBOARD.md +10 -0
  20. package/docs/DEVELOPMENT.md +3 -2
  21. package/docs/INTEGRATIONS.md +1 -1
  22. package/docs/README.md +11 -1
  23. package/package.json +1 -1
  24. package/plugins/antigravity/memorix/rules/memorix.md +1 -1
  25. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  26. package/plugins/claude/memorix/SKILL.md +1 -1
  27. package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +2 -2
  28. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  29. package/plugins/codebuddy/memorix/skills/memorix-memory/SKILL.md +2 -2
  30. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  31. package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +2 -2
  32. package/plugins/copilot/memorix/plugin.json +1 -1
  33. package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +2 -2
  34. package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +2 -2
  35. package/plugins/gemini/memorix/GEMINI.md +1 -1
  36. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  37. package/plugins/omp/memorix/package.json +1 -1
  38. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  39. package/plugins/pi/memorix/package.json +1 -1
  40. package/plugins/pi/memorix/skills/memorix-memory/SKILL.md +2 -2
  41. package/server.json +2 -2
  42. package/src/cli/capability-map.ts +2 -0
  43. package/src/cli/commands/context.ts +11 -0
  44. package/src/cli/commands/evidence.ts +56 -0
  45. package/src/cli/commands/feedback.ts +58 -0
  46. package/src/cli/commands/resume.ts +2 -0
  47. package/src/cli/commands/serve-http.ts +201 -12
  48. package/src/cli/fallback-budget.ts +59 -0
  49. package/src/cli/index.ts +5 -1
  50. package/src/compact/engine.ts +26 -1
  51. package/src/config/dotenv-loader.ts +17 -0
  52. package/src/dashboard/config-provenance.ts +17 -0
  53. package/src/dashboard/evidence.ts +63 -0
  54. package/src/dashboard/server.ts +93 -68
  55. package/src/dashboard/static/index.html +10 -0
  56. package/src/dashboard/static/style.css +86 -0
  57. package/src/hooks/installers/index.ts +1 -1
  58. package/src/hooks/official-skills.ts +3 -3
  59. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  60. package/src/memory/feedback-model.ts +4 -0
  61. package/src/memory/feedback.ts +220 -0
  62. package/src/memory/observations.ts +19 -0
  63. package/src/memory/retention.ts +38 -5
  64. package/src/server/mcp-binding-store.ts +79 -0
  65. package/src/server/mcp-diagnostics.ts +40 -0
  66. package/src/server/tool-profile.ts +4 -0
  67. package/src/server.ts +121 -1
  68. package/src/store/evidence-store.ts +261 -0
  69. package/src/store/sqlite-db.ts +104 -0
  70. package/src/types.ts +8 -0
@@ -22,10 +22,13 @@ import { loadDotenv } from '../config/dotenv-loader.js';
22
22
  import { resetDotenv } from '../config/dotenv-loader.js';
23
23
  import { initProjectRoot } from '../config/yaml-loader.js';
24
24
  import { clearProjectRoot } from '../config/yaml-loader.js';
25
+ import { getResolvedConfig } from '../config/resolved-config.js';
25
26
  import { scopeKnowledgeGraphToProject } from '../memory/graph-scope.js';
26
27
  import { projectObservationRetention, summarizeRetentionProjections } from '../memory/retention.js';
27
28
  import { canManageObservation, filterReadableObservations } from '../memory/visibility.js';
28
29
  import type { Observation } from '../types.js';
30
+ import { EvidenceCardStore } from '../store/evidence-store.js';
31
+ import { MemoryFeedbackStore } from '../memory/feedback.js';
29
32
  import {
30
33
  DashboardMaintenanceError,
31
34
  executeCleanup,
@@ -37,6 +40,7 @@ import {
37
40
  previewDeduplicate,
38
41
  previewRetentionArchive,
39
42
  } from './maintenance.js';
43
+ import { resolveDashboardKeySource } from './config-provenance.js';
40
44
 
41
45
  // MIME types for static file serving
42
46
  const MIME_TYPES: Record<string, string> = {
@@ -326,6 +330,22 @@ async function handleApi(
326
330
  break;
327
331
  }
328
332
 
333
+ case '/evidence': {
334
+ const observations = filterDashboardObservations(
335
+ await getObservationStore().loadByProject(effectiveProjectId, { status: 'active' }),
336
+ effectiveProjectId,
337
+ );
338
+ const evidenceStore = new EvidenceCardStore();
339
+ await evidenceStore.init(effectiveDataDir);
340
+ evidenceStore.syncObservations(observations);
341
+ const query = url.searchParams.get('q') || '';
342
+ const limit = Number(url.searchParams.get('limit') || 20);
343
+ const cards = evidenceStore.list(effectiveProjectId, { limit: Math.min(100, Math.max(1, limit)) })
344
+ .filter((card) => !query || [card.title, card.summary, card.sourceRef, card.locator ?? ''].join(' ').toLowerCase().includes(query.toLowerCase()));
345
+ sendJson(res, { cards, projectId: effectiveProjectId });
346
+ break;
347
+ }
348
+
329
349
  case '/sessions': {
330
350
  const allSessions = await getSessionStore().loadAll();
331
351
  const sessions = filterByProject(allSessions as Array<{ projectId?: string }>, effectiveProjectId);
@@ -381,10 +401,15 @@ async function handleApi(
381
401
  }));
382
402
 
383
403
  const retentionReferenceTime = new Date(now);
404
+ const feedback = new MemoryFeedbackStore();
405
+ await feedback.init(effectiveDataDir).catch(() => undefined);
384
406
  const retention = summarizeRetentionProjections(
385
407
  observations
386
408
  .filter((observation) => observation.type !== 'probe')
387
- .map((observation) => projectObservationRetention(observation, { referenceTime: retentionReferenceTime })),
409
+ .map((observation) => projectObservationRetention(observation, {
410
+ referenceTime: retentionReferenceTime,
411
+ feedback: feedback.getState(effectiveProjectId, 'observation', String(observation.id)),
412
+ })),
388
413
  );
389
414
  const retentionSummary = {
390
415
  active: retention.active,
@@ -489,11 +514,16 @@ async function handleApi(
489
514
  );
490
515
 
491
516
  const referenceTime = new Date();
517
+ const feedback = new MemoryFeedbackStore();
518
+ await feedback.init(effectiveDataDir).catch(() => undefined);
492
519
  const rows = observations
493
520
  .filter((observation) => observation.type !== 'probe')
494
521
  .map((observation) => ({
495
522
  observation,
496
- retention: projectObservationRetention(observation, { referenceTime }),
523
+ retention: projectObservationRetention(observation, {
524
+ referenceTime,
525
+ feedback: feedback.getState(effectiveProjectId, 'observation', String(observation.id)),
526
+ }),
497
527
  }))
498
528
  .sort((a, b) => b.retention.displayScore - a.retention.displayScore);
499
529
  const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
@@ -580,26 +610,18 @@ async function handleApi(
580
610
  const { existsSync } = await import('node:fs');
581
611
  const { join } = await import('node:path');
582
612
 
583
- let yml: any = {};
584
613
  // Use the real project root from dashboard state, not process.cwd()
585
614
  const configProjectRoot = effectiveProjectRoot;
586
- try {
587
- const { loadYamlConfig } = await import('../config/yaml-loader.js');
588
- yml = configProjectRoot ? loadYamlConfig(configProjectRoot) : loadYamlConfig(null);
589
- } catch { /* best effort */ }
590
-
591
- // Load .env files so process.env reflects actual config (fixes #74, #62)
592
- if (configProjectRoot) {
593
- try {
594
- const { loadDotenv } = await import('../config/dotenv-loader.js');
595
- loadDotenv(configProjectRoot);
596
- } catch { /* best effort */ }
597
- }
615
+ // This is the same TOML-first chain used by status and runtime lanes.
616
+ // Do not fall back to user config if a bound project cannot resolve.
617
+ const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
598
618
 
599
619
  // Check which config files exist
600
620
  const files: Record<string, { exists: boolean; path: string; unavailable?: boolean }> = {
601
621
  'project memorix.yml': { exists: false, path: '', unavailable: !configProjectRoot },
602
622
  'user memorix.yml': { exists: false, path: '' },
623
+ 'project memorix.toml': { exists: false, path: '', unavailable: !configProjectRoot },
624
+ 'user config.toml': { exists: false, path: '' },
603
625
  'project .env': { exists: false, path: '', unavailable: !configProjectRoot },
604
626
  'user .env': { exists: false, path: '' },
605
627
  'legacy config.json': { exists: false, path: '' },
@@ -609,6 +631,8 @@ async function handleApi(
609
631
  const paths: Record<string, string | null> = {
610
632
  'project memorix.yml': configProjectRoot ? join(configProjectRoot, 'memorix.yml') : null,
611
633
  'user memorix.yml': join(home, '.memorix', 'memorix.yml'),
634
+ 'project memorix.toml': configProjectRoot ? join(configProjectRoot, 'memorix.toml') : null,
635
+ 'user config.toml': join(home, '.memorix', 'config.toml'),
612
636
  'project .env': configProjectRoot ? join(configProjectRoot, '.env') : null,
613
637
  'user .env': join(home, '.memorix', '.env'),
614
638
  'legacy config.json': join(home, '.memorix', 'config.json'),
@@ -626,82 +650,83 @@ async function handleApi(
626
650
  const values: Array<{ key: string; value: string; source: string; sensitive?: boolean }> = [];
627
651
 
628
652
  // LLM
629
- // Helper: determine source label (distinguishes .env file vs system env)
630
- const getEnvSource = async (envKey: string, ymlSource?: string): Promise<string> => {
631
- if (process.env[envKey]) {
632
- // Check if this key was injected by dotenv-loader (from .env file)
633
- try {
634
- const { getLoadedEnvFiles } = await import('../config/dotenv-loader.js');
635
- const envFiles = getLoadedEnvFiles();
636
- if (envFiles.length > 0) return `.env (${envKey})`;
637
- } catch { /* ignore */ }
638
- return `env:${envKey}`;
639
- }
640
- return ymlSource ?? 'default';
641
- };
653
+ const tomlSource = configProjectRoot && files['project memorix.toml'].exists
654
+ ? 'memorix.toml'
655
+ : files['user config.toml'].exists ? 'config.toml' : 'default';
656
+ const legacySource = files['project memorix.yml'].exists || files['user memorix.yml'].exists
657
+ ? 'memorix.yml'
658
+ : files['legacy config.json'].exists ? 'config.json' : 'default';
659
+ const configSource = resolved.sources.toml.length > 0 ? tomlSource
660
+ : resolved.sources.legacy.length > 0 ? legacySource : 'default';
661
+ const source = (envKey: string): string => resolveDashboardKeySource({
662
+ value: process.env[envKey] ?? '',
663
+ envKeys: [envKey],
664
+ configSource,
665
+ });
642
666
 
643
- const llmProvider = process.env.MEMORIX_LLM_PROVIDER || yml.llm?.provider;
644
- if (llmProvider) values.push({ key: 'llm.provider', value: llmProvider, source: await getEnvSource('MEMORIX_LLM_PROVIDER', yml.llm?.provider ? 'memorix.yml' : undefined) });
667
+ if (resolved.memory.llm.provider) values.push({ key: 'llm.provider', value: resolved.memory.llm.provider, source: source('MEMORIX_LLM_PROVIDER') });
645
668
 
646
- const llmModel = process.env.MEMORIX_LLM_MODEL || yml.llm?.model;
647
- if (llmModel) values.push({ key: 'llm.model', value: llmModel, source: await getEnvSource('MEMORIX_LLM_MODEL', yml.llm?.model ? 'memorix.yml' : undefined) });
669
+ if (resolved.memory.llm.model) values.push({ key: 'llm.model', value: resolved.memory.llm.model, source: source('MEMORIX_LLM_MODEL') });
648
670
 
649
- const llmKey = process.env.MEMORIX_LLM_API_KEY || process.env.MEMORIX_API_KEY || yml.llm?.apiKey || process.env.OPENAI_API_KEY;
671
+ const llmKey = resolved.memory.llm.apiKey;
672
+ const memoryKeyEnv = [
673
+ 'MEMORIX_LLM_API_KEY',
674
+ 'MEMORIX_API_KEY',
675
+ 'OPENAI_API_KEY',
676
+ 'ANTHROPIC_API_KEY',
677
+ ...(resolved.memory.llm.provider === 'openrouter' || resolved.memory.llm.baseUrl?.includes('openrouter.ai')
678
+ ? ['OPENROUTER_API_KEY']
679
+ : []),
680
+ ];
650
681
  if (llmKey) {
651
- let src = 'unknown';
652
- if (process.env.MEMORIX_LLM_API_KEY) src = await getEnvSource('MEMORIX_LLM_API_KEY');
653
- else if (process.env.MEMORIX_API_KEY) src = await getEnvSource('MEMORIX_API_KEY');
654
- else if (yml.llm?.apiKey) src = 'memorix.yml (move to .env!)';
655
- else if (process.env.OPENAI_API_KEY) src = await getEnvSource('OPENAI_API_KEY');
682
+ const src = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
656
683
  values.push({ key: 'llm.apiKey', value: '****' + llmKey.slice(-4), source: src, sensitive: true });
657
684
  } else {
658
685
  values.push({ key: 'llm.apiKey', value: 'not set', source: 'none' });
659
686
  }
660
687
 
661
- const agentProvider = process.env.MEMORIX_AGENT_PROVIDER || process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
662
- if (agentProvider) {
663
- let source: string;
664
- if (process.env.MEMORIX_AGENT_PROVIDER) source = await getEnvSource('MEMORIX_AGENT_PROVIDER');
665
- else if (process.env.MEMORIX_AGENT_LLM_PROVIDER) source = `${await getEnvSource('MEMORIX_AGENT_LLM_PROVIDER')} (legacy)`;
666
- else source = yml.agent?.provider ? 'memorix.yml' : 'default';
667
- values.push({ key: 'agent.provider', value: agentProvider, source });
688
+ if (resolved.agent.provider) {
689
+ values.push({ key: 'agent.provider', value: resolved.agent.provider, source: source('MEMORIX_AGENT_PROVIDER') });
668
690
  }
669
691
 
670
- const agentModel = process.env.MEMORIX_AGENT_MODEL || process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
671
- if (agentModel) {
672
- let source: string;
673
- if (process.env.MEMORIX_AGENT_MODEL) source = await getEnvSource('MEMORIX_AGENT_MODEL');
674
- else if (process.env.MEMORIX_AGENT_LLM_MODEL) source = `${await getEnvSource('MEMORIX_AGENT_LLM_MODEL')} (legacy)`;
675
- else source = yml.agent?.model ? 'memorix.yml' : 'default';
676
- values.push({ key: 'agent.model', value: agentModel, source });
692
+ if (resolved.agent.model) {
693
+ values.push({ key: 'agent.model', value: resolved.agent.model, source: source('MEMORIX_AGENT_MODEL') });
677
694
  }
678
695
 
679
- const agentKey = process.env.MEMORIX_AGENT_API_KEY || process.env.MEMORIX_AGENT_LLM_API_KEY || yml.agent?.apiKey;
696
+ const agentKey = resolved.agent.apiKey;
680
697
  if (agentKey) {
681
- let src = 'unknown';
682
- if (process.env.MEMORIX_AGENT_API_KEY) src = await getEnvSource('MEMORIX_AGENT_API_KEY');
683
- else if (process.env.MEMORIX_AGENT_LLM_API_KEY) src = `${await getEnvSource('MEMORIX_AGENT_LLM_API_KEY')} (legacy)`;
684
- else if (yml.agent?.apiKey) src = 'memorix.yml (move to .env!)';
698
+ const src = resolveDashboardKeySource({
699
+ value: agentKey,
700
+ envKeys: ['MEMORIX_AGENT_API_KEY', 'MEMORIX_AGENT_LLM_API_KEY'],
701
+ configSource,
702
+ });
685
703
  values.push({ key: 'agent.apiKey', value: '****' + agentKey.slice(-4), source: src, sensitive: true });
686
704
  } else {
687
- values.push({ key: 'agent.apiKey', value: 'fallback to llm.apiKey', source: 'default' });
705
+ values.push({
706
+ key: 'agent.apiKey',
707
+ value: 'fallback to llm.apiKey',
708
+ source: llmKey
709
+ ? resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource })
710
+ : 'default',
711
+ });
688
712
  }
689
713
 
690
714
  // Embedding
691
- const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || 'off';
692
- values.push({ key: 'embedding.provider', value: embProvider, source: await getEnvSource('MEMORIX_EMBEDDING', yml.embedding?.provider ? 'memorix.yml' : undefined) });
715
+ values.push({ key: 'embedding.provider', value: resolved.embedding.provider || 'off', source: source('MEMORIX_EMBEDDING') });
716
+ if (resolved.embedding.model) values.push({ key: 'embedding.model', value: resolved.embedding.model, source: source('MEMORIX_EMBEDDING_MODEL') });
693
717
 
694
- // Git
695
- values.push({ key: 'git.autoHook', value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== undefined ? 'memorix.yml' : 'default' });
696
- values.push({ key: 'git.skipMergeCommits', value: String(yml.git?.skipMergeCommits ?? true), source: yml.git?.skipMergeCommits !== undefined ? 'memorix.yml' : 'default' });
718
+ if (resolved.rerank.provider !== 'off') {
719
+ values.push({ key: 'rerank.provider', value: resolved.rerank.provider, source: source('MEMORIX_RERANK_PROVIDER') });
720
+ if (resolved.rerank.model) values.push({ key: 'rerank.model', value: resolved.rerank.model, source: source('MEMORIX_RERANK_MODEL') });
721
+ }
697
722
 
698
- // Behavior
699
- if (yml.behavior?.formationMode) values.push({ key: 'behavior.formationMode', value: yml.behavior.formationMode, source: 'memorix.yml' });
700
- if (yml.behavior?.sessionInject) values.push({ key: 'behavior.sessionInject', value: yml.behavior.sessionInject, source: 'memorix.yml' });
723
+ // Git
724
+ values.push({ key: 'git.autoHook', value: String(resolved.git.autoHook ?? false), source: configSource });
725
+ values.push({ key: 'git.skipMergeCommits', value: String(resolved.git.skipMergeCommits ?? true), source: configSource });
701
726
 
702
727
  // Server
703
- values.push({ key: 'server.transport', value: yml.server?.transport || 'stdio', source: yml.server?.transport ? 'memorix.yml' : 'default' });
704
- values.push({ key: 'server.dashboard', value: String(yml.server?.dashboard ?? true), source: yml.server?.dashboard !== undefined ? 'memorix.yml' : 'default' });
728
+ values.push({ key: 'server.transport', value: resolved.server.transport || 'stdio', source: configSource });
729
+ values.push({ key: 'server.dashboard', value: String(resolved.server.dashboard ?? true), source: configSource });
705
730
 
706
731
  sendJson(res, { files, values });
707
732
  break;
@@ -92,6 +92,15 @@
92
92
  </svg>
93
93
  <span class="nav-label">Observations</span>
94
94
  </button>
95
+ <button class="nav-btn" data-page="evidence" title="Evidence" aria-label="Evidence">
96
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
97
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
98
+ <polyline points="14 2 14 8 20 8"/>
99
+ <line x1="8" y1="13" x2="16" y2="13"/>
100
+ <line x1="8" y1="17" x2="14" y2="17"/>
101
+ </svg>
102
+ <span class="nav-label">Evidence</span>
103
+ </button>
95
104
  </div>
96
105
  <div class="sidebar-section-label" data-section="health">HEALTH</div>
97
106
  <div class="sidebar-nav">
@@ -175,6 +184,7 @@
175
184
  <div id="page-knowledge" class="page"></div>
176
185
  <div id="page-graph" class="page"></div>
177
186
  <div id="page-observations" class="page"></div>
187
+ <div id="page-evidence" class="page"></div>
178
188
  <div id="page-retention" class="page"></div>
179
189
  <div id="page-config" class="page"></div>
180
190
  <div id="page-identity" class="page"></div>
@@ -64,6 +64,92 @@
64
64
  --transition-emphasized: 500ms cubic-bezier(0.2, 0, 0, 1);
65
65
  }
66
66
 
67
+ /* Evidence Console: provenance is readable at a glance and stays bounded on
68
+ narrow screens; long paths and generated titles never widen the viewport. */
69
+ .evidence-console {
70
+ display: grid;
71
+ gap: 12px;
72
+ min-width: 0;
73
+ }
74
+
75
+ .evidence-card {
76
+ border: 1px solid var(--border-color, rgba(148, 163, 184, .18));
77
+ border-radius: 8px;
78
+ background: var(--surface, rgba(15, 23, 42, .45));
79
+ padding: 16px;
80
+ min-width: 0;
81
+ }
82
+
83
+ .evidence-card-header {
84
+ display: flex;
85
+ gap: 12px;
86
+ align-items: flex-start;
87
+ justify-content: space-between;
88
+ min-width: 0;
89
+ }
90
+
91
+ .evidence-card-heading {
92
+ min-width: 0;
93
+ }
94
+
95
+ .evidence-card-heading h2 {
96
+ margin: 4px 0 0;
97
+ font-size: 16px;
98
+ line-height: 1.35;
99
+ overflow-wrap: anywhere;
100
+ }
101
+
102
+ .evidence-card-kind {
103
+ color: var(--text-muted);
104
+ font-size: 11px;
105
+ text-transform: uppercase;
106
+ }
107
+
108
+ .evidence-card-state {
109
+ display: flex;
110
+ gap: 6px;
111
+ flex: 0 0 auto;
112
+ flex-wrap: wrap;
113
+ justify-content: flex-end;
114
+ }
115
+
116
+ .evidence-state {
117
+ border: 1px solid currentColor;
118
+ border-radius: 999px;
119
+ padding: 3px 7px;
120
+ font-size: 11px;
121
+ white-space: nowrap;
122
+ }
123
+
124
+ .evidence-current, .evidence-verified { color: #4ade80; }
125
+ .evidence-stale, .evidence-conflicted { color: #fb923c; }
126
+ .evidence-unverified { color: var(--text-muted); }
127
+
128
+ .evidence-card-summary {
129
+ margin: 12px 0;
130
+ color: var(--text-secondary, var(--text-muted));
131
+ line-height: 1.55;
132
+ overflow-wrap: anywhere;
133
+ }
134
+
135
+ .evidence-card-meta {
136
+ display: grid;
137
+ grid-template-columns: repeat(3, minmax(0, 1fr));
138
+ gap: 10px;
139
+ margin: 0;
140
+ }
141
+
142
+ .evidence-card-meta div { min-width: 0; }
143
+ .evidence-card-meta dt { color: var(--text-muted); font-size: 11px; margin-bottom: 3px; }
144
+ .evidence-card-meta dd { margin: 0; font-size: 12px; overflow-wrap: anywhere; }
145
+ .evidence-card-warning { margin-top: 10px; color: #fb923c; font-size: 12px; overflow-wrap: anywhere; }
146
+
147
+ @media (max-width: 600px) {
148
+ .evidence-card-header { display: block; }
149
+ .evidence-card-state { justify-content: flex-start; margin-top: 10px; }
150
+ .evidence-card-meta { grid-template-columns: 1fr; }
151
+ }
152
+
67
153
  /* Light Theme - MD3 */
68
154
  [data-theme="light"] {
69
155
  --bg-base: #FFFBFE;
@@ -1259,7 +1259,7 @@ function getAgentRulesContent(agent?: AgentName, scope: 'project' | 'global' = '
1259
1259
  '',
1260
1260
  `Default first step for non-trivial coding work: call \x60memorix_project_context\x60 with the user's actual task before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Memorix will choose a task-lensed brief (bugfix, feature, release, onboarding, refactor, docs, test, or general). When the task is continuing prior work, the same brief also includes a bounded prior-work projection. Treat its "Start here" files as the first ${contextNoun} files to inspect.`,
1261
1261
  '',
1262
- 'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select \x60memorix_project_context\x60 first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: \x60memorix resume "<task>" --brief-json\x60. For a new task, use \x60memorix context "<task>" --brief-json\x60 instead. The absence of \x60.memorix\x60 or visible memory files never proves project memory is empty. Use \x60--json\x60 only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
1262
+ 'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select \x60memorix_project_context\x60 first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: \x60memorix resume "<task>" --fallback --brief-json\x60. For a new task, use \x60memorix context "<task>" --fallback --brief-json\x60 instead. The absence of \x60.memorix\x60 or visible memory files never proves project memory is empty. Use \x60--json\x60 only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
1263
1263
  '',
1264
1264
  'After a successful \x60memorix_project_context\x60 result, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use \x60memorix_context_pack\x60, \x60memorix_search\x60, or \x60memorix_detail\x60 only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in \x60purpose\x60 when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.',
1265
1265
  'If the user asks for read-only work or says not to modify files, do not call \x60memorix_store\x60 just to record an assessment. Store only when the user explicitly asks to preserve it.',
@@ -31,8 +31,8 @@ export const OFFICIAL_MEMORIX_SKILLS: OfficialMemorixSkill[] = [
31
31
  '',
32
32
  '| Situation | Prefer | CLI fallback |',
33
33
  '|---|---|---|',
34
- '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --brief-json` |',
35
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
34
+ '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --fallback --brief-json` |',
35
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --fallback --brief-json` |',
36
36
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
37
37
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
38
38
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -45,7 +45,7 @@ export const OFFICIAL_MEMORIX_SKILLS: OfficialMemorixSkill[] = [
45
45
  '',
46
46
  '- Default first step for non-trivial coding work: call `memorix_project_context` before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Pass the user\'s actual task so Memorix can choose a bugfix, feature, release, onboarding, refactor, docs, test, or general brief. Start by reading its suggested files.',
47
47
  '- In Claude Code headless/print-mode, an initial MCP `pending` status is not a failure by itself. Prefer MCP first: use the client\'s tool discovery/dynamic loading to find `memorix_project_context` when it is not in the first visible tool list.',
48
- '- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --brief-json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --brief-json` instead. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
48
+ '- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --fallback --brief-json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --fallback --brief-json` instead. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
49
49
  '- After a successful `memorix_project_context`, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use Context Pack, search, or detail only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.',
50
50
  '- If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.',
51
51
  '- Use `memorix_search` for a specific decision, bug, file, or prior change that the project context did not answer. Fetch detail only for a result you still need after the brief.',
@@ -7,7 +7,7 @@ You have access to Memorix memory tools. Follow these rules to maintain persiste
7
7
  At the beginning of non-trivial coding work, use Memorix Memory Autopilot before local progress notes or broad file exploration. A session bind is not required for every conversation.
8
8
 
9
9
  1. Default first step for non-trivial coding work: call `memorix_project_context` with the user's actual task before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Memorix will choose a task-lensed brief (bugfix, feature, release, onboarding, refactor, docs, test, or general). Treat its "Start here" files as the first code or docs to inspect.
10
- 2. If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user's real task before inspecting files, Git history, progress notes, or guessing: use `memorix resume "<task>" --brief-json`. For a new task, use `memorix context "<task>" --brief-json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax. Use `--json` only when a diagnostic needs the detailed legacy payload.
10
+ 2. If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user's real task before inspecting files, Git history, progress notes, or guessing: use `memorix resume "<task>" --fallback --brief-json`. For a new task, use `memorix context "<task>" --fallback --brief-json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax. Use `--json` only when a diagnostic needs the detailed legacy payload.
11
11
  3. After a successful `memorix_project_context` result, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use `memorix_context_pack`, `memorix_search`, or `memorix_detail` only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.
12
12
  If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.
13
13
  4. Use `memorix_context_pack` when you need structured refs and freshness for code-bound memories.
@@ -0,0 +1,4 @@
1
+ /** Pure feedback math shared by retrieval/retention without opening SQLite. */
2
+ export function feedbackWeightMultiplier(weight: number): number {
3
+ return Math.max(0.35, Math.min(1.4, 0.35 + Math.max(0, Math.min(2, weight)) * 0.525));
4
+ }