memorix 1.0.8 → 1.0.10

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/dist/index.js CHANGED
@@ -675,6 +675,13 @@ var init_persistence = __esm({
675
675
  });
676
676
 
677
677
  // src/store/graph-store.ts
678
+ var graph_store_exports = {};
679
+ __export(graph_store_exports, {
680
+ GraphSqliteStore: () => GraphSqliteStore,
681
+ getGraphStore: () => getGraphStore,
682
+ initGraphStore: () => initGraphStore,
683
+ resetGraphStore: () => resetGraphStore
684
+ });
678
685
  import path6 from "path";
679
686
  import fs5 from "fs";
680
687
  function safeJsonParse(val, fallback) {
@@ -710,6 +717,16 @@ function getGraphStore() {
710
717
  if (!_graphStore) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
711
718
  return _graphStore;
712
719
  }
720
+ function resetGraphStore() {
721
+ if (_graphStore) {
722
+ try {
723
+ _graphStore.close();
724
+ } catch {
725
+ }
726
+ }
727
+ _graphStore = null;
728
+ _graphDataDir = null;
729
+ }
713
730
  var GraphSqliteStore, _graphStore, _graphDataDir;
714
731
  var init_graph_store = __esm({
715
732
  "src/store/graph-store.ts"() {
@@ -1041,7 +1058,8 @@ var init_types = __esm({
1041
1058
  "why-it-exists": "[WHY]",
1042
1059
  "decision": "[DECISION]",
1043
1060
  "trade-off": "[TRADEOFF]",
1044
- "reasoning": "[REASONING]"
1061
+ "reasoning": "[REASONING]",
1062
+ "probe": "[PROBE]"
1045
1063
  };
1046
1064
  TOPIC_KEY_FAMILIES = {
1047
1065
  "architecture": ["architecture", "design", "adr", "structure", "pattern"],
@@ -1055,6 +1073,15 @@ var init_types = __esm({
1055
1073
  });
1056
1074
 
1057
1075
  // src/store/mini-skill-store.ts
1076
+ var mini_skill_store_exports = {};
1077
+ __export(mini_skill_store_exports, {
1078
+ MiniSkillGracefulDegrade: () => MiniSkillGracefulDegrade,
1079
+ MiniSkillSqliteStore: () => MiniSkillSqliteStore,
1080
+ getMiniSkillStore: () => getMiniSkillStore,
1081
+ initMiniSkillStore: () => initMiniSkillStore,
1082
+ isMiniSkillStoreInitialized: () => isMiniSkillStoreInitialized,
1083
+ resetMiniSkillStore: () => resetMiniSkillStore
1084
+ });
1058
1085
  import path7 from "path";
1059
1086
  import fs6 from "fs";
1060
1087
  function skillToRow(skill) {
@@ -1108,6 +1135,10 @@ function getMiniSkillStore() {
1108
1135
  }
1109
1136
  return _store;
1110
1137
  }
1138
+ function resetMiniSkillStore() {
1139
+ _store = null;
1140
+ _storeDataDir = null;
1141
+ }
1111
1142
  async function initMiniSkillStore(dataDir) {
1112
1143
  if (_store && _storeDataDir === dataDir) return _store;
1113
1144
  _store = null;
@@ -1354,6 +1385,12 @@ async function promoteToMiniSkill(projectDir2, projectId, observations2, options
1354
1385
  `Cannot promote: ${nonActive.length} observation(s) are not active. Blocked: ${nonActive.map((o) => `#${o.id} (${o.status ?? "unknown"})`).join(", ")}. Only active observations can be promoted to permanent knowledge.`
1355
1386
  );
1356
1387
  }
1388
+ const probes = observations2.filter((o) => o.type === "probe");
1389
+ if (probes.length > 0) {
1390
+ throw new Error(
1391
+ `Cannot promote probe observations \u2014 they are operational heartbeats, not durable knowledge. Blocked: ${probes.map((o) => `#${o.id} "${o.title.substring(0, 60)}"`).join(", ")}.`
1392
+ );
1393
+ }
1357
1394
  if (!options?.force) {
1358
1395
  const commandLogs = observations2.filter((o) => COMMAND_LOG_TITLE.test(o.title));
1359
1396
  if (commandLogs.length > 0) {
@@ -1570,6 +1607,7 @@ var init_mini_skills = __esm({
1570
1607
  // src/config/yaml-loader.ts
1571
1608
  var yaml_loader_exports = {};
1572
1609
  __export(yaml_loader_exports, {
1610
+ clearProjectRoot: () => clearProjectRoot,
1573
1611
  initProjectRoot: () => initProjectRoot,
1574
1612
  loadYamlConfig: () => loadYamlConfig,
1575
1613
  resetYamlConfigCache: () => resetYamlConfigCache
@@ -1581,6 +1619,9 @@ function initProjectRoot(root) {
1581
1619
  globalProjectRoot = root;
1582
1620
  configCache.delete(root);
1583
1621
  }
1622
+ function clearProjectRoot() {
1623
+ globalProjectRoot = null;
1624
+ }
1584
1625
  function loadYamlConfig(projectRoot) {
1585
1626
  const resolvedRoot = projectRoot === null ? null : projectRoot ?? globalProjectRoot ?? null;
1586
1627
  const cached2 = configCache.get(resolvedRoot ?? null);
@@ -1608,6 +1649,7 @@ function loadYamlConfig(projectRoot) {
1608
1649
  ...projectConfig,
1609
1650
  // Deep merge for nested objects where both exist
1610
1651
  llm: { ...userConfig.llm, ...projectConfig.llm },
1652
+ agent: { ...userConfig.agent, ...projectConfig.agent },
1611
1653
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
1612
1654
  git: { ...userConfig.git, ...projectConfig.git },
1613
1655
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
@@ -1710,6 +1752,10 @@ var init_dotenv_loader = __esm({
1710
1752
  // src/config.ts
1711
1753
  var config_exports = {};
1712
1754
  __export(config_exports, {
1755
+ getAgentLLMApiKey: () => getAgentLLMApiKey,
1756
+ getAgentLLMBaseUrl: () => getAgentLLMBaseUrl,
1757
+ getAgentLLMModel: () => getAgentLLMModel,
1758
+ getAgentLLMProvider: () => getAgentLLMProvider,
1713
1759
  getEmbeddingApiKey: () => getEmbeddingApiKey,
1714
1760
  getEmbeddingBaseUrl: () => getEmbeddingBaseUrl,
1715
1761
  getEmbeddingDimensions: () => getEmbeddingDimensions,
@@ -1769,6 +1815,23 @@ function getLLMModel(providerDefault) {
1769
1815
  function getLLMBaseUrl(providerDefault) {
1770
1816
  return process.env.MEMORIX_LLM_BASE_URL || loadYamlConfig().llm?.baseUrl || loadFileConfig().llm?.baseUrl || providerDefault;
1771
1817
  }
1818
+ function getAgentLLMApiKey() {
1819
+ return process.env.MEMORIX_AGENT_LLM_API_KEY || loadYamlConfig().agent?.apiKey || loadFileConfig().agent?.apiKey || getLLMApiKey();
1820
+ }
1821
+ function getAgentLLMProvider() {
1822
+ if (process.env.MEMORIX_AGENT_LLM_PROVIDER) return process.env.MEMORIX_AGENT_LLM_PROVIDER;
1823
+ const yml = loadYamlConfig();
1824
+ if (yml.agent?.provider) return yml.agent.provider;
1825
+ const cfg = loadFileConfig();
1826
+ if (cfg.agent?.provider) return cfg.agent.provider;
1827
+ return getLLMProvider();
1828
+ }
1829
+ function getAgentLLMModel(providerDefault) {
1830
+ return process.env.MEMORIX_AGENT_LLM_MODEL || loadYamlConfig().agent?.model || loadFileConfig().agent?.model || getLLMModel(providerDefault);
1831
+ }
1832
+ function getAgentLLMBaseUrl(providerDefault) {
1833
+ return process.env.MEMORIX_AGENT_LLM_BASE_URL || loadYamlConfig().agent?.baseUrl || loadFileConfig().agent?.baseUrl || getLLMBaseUrl(providerDefault);
1834
+ }
1772
1835
  function getEmbeddingMode() {
1773
1836
  const env = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
1774
1837
  if (env === "fastembed" || env === "transformers" || env === "api" || env === "auto") return env;
@@ -3058,20 +3121,20 @@ async function* callLLMWithToolsStream(messages, tools) {
3058
3121
  }
3059
3122
  yield* callOpenAIWithToolsStream(messages, tools);
3060
3123
  }
3061
- function initLLM() {
3062
- const { getLLMApiKey: getLLMApiKey2, getLLMProvider: getLLMProvider2, getLLMModel: getLLMModel2, getLLMBaseUrl: getLLMBaseUrl2 } = (init_config(), __toCommonJS(config_exports));
3063
- const apiKey = getLLMApiKey2();
3124
+ function initLLM(options = {}) {
3125
+ const scope = options.scope ?? "memory";
3126
+ const apiKey = scope === "agent" ? getAgentLLMApiKey() : getLLMApiKey();
3064
3127
  if (!apiKey) {
3065
3128
  currentConfig = null;
3066
3129
  return null;
3067
3130
  }
3068
- const provider2 = getLLMProvider2();
3131
+ const provider2 = scope === "agent" ? getAgentLLMProvider() : getLLMProvider();
3069
3132
  const defaults = PROVIDER_DEFAULTS[provider2] ?? PROVIDER_DEFAULTS.openai;
3070
3133
  currentConfig = {
3071
3134
  provider: provider2,
3072
3135
  apiKey,
3073
- model: getLLMModel2(defaults.model),
3074
- baseUrl: getLLMBaseUrl2(defaults.baseUrl)
3136
+ model: scope === "agent" ? getAgentLLMModel(defaults.model) : getLLMModel(defaults.model),
3137
+ baseUrl: scope === "agent" ? getAgentLLMBaseUrl(defaults.baseUrl) : getLLMBaseUrl(defaults.baseUrl)
3075
3138
  };
3076
3139
  return currentConfig;
3077
3140
  }
@@ -3578,6 +3641,7 @@ var init_provider2 = __esm({
3578
3641
  "src/llm/provider.ts"() {
3579
3642
  "use strict";
3580
3643
  init_esm_shims();
3644
+ init_config();
3581
3645
  LLM_TIMEOUT_DEFAULT_MS = 3e4;
3582
3646
  LLM_TIMEOUT_MIN_MS = 1e3;
3583
3647
  LLM_TIMEOUT_MAX_MS = 3e5;
@@ -4407,6 +4471,10 @@ async function searchObservations(options) {
4407
4471
  if (statusFilter === "all") return true;
4408
4472
  const doc = hit.document;
4409
4473
  return (doc.status || "active") === statusFilter;
4474
+ }).filter((hit) => {
4475
+ if (options.type === "probe") return true;
4476
+ const doc = hit.document;
4477
+ return doc.type !== "probe";
4410
4478
  }).map((hit) => {
4411
4479
  const doc = hit.document;
4412
4480
  const obsType = doc.type;
@@ -6788,6 +6856,11 @@ function hasContradiction(oldText, newText) {
6788
6856
  ];
6789
6857
  return negationPatterns.some((p) => p.test(newText));
6790
6858
  }
6859
+ function normalizedSearchSimilarity(score) {
6860
+ if (!Number.isFinite(score) || score <= 0) return 0;
6861
+ if (score <= 1) return score;
6862
+ return 0;
6863
+ }
6791
6864
  function mergeNarratives(oldNarrative, newNarrative) {
6792
6865
  if (newNarrative.length > oldNarrative.length * 1.5) return newNarrative;
6793
6866
  if (oldNarrative.length > newNarrative.length * 1.5) return oldNarrative;
@@ -6878,12 +6951,13 @@ function scoreCandidate(extracted, candidate) {
6878
6951
  `${extracted.title} ${extracted.narrative}`,
6879
6952
  `${candidate.title} ${candidate.narrative}`
6880
6953
  );
6881
- const score = candidate.score * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
6954
+ const searchSimilarity = normalizedSearchSimilarity(candidate.score);
6955
+ const score = searchSimilarity * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
6882
6956
  const newLength = extracted.narrative.length + extracted.facts.join(" ").length;
6883
6957
  const oldLength = candidate.narrative.length + candidate.facts.length;
6884
6958
  const richer = newLength > oldLength * 1.15;
6885
6959
  const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
6886
- return { score, entityMatch, richer, contradiction };
6960
+ return { score, searchSimilarity, entityMatch, richer, contradiction };
6887
6961
  }
6888
6962
  async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
6889
6963
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
@@ -6906,7 +6980,7 @@ async function runResolve(extracted, projectId, searchMemories, getObservation2,
6906
6980
  }));
6907
6981
  scored.sort((a, b) => b.score - a.score);
6908
6982
  const best = scored[0];
6909
- if (best.hit.score >= SIMILARITY_DUPLICATE) {
6983
+ if (best.searchSimilarity >= SIMILARITY_DUPLICATE) {
6910
6984
  if (best.richer) {
6911
6985
  const existing = getObservation2(best.hit.observationId);
6912
6986
  const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
@@ -7093,7 +7167,8 @@ var init_evaluate = __esm({
7093
7167
  "how-it-works": 0.6,
7094
7168
  "discovery": 0.55,
7095
7169
  "what-changed": 0.45,
7096
- "session-request": 0.4
7170
+ "session-request": 0.4,
7171
+ "probe": 0.1
7097
7172
  };
7098
7173
  SPECIFICITY_PATTERNS = [
7099
7174
  /\b\d+\.\d+\.\d+\b/,
@@ -7557,6 +7632,9 @@ function scoreObservationForSessionContext(obs, projectTokens, now = Date.now())
7557
7632
  if (obs.valueCategory === "core") {
7558
7633
  score += 2;
7559
7634
  }
7635
+ if (obs.type === "probe") {
7636
+ score -= 100;
7637
+ }
7560
7638
  return score;
7561
7639
  }
7562
7640
  async function startSession(projectDir2, projectId, opts) {
@@ -7765,7 +7843,8 @@ var init_session = __esm({
7765
7843
  "how-it-works": "[INFO]",
7766
7844
  "what-changed": "[CHANGE]",
7767
7845
  "why-it-exists": "[DECISION]",
7768
- "session-request": "[SESSION]"
7846
+ "session-request": "[SESSION]",
7847
+ "probe": "[PROBE]"
7769
7848
  };
7770
7849
  TYPE_WEIGHTS2 = {
7771
7850
  "gotcha": 6,
@@ -7844,11 +7923,17 @@ function getValueCategoryMultiplier(doc) {
7844
7923
  return 1;
7845
7924
  }
7846
7925
  function getEffectiveRetentionDays(doc) {
7926
+ const typeOverride = TYPE_RETENTION_OVERRIDE[doc.type];
7927
+ if (typeOverride !== void 0) {
7928
+ const raw2 = typeOverride * getSourceRetentionMultiplier(doc);
7929
+ return Math.max(MIN_RETENTION_DAYS, raw2);
7930
+ }
7847
7931
  const importance = getImportanceLevel(doc);
7848
7932
  const raw = RETENTION_DAYS[importance] * getSourceRetentionMultiplier(doc) * getValueCategoryMultiplier(doc);
7849
7933
  return Math.max(MIN_RETENTION_DAYS, raw);
7850
7934
  }
7851
7935
  function isImmune(doc) {
7936
+ if (doc.type === "probe") return false;
7852
7937
  if (doc.valueCategory === "core") return true;
7853
7938
  const importance = getImportanceLevel(doc);
7854
7939
  if (importance === "critical") return true;
@@ -7857,6 +7942,7 @@ function isImmune(doc) {
7857
7942
  return concepts.some((c) => PROTECTED_TAGS.has(c));
7858
7943
  }
7859
7944
  function getImmunityReason(doc) {
7945
+ if (doc.type === "probe") return null;
7860
7946
  if (doc.valueCategory === "core") return "core valueCategory (formation-classified)";
7861
7947
  const importance = getImportanceLevel(doc);
7862
7948
  if (importance === "critical") return "critical importance";
@@ -8013,7 +8099,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap) {
8013
8099
  return { archived: archivedCount, remaining: activeObs.length - archivedCount };
8014
8100
  });
8015
8101
  }
8016
- var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY, MIN_RETENTION_DAYS;
8102
+ var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, TYPE_RETENTION_OVERRIDE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY, MIN_RETENTION_DAYS;
8017
8103
  var init_retention = __esm({
8018
8104
  "src/memory/retention.ts"() {
8019
8105
  "use strict";
@@ -8041,7 +8127,12 @@ var init_retention = __esm({
8041
8127
  "what-changed": "low",
8042
8128
  "why-it-exists": "medium",
8043
8129
  discovery: "low",
8044
- "session-request": "low"
8130
+ "session-request": "low",
8131
+ probe: "low"
8132
+ };
8133
+ TYPE_RETENTION_OVERRIDE = {
8134
+ probe: 7
8135
+ // Operational heartbeats: expire after ~7 days regardless of source/valueCategory
8045
8136
  };
8046
8137
  PROTECTED_TAGS = /* @__PURE__ */ new Set(["keep", "important", "pinned", "critical"]);
8047
8138
  MIN_ACCESS_FOR_IMMUNITY = 3;
@@ -9769,9 +9860,453 @@ var init_project_classification = __esm({
9769
9860
  }
9770
9861
  });
9771
9862
 
9863
+ // src/wiki/generator.ts
9864
+ var generator_exports = {};
9865
+ __export(generator_exports, {
9866
+ contextualHasSubstance: () => contextualHasSubstance,
9867
+ generateKnowledgeBase: () => generateKnowledgeBase,
9868
+ isCommandLog: () => isCommandLog,
9869
+ isEligible: () => isEligible,
9870
+ isExcludedType: () => isExcludedType
9871
+ });
9872
+ function isExcludedType(o) {
9873
+ return o.type === "probe";
9874
+ }
9875
+ function isCommandLog(o) {
9876
+ return COMMAND_LOG_TITLE3.test(o.title || "");
9877
+ }
9878
+ function isInactive(o) {
9879
+ const status = o.status ?? "active";
9880
+ return status !== "active";
9881
+ }
9882
+ function isOtherProject(o, projectId) {
9883
+ return o.projectId !== projectId;
9884
+ }
9885
+ function contextualHasSubstance(o) {
9886
+ if (o.valueCategory !== "contextual") return true;
9887
+ const hasFacts = (o.facts?.length ?? 0) > 0;
9888
+ const hasConcepts = (o.concepts?.length ?? 0) > 0;
9889
+ const hasFiles = (o.filesModified?.length ?? 0) > 0;
9890
+ const hasEntity = !!(o.entityName && o.entityName !== "quick-note" && o.entityName !== "unknown");
9891
+ return hasFacts || hasConcepts || hasFiles || hasEntity;
9892
+ }
9893
+ function isEligible(o, projectId) {
9894
+ if (isExcludedType(o)) return false;
9895
+ if (isCommandLog(o)) return false;
9896
+ if (isInactive(o)) return false;
9897
+ if (isOtherProject(o, projectId)) return false;
9898
+ if (o.valueCategory === "ephemeral") return false;
9899
+ if (!contextualHasSubstance(o)) return false;
9900
+ return true;
9901
+ }
9902
+ function obsToItem(o) {
9903
+ const ref = {
9904
+ kind: o.source === "git" ? "git" : "observation",
9905
+ id: `obs:${o.id}`,
9906
+ title: o.title
9907
+ };
9908
+ return {
9909
+ title: o.title,
9910
+ summary: o.narrative?.slice(0, 200) || "",
9911
+ type: o.type,
9912
+ entityName: o.entityName || void 0,
9913
+ refs: [ref]
9914
+ };
9915
+ }
9916
+ function skillToItem(s) {
9917
+ const ref = {
9918
+ kind: "mini-skill",
9919
+ id: `skill:${s.id}`,
9920
+ title: s.title
9921
+ };
9922
+ const obsRefs = s.sourceObservationIds.map(
9923
+ (oid) => ({ kind: "observation", id: `obs:${oid}` })
9924
+ );
9925
+ return {
9926
+ title: s.title,
9927
+ summary: s.instruction?.slice(0, 200) || "",
9928
+ type: "mini-skill",
9929
+ entityName: s.sourceEntity || void 0,
9930
+ refs: [ref, ...obsRefs]
9931
+ };
9932
+ }
9933
+ function buildGitSection(observations2) {
9934
+ const gitObs = observations2.filter(
9935
+ (o) => o.source === "git" && o.sourceDetail === "git-ingest"
9936
+ );
9937
+ const items = gitObs.map(obsToItem);
9938
+ return {
9939
+ id: "git-backed-facts",
9940
+ title: "Git-backed Facts",
9941
+ items,
9942
+ empty: items.length === 0
9943
+ };
9944
+ }
9945
+ function buildSkillsSection(skills) {
9946
+ const items = skills.map(skillToItem);
9947
+ return {
9948
+ id: "promoted-skills",
9949
+ title: "Promoted Skills",
9950
+ items,
9951
+ empty: items.length === 0
9952
+ };
9953
+ }
9954
+ function buildProjectOverview(projectId, eligibleObs, skills) {
9955
+ const refs = [
9956
+ ...eligibleObs.slice(0, 5).map((o) => ({
9957
+ kind: o.source === "git" ? "git" : "observation",
9958
+ id: `obs:${o.id}`,
9959
+ title: o.title
9960
+ })),
9961
+ ...skills.slice(0, 5).map((s) => ({
9962
+ kind: "mini-skill",
9963
+ id: `skill:${s.id}`,
9964
+ title: s.title
9965
+ }))
9966
+ ];
9967
+ if (refs.length === 0) {
9968
+ return {
9969
+ id: "project-overview",
9970
+ title: "Project Overview",
9971
+ items: [],
9972
+ empty: true
9973
+ };
9974
+ }
9975
+ const lines = [];
9976
+ lines.push(`Project: ${projectId}`);
9977
+ lines.push(`Observations in KB: ${eligibleObs.length}`);
9978
+ lines.push(`Promoted skills: ${skills.length}`);
9979
+ const entityCounts = /* @__PURE__ */ new Map();
9980
+ for (const o of eligibleObs) {
9981
+ if (o.entityName) {
9982
+ entityCounts.set(o.entityName, (entityCounts.get(o.entityName) || 0) + 1);
9983
+ }
9984
+ }
9985
+ const topEntities = [...entityCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
9986
+ if (topEntities.length > 0) {
9987
+ lines.push(`Top entities: ${topEntities.map(([name, count2]) => `${name} (${count2})`).join(", ")}`);
9988
+ }
9989
+ return {
9990
+ id: "project-overview",
9991
+ title: "Project Overview",
9992
+ items: [{
9993
+ title: projectId,
9994
+ summary: lines.join("\n"),
9995
+ type: "overview",
9996
+ refs
9997
+ }]
9998
+ };
9999
+ }
10000
+ function generateKnowledgeBase(options) {
10001
+ const { projectId, observations: observations2, miniSkills } = options;
10002
+ const eligible = observations2.filter((o) => isEligible(o, projectId));
10003
+ const scopedMiniSkills = miniSkills.filter((s) => s.projectId === projectId);
10004
+ const typedSections = SECTION_DEFS.map((def) => {
10005
+ const matched = eligible.filter(def.typeMatch);
10006
+ const items = matched.map(obsToItem);
10007
+ return {
10008
+ id: def.id,
10009
+ title: def.title,
10010
+ items,
10011
+ empty: items.length === 0
10012
+ };
10013
+ });
10014
+ const projectOverview = buildProjectOverview(projectId, eligible, scopedMiniSkills);
10015
+ const gitSection = buildGitSection(eligible);
10016
+ const skillsSection = buildSkillsSection(scopedMiniSkills);
10017
+ const sections = [
10018
+ projectOverview,
10019
+ ...typedSections,
10020
+ gitSection,
10021
+ skillsSection
10022
+ ];
10023
+ const allRefs = sections.flatMap((s) => s.items.flatMap((i) => i.refs));
10024
+ const obsRefCount = allRefs.filter((r) => r.kind === "observation" || r.kind === "git").length;
10025
+ const skillRefCount = allRefs.filter((r) => r.kind === "mini-skill").length;
10026
+ return {
10027
+ title: "Knowledge Base",
10028
+ subtitle: "LLM Wiki",
10029
+ projectId,
10030
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
10031
+ sections,
10032
+ stats: {
10033
+ observationsUsed: eligible.length,
10034
+ miniSkillsUsed: scopedMiniSkills.length,
10035
+ refs: obsRefCount + skillRefCount
10036
+ }
10037
+ };
10038
+ }
10039
+ var COMMAND_LOG_TITLE3, SECTION_DEFS;
10040
+ var init_generator = __esm({
10041
+ "src/wiki/generator.ts"() {
10042
+ "use strict";
10043
+ init_esm_shims();
10044
+ COMMAND_LOG_TITLE3 = /^(Ran:|Command:|Executed:)\s/i;
10045
+ SECTION_DEFS = [
10046
+ {
10047
+ id: "core-decisions",
10048
+ title: "Core Decisions",
10049
+ typeMatch: (o) => o.type === "decision" || o.type === "trade-off" || o.type === "reasoning"
10050
+ },
10051
+ {
10052
+ id: "operational-knowledge",
10053
+ title: "Operational Knowledge",
10054
+ typeMatch: (o) => o.type === "how-it-works" || o.type === "what-changed" || o.type === "why-it-exists" || o.type === "discovery" || o.type === "session-request"
10055
+ },
10056
+ {
10057
+ id: "known-gotchas",
10058
+ title: "Known Gotchas",
10059
+ typeMatch: (o) => o.type === "gotcha" || o.type === "problem-solution"
10060
+ }
10061
+ ];
10062
+ }
10063
+ });
10064
+
10065
+ // src/wiki/knowledge-graph.ts
10066
+ var knowledge_graph_exports = {};
10067
+ __export(knowledge_graph_exports, {
10068
+ generateKnowledgeGraph: () => generateKnowledgeGraph
10069
+ });
10070
+ function inferEdges(nodes, entityNameIndex) {
10071
+ const edges = [];
10072
+ const seen = /* @__PURE__ */ new Set();
10073
+ const maxRelatesEdgesPerEntitySection = 8;
10074
+ function addEdge(source, target, edgeType) {
10075
+ const key = `${source}:${edgeType}:${target}`;
10076
+ if (seen.has(key)) return;
10077
+ if (source === target) return;
10078
+ seen.add(key);
10079
+ edges.push({
10080
+ id: `e_${edges.length}_${source}_${target}`,
10081
+ source,
10082
+ target,
10083
+ edgeType
10084
+ });
10085
+ }
10086
+ function rankNodesForEntity(a, b) {
10087
+ const evidenceDiff = (b.evidenceCount || 0) - (a.evidenceCount || 0);
10088
+ if (evidenceDiff !== 0) return evidenceDiff;
10089
+ return a.id.localeCompare(b.id);
10090
+ }
10091
+ for (const [, group] of entityNameIndex) {
10092
+ if (group.length < 2) continue;
10093
+ const bySection = /* @__PURE__ */ new Map();
10094
+ for (const node of group) {
10095
+ const sectionGroup = bySection.get(node.sectionId) || [];
10096
+ sectionGroup.push(node);
10097
+ bySection.set(node.sectionId, sectionGroup);
10098
+ }
10099
+ for (const sectionGroup of bySection.values()) {
10100
+ const ranked = [...sectionGroup].sort(rankNodesForEntity);
10101
+ if (ranked.length === 2) {
10102
+ addEdge(ranked[0].id, ranked[1].id, "relates_to");
10103
+ addEdge(ranked[1].id, ranked[0].id, "relates_to");
10104
+ continue;
10105
+ }
10106
+ const anchor = ranked[0];
10107
+ for (const node of ranked.slice(1, maxRelatesEdgesPerEntitySection + 1)) {
10108
+ addEdge(anchor.id, node.id, "relates_to");
10109
+ }
10110
+ }
10111
+ const sectionAnchors = [...bySection.entries()].map(([sectionId, sectionGroup]) => ({
10112
+ sectionId,
10113
+ anchor: [...sectionGroup].sort(rankNodesForEntity)[0]
10114
+ })).sort((a, b) => sectionPriority(a.sectionId) - sectionPriority(b.sectionId));
10115
+ for (let i = 0; i < sectionAnchors.length; i++) {
10116
+ for (let j = i + 1; j < sectionAnchors.length; j++) {
10117
+ const from = sectionAnchors[i].anchor;
10118
+ const to = sectionAnchors[j].anchor;
10119
+ const edgeType = sectionPriority(sectionAnchors[i].sectionId) === sectionPriority(sectionAnchors[j].sectionId) ? "relates_to" : "supports";
10120
+ addEdge(from.id, to.id, edgeType);
10121
+ }
10122
+ }
10123
+ }
10124
+ return edges;
10125
+ }
10126
+ function sectionPriority(sectionId) {
10127
+ const order = ["core-decisions", "known-gotchas", "operational-knowledge", "git-backed-facts", "promoted-skills"];
10128
+ const idx = order.indexOf(sectionId);
10129
+ return idx >= 0 ? idx : order.length;
10130
+ }
10131
+ function sectionIdForObs(o) {
10132
+ if (GIT_SECTION.typeMatch(o)) return GIT_SECTION.id;
10133
+ for (const def of SECTION_DEFS2) {
10134
+ if (def.typeMatch(o)) return def.id;
10135
+ }
10136
+ return "operational-knowledge";
10137
+ }
10138
+ function mapRelationType(relationType) {
10139
+ const lower = relationType.toLowerCase();
10140
+ if (lower.includes("support") || lower.includes("depend")) return "supports";
10141
+ if (lower.includes("relat") || lower.includes("connect") || lower.includes("associat")) return "relates_to";
10142
+ if (lower.includes("mention") || lower.includes("refer")) return "mentions";
10143
+ if (lower.includes("deriv") || lower.includes("origin") || lower.includes("source")) return "derived_from";
10144
+ return "relates_to";
10145
+ }
10146
+ function generateKnowledgeGraph(options) {
10147
+ const { projectId, observations: observations2, miniSkills, graphEntities, graphRelations } = options;
10148
+ const eligible = observations2.filter((o) => isEligible(o, projectId));
10149
+ const scopedMiniSkills = miniSkills.filter((s) => s.projectId === projectId);
10150
+ const nodes = [];
10151
+ const entityNameIndex = /* @__PURE__ */ new Map();
10152
+ for (const o of eligible) {
10153
+ const sectionId = sectionIdForObs(o);
10154
+ const ref = {
10155
+ kind: o.source === "git" ? "git" : "observation",
10156
+ id: `obs:${o.id}`,
10157
+ title: o.title
10158
+ };
10159
+ const node = {
10160
+ id: `obs:${o.id}`,
10161
+ label: o.title,
10162
+ nodeType: o.type,
10163
+ sectionId,
10164
+ entityName: o.entityName || void 0,
10165
+ evidenceCount: (o.facts?.length ?? 0) + (o.concepts?.length ?? 0) + (o.filesModified?.length ?? 0),
10166
+ summary: o.narrative?.slice(0, 200) || "",
10167
+ refs: [ref]
10168
+ };
10169
+ nodes.push(node);
10170
+ if (o.entityName) {
10171
+ const group = entityNameIndex.get(o.entityName) || [];
10172
+ group.push(node);
10173
+ entityNameIndex.set(o.entityName, group);
10174
+ }
10175
+ }
10176
+ for (const s of scopedMiniSkills) {
10177
+ const ref = {
10178
+ kind: "mini-skill",
10179
+ id: `skill:${s.id}`,
10180
+ title: s.title
10181
+ };
10182
+ const obsRefs = s.sourceObservationIds.map(
10183
+ (oid) => ({ kind: "observation", id: `obs:${oid}` })
10184
+ );
10185
+ const node = {
10186
+ id: `skill:${s.id}`,
10187
+ label: s.title,
10188
+ nodeType: "mini-skill",
10189
+ sectionId: "promoted-skills",
10190
+ entityName: s.sourceEntity || void 0,
10191
+ evidenceCount: obsRefs.length,
10192
+ summary: s.instruction?.slice(0, 200) || "",
10193
+ refs: [ref, ...obsRefs]
10194
+ };
10195
+ nodes.push(node);
10196
+ if (s.sourceEntity) {
10197
+ const group = entityNameIndex.get(s.sourceEntity) || [];
10198
+ group.push(node);
10199
+ entityNameIndex.set(s.sourceEntity, group);
10200
+ }
10201
+ }
10202
+ const edges = inferEdges(nodes, entityNameIndex);
10203
+ for (const s of scopedMiniSkills) {
10204
+ const skillNodeId = `skill:${s.id}`;
10205
+ for (const oid of s.sourceObservationIds) {
10206
+ const obsNodeId = `obs:${oid}`;
10207
+ if (nodes.some((n) => n.id === obsNodeId)) {
10208
+ edges.push({
10209
+ id: `e_${edges.length}_${skillNodeId}_${obsNodeId}`,
10210
+ source: skillNodeId,
10211
+ target: obsNodeId,
10212
+ edgeType: "derived_from"
10213
+ });
10214
+ }
10215
+ }
10216
+ }
10217
+ if (graphRelations && graphRelations.length > 0) {
10218
+ const entityNodeMap = /* @__PURE__ */ new Map();
10219
+ for (const n of nodes) {
10220
+ if (n.entityName && !entityNodeMap.has(n.entityName)) {
10221
+ entityNodeMap.set(n.entityName, n.id);
10222
+ }
10223
+ }
10224
+ const seen = new Set(edges.map((e) => `${e.source}:${e.edgeType}:${e.target}`));
10225
+ for (const rel of graphRelations) {
10226
+ const srcId = entityNodeMap.get(rel.from);
10227
+ const tgtId = entityNodeMap.get(rel.to);
10228
+ if (!srcId || !tgtId) continue;
10229
+ if (srcId === tgtId) continue;
10230
+ const edgeType = mapRelationType(rel.relationType);
10231
+ const key = `${srcId}:${edgeType}:${tgtId}`;
10232
+ if (seen.has(key)) continue;
10233
+ seen.add(key);
10234
+ edges.push({
10235
+ id: `e_gr_${edges.length}_${srcId}_${tgtId}`,
10236
+ source: srcId,
10237
+ target: tgtId,
10238
+ edgeType
10239
+ });
10240
+ }
10241
+ }
10242
+ const sectionCounts = {};
10243
+ for (const n of nodes) {
10244
+ sectionCounts[n.sectionId] = (sectionCounts[n.sectionId] || 0) + 1;
10245
+ }
10246
+ const clusters = ALL_SECTIONS.filter((def) => (sectionCounts[def.id] ?? 0) > 0).map((def) => ({
10247
+ id: `cluster:${def.id}`,
10248
+ label: def.label,
10249
+ sectionId: def.id,
10250
+ nodeCount: sectionCounts[def.id] ?? 0
10251
+ }));
10252
+ const stats = {
10253
+ totalNodes: nodes.length,
10254
+ totalEdges: edges.length,
10255
+ clusterCount: clusters.length,
10256
+ sectionCounts
10257
+ };
10258
+ return {
10259
+ title: "Knowledge Graph",
10260
+ projectId,
10261
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
10262
+ nodes,
10263
+ edges,
10264
+ clusters,
10265
+ stats
10266
+ };
10267
+ }
10268
+ var SECTION_DEFS2, GIT_SECTION, SKILLS_SECTION, ALL_SECTIONS;
10269
+ var init_knowledge_graph = __esm({
10270
+ "src/wiki/knowledge-graph.ts"() {
10271
+ "use strict";
10272
+ init_esm_shims();
10273
+ init_generator();
10274
+ SECTION_DEFS2 = [
10275
+ {
10276
+ id: "core-decisions",
10277
+ label: "Core Decisions",
10278
+ typeMatch: (o) => o.type === "decision" || o.type === "trade-off" || o.type === "reasoning"
10279
+ },
10280
+ {
10281
+ id: "operational-knowledge",
10282
+ label: "Operational Knowledge",
10283
+ typeMatch: (o) => o.type === "how-it-works" || o.type === "what-changed" || o.type === "why-it-exists" || o.type === "discovery" || o.type === "session-request"
10284
+ },
10285
+ {
10286
+ id: "known-gotchas",
10287
+ label: "Known Gotchas",
10288
+ typeMatch: (o) => o.type === "gotcha" || o.type === "problem-solution"
10289
+ }
10290
+ ];
10291
+ GIT_SECTION = {
10292
+ id: "git-backed-facts",
10293
+ label: "Git-backed Facts",
10294
+ typeMatch: (o) => o.source === "git" && o.sourceDetail === "git-ingest"
10295
+ };
10296
+ SKILLS_SECTION = {
10297
+ id: "promoted-skills",
10298
+ label: "Promoted Skills",
10299
+ typeMatch: () => false
10300
+ // skills handled separately
10301
+ };
10302
+ ALL_SECTIONS = [...SECTION_DEFS2, GIT_SECTION, SKILLS_SECTION];
10303
+ }
10304
+ });
10305
+
9772
10306
  // src/dashboard/server.ts
9773
10307
  var server_exports = {};
9774
10308
  __export(server_exports, {
10309
+ prepareDashboardConfig: () => prepareDashboardConfig,
9775
10310
  startDashboard: () => startDashboard
9776
10311
  });
9777
10312
  import { createServer } from "http";
@@ -9797,6 +10332,25 @@ function isActiveStatus(status) {
9797
10332
  function filterActiveByProject(items, projectId) {
9798
10333
  return items.filter((item) => item.projectId === projectId && isActiveStatus(item.status));
9799
10334
  }
10335
+ function prepareDashboardConfig(projectRoot) {
10336
+ if (!projectRoot) {
10337
+ if (preparedDashboardProjectRoot !== null) {
10338
+ resetDotenv();
10339
+ preparedDashboardProjectRoot = null;
10340
+ }
10341
+ clearProjectRoot();
10342
+ return;
10343
+ }
10344
+ try {
10345
+ if (preparedDashboardProjectRoot !== null && preparedDashboardProjectRoot !== projectRoot) {
10346
+ resetDotenv();
10347
+ }
10348
+ initProjectRoot(projectRoot);
10349
+ loadDotenv(projectRoot);
10350
+ preparedDashboardProjectRoot = projectRoot;
10351
+ } catch {
10352
+ }
10353
+ }
9800
10354
  function computeProjectGraphCounts(allEntities, allRelations, projectObs) {
9801
10355
  const entityNames = new Set(
9802
10356
  projectObs.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
@@ -9822,6 +10376,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9822
10376
  effectiveProjectResolved = true;
9823
10377
  effectiveProjectRoot = null;
9824
10378
  }
10379
+ prepareDashboardConfig(effectiveProjectRoot);
9825
10380
  try {
9826
10381
  switch (apiPath) {
9827
10382
  case "/projects": {
@@ -9910,6 +10465,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9910
10465
  const typeCounts = {};
9911
10466
  for (const obs of observations2) {
9912
10467
  const t = obs.type || "unknown";
10468
+ if (t === "probe") continue;
9913
10469
  typeCounts[t] = (typeCounts[t] || 0) + 1;
9914
10470
  }
9915
10471
  const sourceCounts = { git: 0, agent: 0, manual: 0 };
@@ -9951,7 +10507,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9951
10507
  else if (score >= 1) retentionSummary.stale++;
9952
10508
  else retentionSummary.archive++;
9953
10509
  }
9954
- const sorted = [...observations2].sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
10510
+ const sorted = [...observations2].filter((o) => o.type !== "probe").sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
9955
10511
  let embeddingStatus = { enabled: false, provider: "", dimensions: 0 };
9956
10512
  try {
9957
10513
  const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
@@ -9991,7 +10547,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9991
10547
  const allObs = await getObservationStore().loadAll();
9992
10548
  const observations2 = filterActiveByProject(allObs, effectiveProjectId);
9993
10549
  const now = Date.now();
9994
- const scored = observations2.map((obs) => {
10550
+ const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
9995
10551
  const age = now - new Date(obs.createdAt || now).getTime();
9996
10552
  const ageHours = age / (1e3 * 60 * 60);
9997
10553
  const importance = obs.importance ?? 5;
@@ -10023,6 +10579,50 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
10023
10579
  });
10024
10580
  break;
10025
10581
  }
10582
+ case "/knowledge": {
10583
+ const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
10584
+ const { initObservations: initObservations2, getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
10585
+ const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
10586
+ await initObservations2(effectiveDataDir);
10587
+ await initMiniSkillStore2(effectiveDataDir);
10588
+ const allObs = getAllObservations2();
10589
+ const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
10590
+ const overview = generateKnowledgeBase2({
10591
+ projectId: effectiveProjectId,
10592
+ observations: allObs,
10593
+ miniSkills: skills
10594
+ });
10595
+ sendJson(res, overview);
10596
+ break;
10597
+ }
10598
+ case "/knowledge-graph": {
10599
+ const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
10600
+ const { initObservations: initObservations2, getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
10601
+ const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
10602
+ const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
10603
+ await initObservations2(effectiveDataDir);
10604
+ await initMiniSkillStore2(effectiveDataDir);
10605
+ await initGraphStore2(effectiveDataDir);
10606
+ const allObs = getAllObservations2();
10607
+ const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
10608
+ const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
10609
+ const graphObs = await getObservationStore().loadAll();
10610
+ const projectEntityNames = new Set(
10611
+ graphObs.filter((o) => o.projectId === effectiveProjectId && (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
10612
+ );
10613
+ const scopedEntities = fullGraph.entities.filter((e) => projectEntityNames.has(e.name));
10614
+ const scopedEntityNameSet = new Set(scopedEntities.map((e) => e.name));
10615
+ const scopedRelations = fullGraph.relations.filter((r) => scopedEntityNameSet.has(r.from) && scopedEntityNameSet.has(r.to));
10616
+ const graph = generateKnowledgeGraph2({
10617
+ projectId: effectiveProjectId,
10618
+ observations: allObs,
10619
+ miniSkills: skills,
10620
+ graphEntities: scopedEntities,
10621
+ graphRelations: scopedRelations
10622
+ });
10623
+ sendJson(res, graph);
10624
+ break;
10625
+ }
10026
10626
  case "/config": {
10027
10627
  const os4 = await import("os");
10028
10628
  const { existsSync: existsSync10 } = await import("fs");
@@ -10031,7 +10631,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
10031
10631
  const configProjectRoot = effectiveProjectRoot;
10032
10632
  try {
10033
10633
  const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
10034
- yml = loadYamlConfig2();
10634
+ yml = configProjectRoot ? loadYamlConfig2(configProjectRoot) : loadYamlConfig2(null);
10035
10635
  } catch {
10036
10636
  }
10037
10637
  if (configProjectRoot) {
@@ -10094,6 +10694,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
10094
10694
  } else {
10095
10695
  values.push({ key: "llm.apiKey", value: "not set", source: "none" });
10096
10696
  }
10697
+ const agentProvider = process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
10698
+ if (agentProvider) values.push({ key: "agent.llm.provider", value: agentProvider, source: await getEnvSource("MEMORIX_AGENT_LLM_PROVIDER", yml.agent?.provider ? "memorix.yml" : void 0) });
10699
+ const agentModel = process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
10700
+ if (agentModel) values.push({ key: "agent.llm.model", value: agentModel, source: await getEnvSource("MEMORIX_AGENT_LLM_MODEL", yml.agent?.model ? "memorix.yml" : void 0) });
10701
+ const agentKey = process.env.MEMORIX_AGENT_LLM_API_KEY || yml.agent?.apiKey;
10702
+ if (agentKey) {
10703
+ let src = "unknown";
10704
+ if (process.env.MEMORIX_AGENT_LLM_API_KEY) src = await getEnvSource("MEMORIX_AGENT_LLM_API_KEY");
10705
+ else if (yml.agent?.apiKey) src = "memorix.yml (move to .env!)";
10706
+ values.push({ key: "agent.llm.apiKey", value: "****" + agentKey.slice(-4), source: src, sensitive: true });
10707
+ } else {
10708
+ values.push({ key: "agent.llm.apiKey", value: "fallback to llm.apiKey", source: "default" });
10709
+ }
10097
10710
  const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
10098
10711
  values.push({ key: "embedding.provider", value: embProvider, source: await getEnvSource("MEMORIX_EMBEDDING", yml.embedding?.provider ? "memorix.yml" : void 0) });
10099
10712
  values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
@@ -10259,13 +10872,20 @@ async function serveStatic(req, res, staticDir) {
10259
10872
  const ext = path14.extname(filePath);
10260
10873
  res.writeHead(200, {
10261
10874
  "Content-Type": MIME_TYPES[ext] || "application/octet-stream",
10262
- "Cache-Control": "no-cache"
10875
+ "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
10876
+ "Pragma": "no-cache",
10877
+ "Expires": "0"
10263
10878
  });
10264
10879
  res.end(data);
10265
10880
  } catch {
10266
10881
  try {
10267
10882
  const indexData = await fs12.readFile(path14.join(staticDir, "index.html"));
10268
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
10883
+ res.writeHead(200, {
10884
+ "Content-Type": "text/html; charset=utf-8",
10885
+ "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
10886
+ "Pragma": "no-cache",
10887
+ "Expires": "0"
10888
+ });
10269
10889
  res.end(indexData);
10270
10890
  } catch {
10271
10891
  sendError(res, "Not found", 404);
@@ -10522,7 +11142,7 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
10522
11142
  });
10523
11143
  });
10524
11144
  }
10525
- var MIME_TYPES;
11145
+ var MIME_TYPES, preparedDashboardProjectRoot;
10526
11146
  var init_server = __esm({
10527
11147
  "src/dashboard/server.ts"() {
10528
11148
  "use strict";
@@ -10531,6 +11151,10 @@ var init_server = __esm({
10531
11151
  init_obs_store();
10532
11152
  init_session_store();
10533
11153
  init_graph_store();
11154
+ init_dotenv_loader();
11155
+ init_dotenv_loader();
11156
+ init_yaml_loader();
11157
+ init_yaml_loader();
10534
11158
  MIME_TYPES = {
10535
11159
  ".html": "text/html; charset=utf-8",
10536
11160
  ".css": "text/css; charset=utf-8",
@@ -10540,6 +11164,7 @@ var init_server = __esm({
10540
11164
  ".png": "image/png",
10541
11165
  ".ico": "image/x-icon"
10542
11166
  };
11167
+ preparedDashboardProjectRoot = null;
10543
11168
  }
10544
11169
  });
10545
11170
 
@@ -11696,7 +12321,7 @@ function generateKiroHookFiles() {
11696
12321
  when: { type: "promptSubmit" },
11697
12322
  then: {
11698
12323
  type: "askAgent",
11699
- prompt: "Before responding, load context:\n1. Call memorix_session_start to get previous session summary and key memories\n2. Call memorix_search with a query related to the user's prompt for additional context\n3. If search results are found, use memorix_detail to fetch the most relevant ones\n4. Reference relevant memories naturally in your response"
12324
+ prompt: "Before responding, load useful project context:\n1. Call memorix_search with a query related to the user's prompt for relevant memories\n2. If search results are found, use memorix_detail to fetch the most relevant ones\n3. If memorix_search says this is a fresh project with no Memorix memories yet, do not repeat memorix_search again in the same turn unless the user explicitly asks for history/context or new memories were written\n4. Call memorix_session_start only when explicit session semantics are useful, such as handoff, long-running work, team coordination, or HTTP project binding\n5. Reference relevant memories naturally in your response"
11700
12325
  }
11701
12326
  }, null, 2)
11702
12327
  },
@@ -11736,6 +12361,8 @@ import { spawnSync } from 'node:child_process';
11736
12361
  export const MemorixPlugin = async ({ project, client, $, directory, worktree }) => {
11737
12362
  // Generate a stable session ID for this plugin lifetime
11738
12363
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
12364
+ let pendingAssistantResponse = null;
12365
+ let lastDeliveredAssistantKey = '';
11739
12366
 
11740
12367
  /**
11741
12368
  * Send event JSON to \`memorix hook\` via child_process.spawnSync.
@@ -11768,6 +12395,33 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
11768
12395
  }
11769
12396
  }
11770
12397
 
12398
+ function extractMessageInfo(input) {
12399
+ if (input && typeof input === 'object') {
12400
+ if (input.info && typeof input.info === 'object') return input.info;
12401
+ if (input.message && typeof input.message === 'object') return input.message;
12402
+ if (input.properties && typeof input.properties === 'object') {
12403
+ if (input.properties.info && typeof input.properties.info === 'object') return input.properties.info;
12404
+ if (input.properties.message && typeof input.properties.message === 'object') return input.properties.message;
12405
+ }
12406
+ }
12407
+ return input;
12408
+ }
12409
+
12410
+ function extractMessageText(message) {
12411
+ if (!message || typeof message !== 'object') return '';
12412
+ if (typeof message.content === 'string') return message.content.trim();
12413
+ const parts = Array.isArray(message.parts) ? message.parts : [];
12414
+ return parts
12415
+ .map((part) => {
12416
+ if (!part || typeof part !== 'object') return '';
12417
+ if (typeof part.text === 'string') return part.text;
12418
+ if (typeof part.content === 'string') return part.content;
12419
+ return '';
12420
+ })
12421
+ .join('')
12422
+ .trim();
12423
+ }
12424
+
11771
12425
  return {
11772
12426
  /** Session created \u2014 record session start */
11773
12427
  'session.created': async ({ session }) => {
@@ -11780,6 +12434,21 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
11780
12434
 
11781
12435
  /** Session idle \u2014 record session end */
11782
12436
  'session.idle': async ({ session }) => {
12437
+ if (pendingAssistantResponse?.text) {
12438
+ const deliveryKey = pendingAssistantResponse.id
12439
+ ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
12440
+ : pendingAssistantResponse.text;
12441
+ if (deliveryKey !== lastDeliveredAssistantKey) {
12442
+ runHook({
12443
+ agent: 'opencode',
12444
+ hook_event_name: 'message.updated',
12445
+ ai_response: pendingAssistantResponse.text,
12446
+ message_id: pendingAssistantResponse.id,
12447
+ cwd: directory,
12448
+ });
12449
+ lastDeliveredAssistantKey = deliveryKey;
12450
+ }
12451
+ }
11783
12452
  runHook({
11784
12453
  agent: 'opencode',
11785
12454
  hook_event_name: 'session.idle',
@@ -11808,6 +12477,19 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
11808
12477
  });
11809
12478
  },
11810
12479
 
12480
+ /** Message updated \u2014 cache the latest assistant response until session.idle */
12481
+ 'message.updated': async (input, output) => {
12482
+ const message = extractMessageInfo(input);
12483
+ const role = message?.role ?? input?.role ?? input?.info?.role;
12484
+ if (role !== 'assistant') return;
12485
+ const text = extractMessageText(message);
12486
+ if (!text) return;
12487
+ pendingAssistantResponse = {
12488
+ id: message?.id ?? input?.id ?? input?.messageID,
12489
+ text,
12490
+ };
12491
+ },
12492
+
11811
12493
  /** Session compacted \u2014 record post-compact event */
11812
12494
  'session.compacted': async ({ session }) => {
11813
12495
  runHook({
@@ -12057,7 +12739,7 @@ async function installHooks(agent, projectRoot, global = false) {
12057
12739
  return {
12058
12740
  agent,
12059
12741
  configPath: pluginPath,
12060
- events: ["session_start", "session_end", "post_tool", "post_edit", "post_compact", "post_command"],
12742
+ events: ["session_start", "session_end", "post_tool", "post_edit", "post_compact", "post_command", "post_response"],
12061
12743
  generated: { note: "OpenCode plugin installed at " + pluginPath }
12062
12744
  };
12063
12745
  }
@@ -12250,20 +12932,18 @@ alwaysApply: true
12250
12932
 
12251
12933
  You have access to Memorix memory tools. Follow these rules to maintain persistent context across sessions.
12252
12934
 
12253
- ## RULE 1: Session Start \u2014 Bind Project, Then Load Context
12935
+ ## RULE 1: Use Memory When Useful
12254
12936
 
12255
- At the **beginning of every conversation**, BEFORE responding to the user:
12937
+ At the beginning of a conversation, use Memorix when prior project context would materially help the task. Do not require a session bind for every conversation.
12256
12938
 
12257
- 1. Call \`memorix_session_start\` with parameters:
12258
- - \`agent\`: your agent identifier (e.g. "windsurf", "codex", "antigravity")
12259
- - \`projectRoot\`: the **absolute path** of the current workspace or repo root
12260
- This binds the session to the correct project. Without \`projectRoot\`, memories may go to the wrong bucket.
12261
- 2. Then call \`memorix_search\` with a query related to the user's first message for additional context
12262
- 3. If search results are found, use \`memorix_detail\` to fetch the most relevant ones
12263
- 4. Reference relevant memories naturally \u2014 the user should feel you "remember" them
12939
+ 1. Call \`memorix_search\` with a query related to the user's first message or current task.
12940
+ 2. If search results are found, use \`memorix_detail\` to fetch the most relevant ones.
12941
+ 3. If \`memorix_search\` says this is a fresh project with no Memorix memories yet, treat that as a successful cold-start signal. Do not repeat \`memorix_search\` again in the same turn unless the user explicitly asks for history/context or new memories were written.
12942
+ 4. Call \`memorix_session_start\` only when explicit session semantics are useful: handoff, long-running work, team coordination, restoring prior session context, or HTTP project binding.
12943
+ 5. Reference relevant memories naturally \u2014 the user should feel you understand the project, not that you are following a ritual.
12264
12944
 
12265
12945
  **Important:** \`projectRoot\` is a detection anchor only; Git remains the source of truth for project identity.
12266
- In HTTP control-plane mode (\`memorix serve-http\` / \`memorix background start\`), explicit \`projectRoot\` binding is required for correct multi-project isolation.
12946
+ In HTTP control-plane mode (\`memorix serve-http\` / \`memorix background start\`), explicit \`projectRoot\` binding is recommended when the workspace path is available and required for correct multi-project isolation.
12267
12947
  \`memorix_session_start\` is lightweight by default: it starts memory/session context only. Do not set \`joinTeam\` unless the user explicitly needs autonomous Agent Team tasks, messages, file locks, or orchestrated CLI-agent workflows.
12268
12948
 
12269
12949
  ## RULE 2: Store Important Context
@@ -12515,7 +13195,7 @@ var init_installers = __esm({
12515
13195
  "src/hooks/installers/index.ts"() {
12516
13196
  "use strict";
12517
13197
  init_esm_shims();
12518
- OPENCODE_PLUGIN_VERSION = 5;
13198
+ OPENCODE_PLUGIN_VERSION = 6;
12519
13199
  }
12520
13200
  });
12521
13201
 
@@ -13046,7 +13726,8 @@ function getTypeIcon(type) {
13046
13726
  "why-it-exists": "[WHY]",
13047
13727
  "decision": "[DECISION]",
13048
13728
  "trade-off": "[TRADEOFF]",
13049
- "reasoning": "[REASONING]"
13729
+ "reasoning": "[REASONING]",
13730
+ "probe": "[PROBE]"
13050
13731
  };
13051
13732
  return icons[type] ?? "[UNKNOWN]";
13052
13733
  }
@@ -13107,7 +13788,18 @@ init_mini_skills();
13107
13788
  init_secret_filter();
13108
13789
  async function compactSearch(options) {
13109
13790
  const entries = await searchObservations(options);
13110
- const formatted = formatIndexTable(entries, options.query, !options.projectId);
13791
+ let formatted = formatIndexTable(entries, options.query, !options.projectId);
13792
+ if (entries.length === 0 && options.projectId) {
13793
+ const allObservations = getAllObservations();
13794
+ const projectHasStoredMemory = allObservations.some((obs) => obs.projectId === options.projectId);
13795
+ if (!projectHasStoredMemory) {
13796
+ formatted = `This project does not have any Memorix memories yet.
13797
+
13798
+ It looks like a fresh project: the tool call worked, but there is nothing stored to retrieve yet.
13799
+
13800
+ Memories will start appearing after observations, session summaries, hook captures, or git-memory are written.`;
13801
+ }
13802
+ }
13111
13803
  const totalTokens = countTextTokens(formatted);
13112
13804
  return { entries, formatted, totalTokens };
13113
13805
  }
@@ -15545,7 +16237,8 @@ var OBSERVATION_TYPES = [
15545
16237
  "discovery",
15546
16238
  "why-it-exists",
15547
16239
  "decision",
15548
- "trade-off"
16240
+ "trade-off",
16241
+ "probe"
15549
16242
  ];
15550
16243
  function coerceNumberArray(val) {
15551
16244
  if (Array.isArray(val)) return val.map(Number);
@@ -15774,7 +16467,7 @@ The path should point to a directory containing a .git folder.`
15774
16467
  };
15775
16468
  const server = existingServer ?? new McpServer({
15776
16469
  name: "memorix",
15777
- version: true ? "1.0.8" : "1.0.1"
16470
+ version: true ? "1.0.10" : "1.0.1"
15778
16471
  });
15779
16472
  const originalRegisterTool = server.registerTool.bind(server);
15780
16473
  server.registerTool = ((name, ...args) => {