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/sdk.js CHANGED
@@ -674,6 +674,13 @@ var init_persistence = __esm({
674
674
  });
675
675
 
676
676
  // src/store/graph-store.ts
677
+ var graph_store_exports = {};
678
+ __export(graph_store_exports, {
679
+ GraphSqliteStore: () => GraphSqliteStore,
680
+ getGraphStore: () => getGraphStore,
681
+ initGraphStore: () => initGraphStore,
682
+ resetGraphStore: () => resetGraphStore
683
+ });
677
684
  import path6 from "path";
678
685
  import fs5 from "fs";
679
686
  function safeJsonParse(val, fallback) {
@@ -709,6 +716,16 @@ function getGraphStore() {
709
716
  if (!_graphStore) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
710
717
  return _graphStore;
711
718
  }
719
+ function resetGraphStore() {
720
+ if (_graphStore) {
721
+ try {
722
+ _graphStore.close();
723
+ } catch {
724
+ }
725
+ }
726
+ _graphStore = null;
727
+ _graphDataDir = null;
728
+ }
712
729
  var GraphSqliteStore, _graphStore, _graphDataDir;
713
730
  var init_graph_store = __esm({
714
731
  "src/store/graph-store.ts"() {
@@ -1040,7 +1057,8 @@ var init_types = __esm({
1040
1057
  "why-it-exists": "[WHY]",
1041
1058
  "decision": "[DECISION]",
1042
1059
  "trade-off": "[TRADEOFF]",
1043
- "reasoning": "[REASONING]"
1060
+ "reasoning": "[REASONING]",
1061
+ "probe": "[PROBE]"
1044
1062
  };
1045
1063
  TOPIC_KEY_FAMILIES = {
1046
1064
  "architecture": ["architecture", "design", "adr", "structure", "pattern"],
@@ -1059,6 +1077,15 @@ var init_types = __esm({
1059
1077
  });
1060
1078
 
1061
1079
  // src/store/mini-skill-store.ts
1080
+ var mini_skill_store_exports = {};
1081
+ __export(mini_skill_store_exports, {
1082
+ MiniSkillGracefulDegrade: () => MiniSkillGracefulDegrade,
1083
+ MiniSkillSqliteStore: () => MiniSkillSqliteStore,
1084
+ getMiniSkillStore: () => getMiniSkillStore,
1085
+ initMiniSkillStore: () => initMiniSkillStore,
1086
+ isMiniSkillStoreInitialized: () => isMiniSkillStoreInitialized,
1087
+ resetMiniSkillStore: () => resetMiniSkillStore
1088
+ });
1062
1089
  import path7 from "path";
1063
1090
  import fs6 from "fs";
1064
1091
  function skillToRow(skill) {
@@ -1112,6 +1139,10 @@ function getMiniSkillStore() {
1112
1139
  }
1113
1140
  return _store;
1114
1141
  }
1142
+ function resetMiniSkillStore() {
1143
+ _store = null;
1144
+ _storeDataDir = null;
1145
+ }
1115
1146
  async function initMiniSkillStore(dataDir) {
1116
1147
  if (_store && _storeDataDir === dataDir) return _store;
1117
1148
  _store = null;
@@ -1358,6 +1389,12 @@ async function promoteToMiniSkill(projectDir2, projectId, observations2, options
1358
1389
  `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.`
1359
1390
  );
1360
1391
  }
1392
+ const probes = observations2.filter((o) => o.type === "probe");
1393
+ if (probes.length > 0) {
1394
+ throw new Error(
1395
+ `Cannot promote probe observations \u2014 they are operational heartbeats, not durable knowledge. Blocked: ${probes.map((o) => `#${o.id} "${o.title.substring(0, 60)}"`).join(", ")}.`
1396
+ );
1397
+ }
1361
1398
  if (!options?.force) {
1362
1399
  const commandLogs = observations2.filter((o) => COMMAND_LOG_TITLE.test(o.title));
1363
1400
  if (commandLogs.length > 0) {
@@ -1574,6 +1611,7 @@ var init_mini_skills = __esm({
1574
1611
  // src/config/yaml-loader.ts
1575
1612
  var yaml_loader_exports = {};
1576
1613
  __export(yaml_loader_exports, {
1614
+ clearProjectRoot: () => clearProjectRoot,
1577
1615
  initProjectRoot: () => initProjectRoot,
1578
1616
  loadYamlConfig: () => loadYamlConfig,
1579
1617
  resetYamlConfigCache: () => resetYamlConfigCache
@@ -1585,6 +1623,9 @@ function initProjectRoot(root) {
1585
1623
  globalProjectRoot = root;
1586
1624
  configCache.delete(root);
1587
1625
  }
1626
+ function clearProjectRoot() {
1627
+ globalProjectRoot = null;
1628
+ }
1588
1629
  function loadYamlConfig(projectRoot) {
1589
1630
  const resolvedRoot = projectRoot === null ? null : projectRoot ?? globalProjectRoot ?? null;
1590
1631
  const cached2 = configCache.get(resolvedRoot ?? null);
@@ -1612,6 +1653,7 @@ function loadYamlConfig(projectRoot) {
1612
1653
  ...projectConfig,
1613
1654
  // Deep merge for nested objects where both exist
1614
1655
  llm: { ...userConfig.llm, ...projectConfig.llm },
1656
+ agent: { ...userConfig.agent, ...projectConfig.agent },
1615
1657
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
1616
1658
  git: { ...userConfig.git, ...projectConfig.git },
1617
1659
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
@@ -1714,6 +1756,10 @@ var init_dotenv_loader = __esm({
1714
1756
  // src/config.ts
1715
1757
  var config_exports = {};
1716
1758
  __export(config_exports, {
1759
+ getAgentLLMApiKey: () => getAgentLLMApiKey,
1760
+ getAgentLLMBaseUrl: () => getAgentLLMBaseUrl,
1761
+ getAgentLLMModel: () => getAgentLLMModel,
1762
+ getAgentLLMProvider: () => getAgentLLMProvider,
1717
1763
  getEmbeddingApiKey: () => getEmbeddingApiKey,
1718
1764
  getEmbeddingBaseUrl: () => getEmbeddingBaseUrl,
1719
1765
  getEmbeddingDimensions: () => getEmbeddingDimensions,
@@ -1773,6 +1819,23 @@ function getLLMModel(providerDefault) {
1773
1819
  function getLLMBaseUrl(providerDefault) {
1774
1820
  return process.env.MEMORIX_LLM_BASE_URL || loadYamlConfig().llm?.baseUrl || loadFileConfig().llm?.baseUrl || providerDefault;
1775
1821
  }
1822
+ function getAgentLLMApiKey() {
1823
+ return process.env.MEMORIX_AGENT_LLM_API_KEY || loadYamlConfig().agent?.apiKey || loadFileConfig().agent?.apiKey || getLLMApiKey();
1824
+ }
1825
+ function getAgentLLMProvider() {
1826
+ if (process.env.MEMORIX_AGENT_LLM_PROVIDER) return process.env.MEMORIX_AGENT_LLM_PROVIDER;
1827
+ const yml = loadYamlConfig();
1828
+ if (yml.agent?.provider) return yml.agent.provider;
1829
+ const cfg = loadFileConfig();
1830
+ if (cfg.agent?.provider) return cfg.agent.provider;
1831
+ return getLLMProvider();
1832
+ }
1833
+ function getAgentLLMModel(providerDefault) {
1834
+ return process.env.MEMORIX_AGENT_LLM_MODEL || loadYamlConfig().agent?.model || loadFileConfig().agent?.model || getLLMModel(providerDefault);
1835
+ }
1836
+ function getAgentLLMBaseUrl(providerDefault) {
1837
+ return process.env.MEMORIX_AGENT_LLM_BASE_URL || loadYamlConfig().agent?.baseUrl || loadFileConfig().agent?.baseUrl || getLLMBaseUrl(providerDefault);
1838
+ }
1776
1839
  function getEmbeddingMode() {
1777
1840
  const env = process.env.MEMORIX_EMBEDDING?.toLowerCase()?.trim();
1778
1841
  if (env === "fastembed" || env === "transformers" || env === "api" || env === "auto") return env;
@@ -3062,20 +3125,20 @@ async function* callLLMWithToolsStream(messages, tools) {
3062
3125
  }
3063
3126
  yield* callOpenAIWithToolsStream(messages, tools);
3064
3127
  }
3065
- function initLLM() {
3066
- const { getLLMApiKey: getLLMApiKey2, getLLMProvider: getLLMProvider2, getLLMModel: getLLMModel2, getLLMBaseUrl: getLLMBaseUrl2 } = (init_config(), __toCommonJS(config_exports));
3067
- const apiKey = getLLMApiKey2();
3128
+ function initLLM(options = {}) {
3129
+ const scope = options.scope ?? "memory";
3130
+ const apiKey = scope === "agent" ? getAgentLLMApiKey() : getLLMApiKey();
3068
3131
  if (!apiKey) {
3069
3132
  currentConfig = null;
3070
3133
  return null;
3071
3134
  }
3072
- const provider2 = getLLMProvider2();
3135
+ const provider2 = scope === "agent" ? getAgentLLMProvider() : getLLMProvider();
3073
3136
  const defaults = PROVIDER_DEFAULTS[provider2] ?? PROVIDER_DEFAULTS.openai;
3074
3137
  currentConfig = {
3075
3138
  provider: provider2,
3076
3139
  apiKey,
3077
- model: getLLMModel2(defaults.model),
3078
- baseUrl: getLLMBaseUrl2(defaults.baseUrl)
3140
+ model: scope === "agent" ? getAgentLLMModel(defaults.model) : getLLMModel(defaults.model),
3141
+ baseUrl: scope === "agent" ? getAgentLLMBaseUrl(defaults.baseUrl) : getLLMBaseUrl(defaults.baseUrl)
3079
3142
  };
3080
3143
  return currentConfig;
3081
3144
  }
@@ -3582,6 +3645,7 @@ var init_provider2 = __esm({
3582
3645
  "src/llm/provider.ts"() {
3583
3646
  "use strict";
3584
3647
  init_esm_shims();
3648
+ init_config();
3585
3649
  LLM_TIMEOUT_DEFAULT_MS = 3e4;
3586
3650
  LLM_TIMEOUT_MIN_MS = 1e3;
3587
3651
  LLM_TIMEOUT_MAX_MS = 3e5;
@@ -4411,6 +4475,10 @@ async function searchObservations(options) {
4411
4475
  if (statusFilter === "all") return true;
4412
4476
  const doc = hit.document;
4413
4477
  return (doc.status || "active") === statusFilter;
4478
+ }).filter((hit) => {
4479
+ if (options.type === "probe") return true;
4480
+ const doc = hit.document;
4481
+ return doc.type !== "probe";
4414
4482
  }).map((hit) => {
4415
4483
  const doc = hit.document;
4416
4484
  const obsType = doc.type;
@@ -6792,6 +6860,11 @@ function hasContradiction(oldText, newText) {
6792
6860
  ];
6793
6861
  return negationPatterns.some((p) => p.test(newText));
6794
6862
  }
6863
+ function normalizedSearchSimilarity(score) {
6864
+ if (!Number.isFinite(score) || score <= 0) return 0;
6865
+ if (score <= 1) return score;
6866
+ return 0;
6867
+ }
6795
6868
  function mergeNarratives(oldNarrative, newNarrative) {
6796
6869
  if (newNarrative.length > oldNarrative.length * 1.5) return newNarrative;
6797
6870
  if (oldNarrative.length > newNarrative.length * 1.5) return oldNarrative;
@@ -6882,12 +6955,13 @@ function scoreCandidate(extracted, candidate) {
6882
6955
  `${extracted.title} ${extracted.narrative}`,
6883
6956
  `${candidate.title} ${candidate.narrative}`
6884
6957
  );
6885
- const score = candidate.score * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
6958
+ const searchSimilarity = normalizedSearchSimilarity(candidate.score);
6959
+ const score = searchSimilarity * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
6886
6960
  const newLength = extracted.narrative.length + extracted.facts.join(" ").length;
6887
6961
  const oldLength = candidate.narrative.length + candidate.facts.length;
6888
6962
  const richer = newLength > oldLength * 1.15;
6889
6963
  const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
6890
- return { score, entityMatch, richer, contradiction };
6964
+ return { score, searchSimilarity, entityMatch, richer, contradiction };
6891
6965
  }
6892
6966
  async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
6893
6967
  const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
@@ -6910,7 +6984,7 @@ async function runResolve(extracted, projectId, searchMemories, getObservation2,
6910
6984
  }));
6911
6985
  scored.sort((a, b) => b.score - a.score);
6912
6986
  const best = scored[0];
6913
- if (best.hit.score >= SIMILARITY_DUPLICATE) {
6987
+ if (best.searchSimilarity >= SIMILARITY_DUPLICATE) {
6914
6988
  if (best.richer) {
6915
6989
  const existing = getObservation2(best.hit.observationId);
6916
6990
  const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
@@ -7097,7 +7171,8 @@ var init_evaluate = __esm({
7097
7171
  "how-it-works": 0.6,
7098
7172
  "discovery": 0.55,
7099
7173
  "what-changed": 0.45,
7100
- "session-request": 0.4
7174
+ "session-request": 0.4,
7175
+ "probe": 0.1
7101
7176
  };
7102
7177
  SPECIFICITY_PATTERNS = [
7103
7178
  /\b\d+\.\d+\.\d+\b/,
@@ -7561,6 +7636,9 @@ function scoreObservationForSessionContext(obs, projectTokens, now = Date.now())
7561
7636
  if (obs.valueCategory === "core") {
7562
7637
  score += 2;
7563
7638
  }
7639
+ if (obs.type === "probe") {
7640
+ score -= 100;
7641
+ }
7564
7642
  return score;
7565
7643
  }
7566
7644
  async function startSession(projectDir2, projectId, opts) {
@@ -7769,7 +7847,8 @@ var init_session = __esm({
7769
7847
  "how-it-works": "[INFO]",
7770
7848
  "what-changed": "[CHANGE]",
7771
7849
  "why-it-exists": "[DECISION]",
7772
- "session-request": "[SESSION]"
7850
+ "session-request": "[SESSION]",
7851
+ "probe": "[PROBE]"
7773
7852
  };
7774
7853
  TYPE_WEIGHTS2 = {
7775
7854
  "gotcha": 6,
@@ -7848,11 +7927,17 @@ function getValueCategoryMultiplier(doc) {
7848
7927
  return 1;
7849
7928
  }
7850
7929
  function getEffectiveRetentionDays(doc) {
7930
+ const typeOverride = TYPE_RETENTION_OVERRIDE[doc.type];
7931
+ if (typeOverride !== void 0) {
7932
+ const raw2 = typeOverride * getSourceRetentionMultiplier(doc);
7933
+ return Math.max(MIN_RETENTION_DAYS, raw2);
7934
+ }
7851
7935
  const importance = getImportanceLevel(doc);
7852
7936
  const raw = RETENTION_DAYS[importance] * getSourceRetentionMultiplier(doc) * getValueCategoryMultiplier(doc);
7853
7937
  return Math.max(MIN_RETENTION_DAYS, raw);
7854
7938
  }
7855
7939
  function isImmune(doc) {
7940
+ if (doc.type === "probe") return false;
7856
7941
  if (doc.valueCategory === "core") return true;
7857
7942
  const importance = getImportanceLevel(doc);
7858
7943
  if (importance === "critical") return true;
@@ -7861,6 +7946,7 @@ function isImmune(doc) {
7861
7946
  return concepts.some((c) => PROTECTED_TAGS.has(c));
7862
7947
  }
7863
7948
  function getImmunityReason(doc) {
7949
+ if (doc.type === "probe") return null;
7864
7950
  if (doc.valueCategory === "core") return "core valueCategory (formation-classified)";
7865
7951
  const importance = getImportanceLevel(doc);
7866
7952
  if (importance === "critical") return "critical importance";
@@ -8017,7 +8103,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap) {
8017
8103
  return { archived: archivedCount, remaining: activeObs.length - archivedCount };
8018
8104
  });
8019
8105
  }
8020
- var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY, MIN_RETENTION_DAYS;
8106
+ var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, TYPE_RETENTION_OVERRIDE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY, MIN_RETENTION_DAYS;
8021
8107
  var init_retention = __esm({
8022
8108
  "src/memory/retention.ts"() {
8023
8109
  "use strict";
@@ -8045,7 +8131,12 @@ var init_retention = __esm({
8045
8131
  "what-changed": "low",
8046
8132
  "why-it-exists": "medium",
8047
8133
  discovery: "low",
8048
- "session-request": "low"
8134
+ "session-request": "low",
8135
+ probe: "low"
8136
+ };
8137
+ TYPE_RETENTION_OVERRIDE = {
8138
+ probe: 7
8139
+ // Operational heartbeats: expire after ~7 days regardless of source/valueCategory
8049
8140
  };
8050
8141
  PROTECTED_TAGS = /* @__PURE__ */ new Set(["keep", "important", "pinned", "critical"]);
8051
8142
  MIN_ACCESS_FOR_IMMUNITY = 3;
@@ -9773,9 +9864,453 @@ var init_project_classification = __esm({
9773
9864
  }
9774
9865
  });
9775
9866
 
9867
+ // src/wiki/generator.ts
9868
+ var generator_exports = {};
9869
+ __export(generator_exports, {
9870
+ contextualHasSubstance: () => contextualHasSubstance,
9871
+ generateKnowledgeBase: () => generateKnowledgeBase,
9872
+ isCommandLog: () => isCommandLog,
9873
+ isEligible: () => isEligible,
9874
+ isExcludedType: () => isExcludedType
9875
+ });
9876
+ function isExcludedType(o) {
9877
+ return o.type === "probe";
9878
+ }
9879
+ function isCommandLog(o) {
9880
+ return COMMAND_LOG_TITLE3.test(o.title || "");
9881
+ }
9882
+ function isInactive(o) {
9883
+ const status = o.status ?? "active";
9884
+ return status !== "active";
9885
+ }
9886
+ function isOtherProject(o, projectId) {
9887
+ return o.projectId !== projectId;
9888
+ }
9889
+ function contextualHasSubstance(o) {
9890
+ if (o.valueCategory !== "contextual") return true;
9891
+ const hasFacts = (o.facts?.length ?? 0) > 0;
9892
+ const hasConcepts = (o.concepts?.length ?? 0) > 0;
9893
+ const hasFiles = (o.filesModified?.length ?? 0) > 0;
9894
+ const hasEntity = !!(o.entityName && o.entityName !== "quick-note" && o.entityName !== "unknown");
9895
+ return hasFacts || hasConcepts || hasFiles || hasEntity;
9896
+ }
9897
+ function isEligible(o, projectId) {
9898
+ if (isExcludedType(o)) return false;
9899
+ if (isCommandLog(o)) return false;
9900
+ if (isInactive(o)) return false;
9901
+ if (isOtherProject(o, projectId)) return false;
9902
+ if (o.valueCategory === "ephemeral") return false;
9903
+ if (!contextualHasSubstance(o)) return false;
9904
+ return true;
9905
+ }
9906
+ function obsToItem(o) {
9907
+ const ref = {
9908
+ kind: o.source === "git" ? "git" : "observation",
9909
+ id: `obs:${o.id}`,
9910
+ title: o.title
9911
+ };
9912
+ return {
9913
+ title: o.title,
9914
+ summary: o.narrative?.slice(0, 200) || "",
9915
+ type: o.type,
9916
+ entityName: o.entityName || void 0,
9917
+ refs: [ref]
9918
+ };
9919
+ }
9920
+ function skillToItem(s) {
9921
+ const ref = {
9922
+ kind: "mini-skill",
9923
+ id: `skill:${s.id}`,
9924
+ title: s.title
9925
+ };
9926
+ const obsRefs = s.sourceObservationIds.map(
9927
+ (oid) => ({ kind: "observation", id: `obs:${oid}` })
9928
+ );
9929
+ return {
9930
+ title: s.title,
9931
+ summary: s.instruction?.slice(0, 200) || "",
9932
+ type: "mini-skill",
9933
+ entityName: s.sourceEntity || void 0,
9934
+ refs: [ref, ...obsRefs]
9935
+ };
9936
+ }
9937
+ function buildGitSection(observations2) {
9938
+ const gitObs = observations2.filter(
9939
+ (o) => o.source === "git" && o.sourceDetail === "git-ingest"
9940
+ );
9941
+ const items = gitObs.map(obsToItem);
9942
+ return {
9943
+ id: "git-backed-facts",
9944
+ title: "Git-backed Facts",
9945
+ items,
9946
+ empty: items.length === 0
9947
+ };
9948
+ }
9949
+ function buildSkillsSection(skills) {
9950
+ const items = skills.map(skillToItem);
9951
+ return {
9952
+ id: "promoted-skills",
9953
+ title: "Promoted Skills",
9954
+ items,
9955
+ empty: items.length === 0
9956
+ };
9957
+ }
9958
+ function buildProjectOverview(projectId, eligibleObs, skills) {
9959
+ const refs = [
9960
+ ...eligibleObs.slice(0, 5).map((o) => ({
9961
+ kind: o.source === "git" ? "git" : "observation",
9962
+ id: `obs:${o.id}`,
9963
+ title: o.title
9964
+ })),
9965
+ ...skills.slice(0, 5).map((s) => ({
9966
+ kind: "mini-skill",
9967
+ id: `skill:${s.id}`,
9968
+ title: s.title
9969
+ }))
9970
+ ];
9971
+ if (refs.length === 0) {
9972
+ return {
9973
+ id: "project-overview",
9974
+ title: "Project Overview",
9975
+ items: [],
9976
+ empty: true
9977
+ };
9978
+ }
9979
+ const lines = [];
9980
+ lines.push(`Project: ${projectId}`);
9981
+ lines.push(`Observations in KB: ${eligibleObs.length}`);
9982
+ lines.push(`Promoted skills: ${skills.length}`);
9983
+ const entityCounts = /* @__PURE__ */ new Map();
9984
+ for (const o of eligibleObs) {
9985
+ if (o.entityName) {
9986
+ entityCounts.set(o.entityName, (entityCounts.get(o.entityName) || 0) + 1);
9987
+ }
9988
+ }
9989
+ const topEntities = [...entityCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
9990
+ if (topEntities.length > 0) {
9991
+ lines.push(`Top entities: ${topEntities.map(([name, count2]) => `${name} (${count2})`).join(", ")}`);
9992
+ }
9993
+ return {
9994
+ id: "project-overview",
9995
+ title: "Project Overview",
9996
+ items: [{
9997
+ title: projectId,
9998
+ summary: lines.join("\n"),
9999
+ type: "overview",
10000
+ refs
10001
+ }]
10002
+ };
10003
+ }
10004
+ function generateKnowledgeBase(options) {
10005
+ const { projectId, observations: observations2, miniSkills } = options;
10006
+ const eligible = observations2.filter((o) => isEligible(o, projectId));
10007
+ const scopedMiniSkills = miniSkills.filter((s) => s.projectId === projectId);
10008
+ const typedSections = SECTION_DEFS.map((def) => {
10009
+ const matched = eligible.filter(def.typeMatch);
10010
+ const items = matched.map(obsToItem);
10011
+ return {
10012
+ id: def.id,
10013
+ title: def.title,
10014
+ items,
10015
+ empty: items.length === 0
10016
+ };
10017
+ });
10018
+ const projectOverview = buildProjectOverview(projectId, eligible, scopedMiniSkills);
10019
+ const gitSection = buildGitSection(eligible);
10020
+ const skillsSection = buildSkillsSection(scopedMiniSkills);
10021
+ const sections = [
10022
+ projectOverview,
10023
+ ...typedSections,
10024
+ gitSection,
10025
+ skillsSection
10026
+ ];
10027
+ const allRefs = sections.flatMap((s) => s.items.flatMap((i) => i.refs));
10028
+ const obsRefCount = allRefs.filter((r) => r.kind === "observation" || r.kind === "git").length;
10029
+ const skillRefCount = allRefs.filter((r) => r.kind === "mini-skill").length;
10030
+ return {
10031
+ title: "Knowledge Base",
10032
+ subtitle: "LLM Wiki",
10033
+ projectId,
10034
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
10035
+ sections,
10036
+ stats: {
10037
+ observationsUsed: eligible.length,
10038
+ miniSkillsUsed: scopedMiniSkills.length,
10039
+ refs: obsRefCount + skillRefCount
10040
+ }
10041
+ };
10042
+ }
10043
+ var COMMAND_LOG_TITLE3, SECTION_DEFS;
10044
+ var init_generator = __esm({
10045
+ "src/wiki/generator.ts"() {
10046
+ "use strict";
10047
+ init_esm_shims();
10048
+ COMMAND_LOG_TITLE3 = /^(Ran:|Command:|Executed:)\s/i;
10049
+ SECTION_DEFS = [
10050
+ {
10051
+ id: "core-decisions",
10052
+ title: "Core Decisions",
10053
+ typeMatch: (o) => o.type === "decision" || o.type === "trade-off" || o.type === "reasoning"
10054
+ },
10055
+ {
10056
+ id: "operational-knowledge",
10057
+ title: "Operational Knowledge",
10058
+ typeMatch: (o) => o.type === "how-it-works" || o.type === "what-changed" || o.type === "why-it-exists" || o.type === "discovery" || o.type === "session-request"
10059
+ },
10060
+ {
10061
+ id: "known-gotchas",
10062
+ title: "Known Gotchas",
10063
+ typeMatch: (o) => o.type === "gotcha" || o.type === "problem-solution"
10064
+ }
10065
+ ];
10066
+ }
10067
+ });
10068
+
10069
+ // src/wiki/knowledge-graph.ts
10070
+ var knowledge_graph_exports = {};
10071
+ __export(knowledge_graph_exports, {
10072
+ generateKnowledgeGraph: () => generateKnowledgeGraph
10073
+ });
10074
+ function inferEdges(nodes, entityNameIndex) {
10075
+ const edges = [];
10076
+ const seen = /* @__PURE__ */ new Set();
10077
+ const maxRelatesEdgesPerEntitySection = 8;
10078
+ function addEdge(source, target, edgeType) {
10079
+ const key = `${source}:${edgeType}:${target}`;
10080
+ if (seen.has(key)) return;
10081
+ if (source === target) return;
10082
+ seen.add(key);
10083
+ edges.push({
10084
+ id: `e_${edges.length}_${source}_${target}`,
10085
+ source,
10086
+ target,
10087
+ edgeType
10088
+ });
10089
+ }
10090
+ function rankNodesForEntity(a, b) {
10091
+ const evidenceDiff = (b.evidenceCount || 0) - (a.evidenceCount || 0);
10092
+ if (evidenceDiff !== 0) return evidenceDiff;
10093
+ return a.id.localeCompare(b.id);
10094
+ }
10095
+ for (const [, group] of entityNameIndex) {
10096
+ if (group.length < 2) continue;
10097
+ const bySection = /* @__PURE__ */ new Map();
10098
+ for (const node of group) {
10099
+ const sectionGroup = bySection.get(node.sectionId) || [];
10100
+ sectionGroup.push(node);
10101
+ bySection.set(node.sectionId, sectionGroup);
10102
+ }
10103
+ for (const sectionGroup of bySection.values()) {
10104
+ const ranked = [...sectionGroup].sort(rankNodesForEntity);
10105
+ if (ranked.length === 2) {
10106
+ addEdge(ranked[0].id, ranked[1].id, "relates_to");
10107
+ addEdge(ranked[1].id, ranked[0].id, "relates_to");
10108
+ continue;
10109
+ }
10110
+ const anchor = ranked[0];
10111
+ for (const node of ranked.slice(1, maxRelatesEdgesPerEntitySection + 1)) {
10112
+ addEdge(anchor.id, node.id, "relates_to");
10113
+ }
10114
+ }
10115
+ const sectionAnchors = [...bySection.entries()].map(([sectionId, sectionGroup]) => ({
10116
+ sectionId,
10117
+ anchor: [...sectionGroup].sort(rankNodesForEntity)[0]
10118
+ })).sort((a, b) => sectionPriority(a.sectionId) - sectionPriority(b.sectionId));
10119
+ for (let i = 0; i < sectionAnchors.length; i++) {
10120
+ for (let j = i + 1; j < sectionAnchors.length; j++) {
10121
+ const from = sectionAnchors[i].anchor;
10122
+ const to = sectionAnchors[j].anchor;
10123
+ const edgeType = sectionPriority(sectionAnchors[i].sectionId) === sectionPriority(sectionAnchors[j].sectionId) ? "relates_to" : "supports";
10124
+ addEdge(from.id, to.id, edgeType);
10125
+ }
10126
+ }
10127
+ }
10128
+ return edges;
10129
+ }
10130
+ function sectionPriority(sectionId) {
10131
+ const order = ["core-decisions", "known-gotchas", "operational-knowledge", "git-backed-facts", "promoted-skills"];
10132
+ const idx = order.indexOf(sectionId);
10133
+ return idx >= 0 ? idx : order.length;
10134
+ }
10135
+ function sectionIdForObs(o) {
10136
+ if (GIT_SECTION.typeMatch(o)) return GIT_SECTION.id;
10137
+ for (const def of SECTION_DEFS2) {
10138
+ if (def.typeMatch(o)) return def.id;
10139
+ }
10140
+ return "operational-knowledge";
10141
+ }
10142
+ function mapRelationType(relationType) {
10143
+ const lower = relationType.toLowerCase();
10144
+ if (lower.includes("support") || lower.includes("depend")) return "supports";
10145
+ if (lower.includes("relat") || lower.includes("connect") || lower.includes("associat")) return "relates_to";
10146
+ if (lower.includes("mention") || lower.includes("refer")) return "mentions";
10147
+ if (lower.includes("deriv") || lower.includes("origin") || lower.includes("source")) return "derived_from";
10148
+ return "relates_to";
10149
+ }
10150
+ function generateKnowledgeGraph(options) {
10151
+ const { projectId, observations: observations2, miniSkills, graphEntities, graphRelations } = options;
10152
+ const eligible = observations2.filter((o) => isEligible(o, projectId));
10153
+ const scopedMiniSkills = miniSkills.filter((s) => s.projectId === projectId);
10154
+ const nodes = [];
10155
+ const entityNameIndex = /* @__PURE__ */ new Map();
10156
+ for (const o of eligible) {
10157
+ const sectionId = sectionIdForObs(o);
10158
+ const ref = {
10159
+ kind: o.source === "git" ? "git" : "observation",
10160
+ id: `obs:${o.id}`,
10161
+ title: o.title
10162
+ };
10163
+ const node = {
10164
+ id: `obs:${o.id}`,
10165
+ label: o.title,
10166
+ nodeType: o.type,
10167
+ sectionId,
10168
+ entityName: o.entityName || void 0,
10169
+ evidenceCount: (o.facts?.length ?? 0) + (o.concepts?.length ?? 0) + (o.filesModified?.length ?? 0),
10170
+ summary: o.narrative?.slice(0, 200) || "",
10171
+ refs: [ref]
10172
+ };
10173
+ nodes.push(node);
10174
+ if (o.entityName) {
10175
+ const group = entityNameIndex.get(o.entityName) || [];
10176
+ group.push(node);
10177
+ entityNameIndex.set(o.entityName, group);
10178
+ }
10179
+ }
10180
+ for (const s of scopedMiniSkills) {
10181
+ const ref = {
10182
+ kind: "mini-skill",
10183
+ id: `skill:${s.id}`,
10184
+ title: s.title
10185
+ };
10186
+ const obsRefs = s.sourceObservationIds.map(
10187
+ (oid) => ({ kind: "observation", id: `obs:${oid}` })
10188
+ );
10189
+ const node = {
10190
+ id: `skill:${s.id}`,
10191
+ label: s.title,
10192
+ nodeType: "mini-skill",
10193
+ sectionId: "promoted-skills",
10194
+ entityName: s.sourceEntity || void 0,
10195
+ evidenceCount: obsRefs.length,
10196
+ summary: s.instruction?.slice(0, 200) || "",
10197
+ refs: [ref, ...obsRefs]
10198
+ };
10199
+ nodes.push(node);
10200
+ if (s.sourceEntity) {
10201
+ const group = entityNameIndex.get(s.sourceEntity) || [];
10202
+ group.push(node);
10203
+ entityNameIndex.set(s.sourceEntity, group);
10204
+ }
10205
+ }
10206
+ const edges = inferEdges(nodes, entityNameIndex);
10207
+ for (const s of scopedMiniSkills) {
10208
+ const skillNodeId = `skill:${s.id}`;
10209
+ for (const oid of s.sourceObservationIds) {
10210
+ const obsNodeId = `obs:${oid}`;
10211
+ if (nodes.some((n) => n.id === obsNodeId)) {
10212
+ edges.push({
10213
+ id: `e_${edges.length}_${skillNodeId}_${obsNodeId}`,
10214
+ source: skillNodeId,
10215
+ target: obsNodeId,
10216
+ edgeType: "derived_from"
10217
+ });
10218
+ }
10219
+ }
10220
+ }
10221
+ if (graphRelations && graphRelations.length > 0) {
10222
+ const entityNodeMap = /* @__PURE__ */ new Map();
10223
+ for (const n of nodes) {
10224
+ if (n.entityName && !entityNodeMap.has(n.entityName)) {
10225
+ entityNodeMap.set(n.entityName, n.id);
10226
+ }
10227
+ }
10228
+ const seen = new Set(edges.map((e) => `${e.source}:${e.edgeType}:${e.target}`));
10229
+ for (const rel of graphRelations) {
10230
+ const srcId = entityNodeMap.get(rel.from);
10231
+ const tgtId = entityNodeMap.get(rel.to);
10232
+ if (!srcId || !tgtId) continue;
10233
+ if (srcId === tgtId) continue;
10234
+ const edgeType = mapRelationType(rel.relationType);
10235
+ const key = `${srcId}:${edgeType}:${tgtId}`;
10236
+ if (seen.has(key)) continue;
10237
+ seen.add(key);
10238
+ edges.push({
10239
+ id: `e_gr_${edges.length}_${srcId}_${tgtId}`,
10240
+ source: srcId,
10241
+ target: tgtId,
10242
+ edgeType
10243
+ });
10244
+ }
10245
+ }
10246
+ const sectionCounts = {};
10247
+ for (const n of nodes) {
10248
+ sectionCounts[n.sectionId] = (sectionCounts[n.sectionId] || 0) + 1;
10249
+ }
10250
+ const clusters = ALL_SECTIONS.filter((def) => (sectionCounts[def.id] ?? 0) > 0).map((def) => ({
10251
+ id: `cluster:${def.id}`,
10252
+ label: def.label,
10253
+ sectionId: def.id,
10254
+ nodeCount: sectionCounts[def.id] ?? 0
10255
+ }));
10256
+ const stats = {
10257
+ totalNodes: nodes.length,
10258
+ totalEdges: edges.length,
10259
+ clusterCount: clusters.length,
10260
+ sectionCounts
10261
+ };
10262
+ return {
10263
+ title: "Knowledge Graph",
10264
+ projectId,
10265
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
10266
+ nodes,
10267
+ edges,
10268
+ clusters,
10269
+ stats
10270
+ };
10271
+ }
10272
+ var SECTION_DEFS2, GIT_SECTION, SKILLS_SECTION, ALL_SECTIONS;
10273
+ var init_knowledge_graph = __esm({
10274
+ "src/wiki/knowledge-graph.ts"() {
10275
+ "use strict";
10276
+ init_esm_shims();
10277
+ init_generator();
10278
+ SECTION_DEFS2 = [
10279
+ {
10280
+ id: "core-decisions",
10281
+ label: "Core Decisions",
10282
+ typeMatch: (o) => o.type === "decision" || o.type === "trade-off" || o.type === "reasoning"
10283
+ },
10284
+ {
10285
+ id: "operational-knowledge",
10286
+ label: "Operational Knowledge",
10287
+ typeMatch: (o) => o.type === "how-it-works" || o.type === "what-changed" || o.type === "why-it-exists" || o.type === "discovery" || o.type === "session-request"
10288
+ },
10289
+ {
10290
+ id: "known-gotchas",
10291
+ label: "Known Gotchas",
10292
+ typeMatch: (o) => o.type === "gotcha" || o.type === "problem-solution"
10293
+ }
10294
+ ];
10295
+ GIT_SECTION = {
10296
+ id: "git-backed-facts",
10297
+ label: "Git-backed Facts",
10298
+ typeMatch: (o) => o.source === "git" && o.sourceDetail === "git-ingest"
10299
+ };
10300
+ SKILLS_SECTION = {
10301
+ id: "promoted-skills",
10302
+ label: "Promoted Skills",
10303
+ typeMatch: () => false
10304
+ // skills handled separately
10305
+ };
10306
+ ALL_SECTIONS = [...SECTION_DEFS2, GIT_SECTION, SKILLS_SECTION];
10307
+ }
10308
+ });
10309
+
9776
10310
  // src/dashboard/server.ts
9777
10311
  var server_exports = {};
9778
10312
  __export(server_exports, {
10313
+ prepareDashboardConfig: () => prepareDashboardConfig,
9779
10314
  startDashboard: () => startDashboard
9780
10315
  });
9781
10316
  import { createServer } from "http";
@@ -9801,6 +10336,25 @@ function isActiveStatus(status) {
9801
10336
  function filterActiveByProject(items, projectId) {
9802
10337
  return items.filter((item) => item.projectId === projectId && isActiveStatus(item.status));
9803
10338
  }
10339
+ function prepareDashboardConfig(projectRoot) {
10340
+ if (!projectRoot) {
10341
+ if (preparedDashboardProjectRoot !== null) {
10342
+ resetDotenv();
10343
+ preparedDashboardProjectRoot = null;
10344
+ }
10345
+ clearProjectRoot();
10346
+ return;
10347
+ }
10348
+ try {
10349
+ if (preparedDashboardProjectRoot !== null && preparedDashboardProjectRoot !== projectRoot) {
10350
+ resetDotenv();
10351
+ }
10352
+ initProjectRoot(projectRoot);
10353
+ loadDotenv(projectRoot);
10354
+ preparedDashboardProjectRoot = projectRoot;
10355
+ } catch {
10356
+ }
10357
+ }
9804
10358
  function computeProjectGraphCounts(allEntities, allRelations, projectObs) {
9805
10359
  const entityNames = new Set(
9806
10360
  projectObs.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
@@ -9826,6 +10380,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9826
10380
  effectiveProjectResolved = true;
9827
10381
  effectiveProjectRoot = null;
9828
10382
  }
10383
+ prepareDashboardConfig(effectiveProjectRoot);
9829
10384
  try {
9830
10385
  switch (apiPath) {
9831
10386
  case "/projects": {
@@ -9914,6 +10469,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9914
10469
  const typeCounts = {};
9915
10470
  for (const obs of observations2) {
9916
10471
  const t = obs.type || "unknown";
10472
+ if (t === "probe") continue;
9917
10473
  typeCounts[t] = (typeCounts[t] || 0) + 1;
9918
10474
  }
9919
10475
  const sourceCounts = { git: 0, agent: 0, manual: 0 };
@@ -9955,7 +10511,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9955
10511
  else if (score >= 1) retentionSummary.stale++;
9956
10512
  else retentionSummary.archive++;
9957
10513
  }
9958
- const sorted = [...observations2].sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
10514
+ const sorted = [...observations2].filter((o) => o.type !== "probe").sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
9959
10515
  let embeddingStatus = { enabled: false, provider: "", dimensions: 0 };
9960
10516
  try {
9961
10517
  const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
@@ -9995,7 +10551,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
9995
10551
  const allObs = await getObservationStore().loadAll();
9996
10552
  const observations2 = filterActiveByProject(allObs, effectiveProjectId);
9997
10553
  const now = Date.now();
9998
- const scored = observations2.map((obs) => {
10554
+ const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
9999
10555
  const age = now - new Date(obs.createdAt || now).getTime();
10000
10556
  const ageHours = age / (1e3 * 60 * 60);
10001
10557
  const importance = obs.importance ?? 5;
@@ -10027,6 +10583,50 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
10027
10583
  });
10028
10584
  break;
10029
10585
  }
10586
+ case "/knowledge": {
10587
+ const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
10588
+ const { initObservations: initObservations2, getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
10589
+ const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
10590
+ await initObservations2(effectiveDataDir);
10591
+ await initMiniSkillStore2(effectiveDataDir);
10592
+ const allObs = getAllObservations2();
10593
+ const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
10594
+ const overview = generateKnowledgeBase2({
10595
+ projectId: effectiveProjectId,
10596
+ observations: allObs,
10597
+ miniSkills: skills
10598
+ });
10599
+ sendJson(res, overview);
10600
+ break;
10601
+ }
10602
+ case "/knowledge-graph": {
10603
+ const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
10604
+ const { initObservations: initObservations2, getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
10605
+ const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
10606
+ const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
10607
+ await initObservations2(effectiveDataDir);
10608
+ await initMiniSkillStore2(effectiveDataDir);
10609
+ await initGraphStore2(effectiveDataDir);
10610
+ const allObs = getAllObservations2();
10611
+ const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
10612
+ const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
10613
+ const graphObs = await getObservationStore().loadAll();
10614
+ const projectEntityNames = new Set(
10615
+ graphObs.filter((o) => o.projectId === effectiveProjectId && (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
10616
+ );
10617
+ const scopedEntities = fullGraph.entities.filter((e) => projectEntityNames.has(e.name));
10618
+ const scopedEntityNameSet = new Set(scopedEntities.map((e) => e.name));
10619
+ const scopedRelations = fullGraph.relations.filter((r) => scopedEntityNameSet.has(r.from) && scopedEntityNameSet.has(r.to));
10620
+ const graph = generateKnowledgeGraph2({
10621
+ projectId: effectiveProjectId,
10622
+ observations: allObs,
10623
+ miniSkills: skills,
10624
+ graphEntities: scopedEntities,
10625
+ graphRelations: scopedRelations
10626
+ });
10627
+ sendJson(res, graph);
10628
+ break;
10629
+ }
10030
10630
  case "/config": {
10031
10631
  const os4 = await import("os");
10032
10632
  const { existsSync: existsSync10 } = await import("fs");
@@ -10035,7 +10635,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
10035
10635
  const configProjectRoot = effectiveProjectRoot;
10036
10636
  try {
10037
10637
  const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
10038
- yml = loadYamlConfig2();
10638
+ yml = configProjectRoot ? loadYamlConfig2(configProjectRoot) : loadYamlConfig2(null);
10039
10639
  } catch {
10040
10640
  }
10041
10641
  if (configProjectRoot) {
@@ -10098,6 +10698,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
10098
10698
  } else {
10099
10699
  values.push({ key: "llm.apiKey", value: "not set", source: "none" });
10100
10700
  }
10701
+ const agentProvider = process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
10702
+ if (agentProvider) values.push({ key: "agent.llm.provider", value: agentProvider, source: await getEnvSource("MEMORIX_AGENT_LLM_PROVIDER", yml.agent?.provider ? "memorix.yml" : void 0) });
10703
+ const agentModel = process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
10704
+ if (agentModel) values.push({ key: "agent.llm.model", value: agentModel, source: await getEnvSource("MEMORIX_AGENT_LLM_MODEL", yml.agent?.model ? "memorix.yml" : void 0) });
10705
+ const agentKey = process.env.MEMORIX_AGENT_LLM_API_KEY || yml.agent?.apiKey;
10706
+ if (agentKey) {
10707
+ let src = "unknown";
10708
+ if (process.env.MEMORIX_AGENT_LLM_API_KEY) src = await getEnvSource("MEMORIX_AGENT_LLM_API_KEY");
10709
+ else if (yml.agent?.apiKey) src = "memorix.yml (move to .env!)";
10710
+ values.push({ key: "agent.llm.apiKey", value: "****" + agentKey.slice(-4), source: src, sensitive: true });
10711
+ } else {
10712
+ values.push({ key: "agent.llm.apiKey", value: "fallback to llm.apiKey", source: "default" });
10713
+ }
10101
10714
  const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
10102
10715
  values.push({ key: "embedding.provider", value: embProvider, source: await getEnvSource("MEMORIX_EMBEDDING", yml.embedding?.provider ? "memorix.yml" : void 0) });
10103
10716
  values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
@@ -10263,13 +10876,20 @@ async function serveStatic(req, res, staticDir) {
10263
10876
  const ext = path14.extname(filePath);
10264
10877
  res.writeHead(200, {
10265
10878
  "Content-Type": MIME_TYPES[ext] || "application/octet-stream",
10266
- "Cache-Control": "no-cache"
10879
+ "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
10880
+ "Pragma": "no-cache",
10881
+ "Expires": "0"
10267
10882
  });
10268
10883
  res.end(data);
10269
10884
  } catch {
10270
10885
  try {
10271
10886
  const indexData = await fs12.readFile(path14.join(staticDir, "index.html"));
10272
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
10887
+ res.writeHead(200, {
10888
+ "Content-Type": "text/html; charset=utf-8",
10889
+ "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
10890
+ "Pragma": "no-cache",
10891
+ "Expires": "0"
10892
+ });
10273
10893
  res.end(indexData);
10274
10894
  } catch {
10275
10895
  sendError(res, "Not found", 404);
@@ -10526,7 +11146,7 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
10526
11146
  });
10527
11147
  });
10528
11148
  }
10529
- var MIME_TYPES;
11149
+ var MIME_TYPES, preparedDashboardProjectRoot;
10530
11150
  var init_server = __esm({
10531
11151
  "src/dashboard/server.ts"() {
10532
11152
  "use strict";
@@ -10535,6 +11155,10 @@ var init_server = __esm({
10535
11155
  init_obs_store();
10536
11156
  init_session_store();
10537
11157
  init_graph_store();
11158
+ init_dotenv_loader();
11159
+ init_dotenv_loader();
11160
+ init_yaml_loader();
11161
+ init_yaml_loader();
10538
11162
  MIME_TYPES = {
10539
11163
  ".html": "text/html; charset=utf-8",
10540
11164
  ".css": "text/css; charset=utf-8",
@@ -10544,6 +11168,7 @@ var init_server = __esm({
10544
11168
  ".png": "image/png",
10545
11169
  ".ico": "image/x-icon"
10546
11170
  };
11171
+ preparedDashboardProjectRoot = null;
10547
11172
  }
10548
11173
  });
10549
11174
 
@@ -11700,7 +12325,7 @@ function generateKiroHookFiles() {
11700
12325
  when: { type: "promptSubmit" },
11701
12326
  then: {
11702
12327
  type: "askAgent",
11703
- 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"
12328
+ 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"
11704
12329
  }
11705
12330
  }, null, 2)
11706
12331
  },
@@ -11740,6 +12365,8 @@ import { spawnSync } from 'node:child_process';
11740
12365
  export const MemorixPlugin = async ({ project, client, $, directory, worktree }) => {
11741
12366
  // Generate a stable session ID for this plugin lifetime
11742
12367
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
12368
+ let pendingAssistantResponse = null;
12369
+ let lastDeliveredAssistantKey = '';
11743
12370
 
11744
12371
  /**
11745
12372
  * Send event JSON to \`memorix hook\` via child_process.spawnSync.
@@ -11772,6 +12399,33 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
11772
12399
  }
11773
12400
  }
11774
12401
 
12402
+ function extractMessageInfo(input) {
12403
+ if (input && typeof input === 'object') {
12404
+ if (input.info && typeof input.info === 'object') return input.info;
12405
+ if (input.message && typeof input.message === 'object') return input.message;
12406
+ if (input.properties && typeof input.properties === 'object') {
12407
+ if (input.properties.info && typeof input.properties.info === 'object') return input.properties.info;
12408
+ if (input.properties.message && typeof input.properties.message === 'object') return input.properties.message;
12409
+ }
12410
+ }
12411
+ return input;
12412
+ }
12413
+
12414
+ function extractMessageText(message) {
12415
+ if (!message || typeof message !== 'object') return '';
12416
+ if (typeof message.content === 'string') return message.content.trim();
12417
+ const parts = Array.isArray(message.parts) ? message.parts : [];
12418
+ return parts
12419
+ .map((part) => {
12420
+ if (!part || typeof part !== 'object') return '';
12421
+ if (typeof part.text === 'string') return part.text;
12422
+ if (typeof part.content === 'string') return part.content;
12423
+ return '';
12424
+ })
12425
+ .join('')
12426
+ .trim();
12427
+ }
12428
+
11775
12429
  return {
11776
12430
  /** Session created \u2014 record session start */
11777
12431
  'session.created': async ({ session }) => {
@@ -11784,6 +12438,21 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
11784
12438
 
11785
12439
  /** Session idle \u2014 record session end */
11786
12440
  'session.idle': async ({ session }) => {
12441
+ if (pendingAssistantResponse?.text) {
12442
+ const deliveryKey = pendingAssistantResponse.id
12443
+ ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
12444
+ : pendingAssistantResponse.text;
12445
+ if (deliveryKey !== lastDeliveredAssistantKey) {
12446
+ runHook({
12447
+ agent: 'opencode',
12448
+ hook_event_name: 'message.updated',
12449
+ ai_response: pendingAssistantResponse.text,
12450
+ message_id: pendingAssistantResponse.id,
12451
+ cwd: directory,
12452
+ });
12453
+ lastDeliveredAssistantKey = deliveryKey;
12454
+ }
12455
+ }
11787
12456
  runHook({
11788
12457
  agent: 'opencode',
11789
12458
  hook_event_name: 'session.idle',
@@ -11812,6 +12481,19 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
11812
12481
  });
11813
12482
  },
11814
12483
 
12484
+ /** Message updated \u2014 cache the latest assistant response until session.idle */
12485
+ 'message.updated': async (input, output) => {
12486
+ const message = extractMessageInfo(input);
12487
+ const role = message?.role ?? input?.role ?? input?.info?.role;
12488
+ if (role !== 'assistant') return;
12489
+ const text = extractMessageText(message);
12490
+ if (!text) return;
12491
+ pendingAssistantResponse = {
12492
+ id: message?.id ?? input?.id ?? input?.messageID,
12493
+ text,
12494
+ };
12495
+ },
12496
+
11815
12497
  /** Session compacted \u2014 record post-compact event */
11816
12498
  'session.compacted': async ({ session }) => {
11817
12499
  runHook({
@@ -12061,7 +12743,7 @@ async function installHooks(agent, projectRoot, global = false) {
12061
12743
  return {
12062
12744
  agent,
12063
12745
  configPath: pluginPath,
12064
- events: ["session_start", "session_end", "post_tool", "post_edit", "post_compact", "post_command"],
12746
+ events: ["session_start", "session_end", "post_tool", "post_edit", "post_compact", "post_command", "post_response"],
12065
12747
  generated: { note: "OpenCode plugin installed at " + pluginPath }
12066
12748
  };
12067
12749
  }
@@ -12254,20 +12936,18 @@ alwaysApply: true
12254
12936
 
12255
12937
  You have access to Memorix memory tools. Follow these rules to maintain persistent context across sessions.
12256
12938
 
12257
- ## RULE 1: Session Start \u2014 Bind Project, Then Load Context
12939
+ ## RULE 1: Use Memory When Useful
12258
12940
 
12259
- At the **beginning of every conversation**, BEFORE responding to the user:
12941
+ 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.
12260
12942
 
12261
- 1. Call \`memorix_session_start\` with parameters:
12262
- - \`agent\`: your agent identifier (e.g. "windsurf", "codex", "antigravity")
12263
- - \`projectRoot\`: the **absolute path** of the current workspace or repo root
12264
- This binds the session to the correct project. Without \`projectRoot\`, memories may go to the wrong bucket.
12265
- 2. Then call \`memorix_search\` with a query related to the user's first message for additional context
12266
- 3. If search results are found, use \`memorix_detail\` to fetch the most relevant ones
12267
- 4. Reference relevant memories naturally \u2014 the user should feel you "remember" them
12943
+ 1. Call \`memorix_search\` with a query related to the user's first message or current task.
12944
+ 2. If search results are found, use \`memorix_detail\` to fetch the most relevant ones.
12945
+ 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.
12946
+ 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.
12947
+ 5. Reference relevant memories naturally \u2014 the user should feel you understand the project, not that you are following a ritual.
12268
12948
 
12269
12949
  **Important:** \`projectRoot\` is a detection anchor only; Git remains the source of truth for project identity.
12270
- In HTTP control-plane mode (\`memorix serve-http\` / \`memorix background start\`), explicit \`projectRoot\` binding is required for correct multi-project isolation.
12950
+ 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.
12271
12951
  \`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.
12272
12952
 
12273
12953
  ## RULE 2: Store Important Context
@@ -12519,7 +13199,7 @@ var init_installers = __esm({
12519
13199
  "src/hooks/installers/index.ts"() {
12520
13200
  "use strict";
12521
13201
  init_esm_shims();
12522
- OPENCODE_PLUGIN_VERSION = 5;
13202
+ OPENCODE_PLUGIN_VERSION = 6;
12523
13203
  }
12524
13204
  });
12525
13205
 
@@ -12961,7 +13641,8 @@ function getTypeIcon(type) {
12961
13641
  "why-it-exists": "[WHY]",
12962
13642
  "decision": "[DECISION]",
12963
13643
  "trade-off": "[TRADEOFF]",
12964
- "reasoning": "[REASONING]"
13644
+ "reasoning": "[REASONING]",
13645
+ "probe": "[PROBE]"
12965
13646
  };
12966
13647
  return icons[type] ?? "[UNKNOWN]";
12967
13648
  }
@@ -13022,7 +13703,18 @@ init_mini_skills();
13022
13703
  init_secret_filter();
13023
13704
  async function compactSearch(options) {
13024
13705
  const entries = await searchObservations(options);
13025
- const formatted = formatIndexTable(entries, options.query, !options.projectId);
13706
+ let formatted = formatIndexTable(entries, options.query, !options.projectId);
13707
+ if (entries.length === 0 && options.projectId) {
13708
+ const allObservations = getAllObservations();
13709
+ const projectHasStoredMemory = allObservations.some((obs) => obs.projectId === options.projectId);
13710
+ if (!projectHasStoredMemory) {
13711
+ formatted = `This project does not have any Memorix memories yet.
13712
+
13713
+ It looks like a fresh project: the tool call worked, but there is nothing stored to retrieve yet.
13714
+
13715
+ Memories will start appearing after observations, session summaries, hook captures, or git-memory are written.`;
13716
+ }
13717
+ }
13026
13718
  const totalTokens = countTextTokens(formatted);
13027
13719
  return { entries, formatted, totalTokens };
13028
13720
  }
@@ -15460,7 +16152,8 @@ var OBSERVATION_TYPES = [
15460
16152
  "discovery",
15461
16153
  "why-it-exists",
15462
16154
  "decision",
15463
- "trade-off"
16155
+ "trade-off",
16156
+ "probe"
15464
16157
  ];
15465
16158
  function coerceNumberArray(val) {
15466
16159
  if (Array.isArray(val)) return val.map(Number);
@@ -15689,7 +16382,7 @@ The path should point to a directory containing a .git folder.`
15689
16382
  };
15690
16383
  const server = existingServer ?? new McpServer({
15691
16384
  name: "memorix",
15692
- version: true ? "1.0.8" : "1.0.1"
16385
+ version: true ? "1.0.10" : "1.0.1"
15693
16386
  });
15694
16387
  const originalRegisterTool = server.registerTool.bind(server);
15695
16388
  server.registerTool = ((name, ...args) => {